DEV Community

Cover image for Skills as Sub-Agents: Orchestrating Complex work with Claude Skills
Mukul S
Mukul S

Posted on

Skills as Sub-Agents: Orchestrating Complex work with Claude Skills

If you've built anything with a coding agent, you've hit the wall: the task is too big for one prompt. You ask it to "find out what's causing this bug," and it starts strong — then drowns. Half its context is the raw output of files it dumped, it's lost the thread of which theory it was testing, and the answer it finally gives is confidently wrong.
The problem isn't the model. It's that you asked one agent, with one context window, to do everything — gather evidence, hold it all in its head, and reason about it at the same time.
There's a cleaner pattern. Split the work into skills, and have one skill orchestrate the others as sub-agents. Let's walk through it.

New to skills or the agent loop? The one-line version: a skill is a packaged set of instructions on disk that the agent loads only when it's relevant. A sub-agent is a fresh agent instance with its own separate context window. This post is about combining the two.

The core idea: reasoning up top, work down below

Picture two layers.
The orchestrator sits on top. Its only job is to think: plan the investigation, interpret results, decide what to do next, and conclude. It never opens a file, never greps the codebase, never parses output. The first lines of an orchestrator skill should say exactly that:

You are a reasoning and coordination layer. You think, you plan, you interpret, you propose — but you never dig into the data yourself. That work belongs in sub-agents.
The workers sit below. Each is a small, focused skill that does one concrete thing — find every usage of a function, read a file's git history, run a specific test. The orchestrator spawns each worker as a sub-agent: a separate agent with its own fresh context window.

Why does this split matter so much? Context hygiene.
When a worker greps a huge codebase and reads a dozen files, all of that lands in the worker's context — not the orchestrator's. The worker chews through it, extracts the one thing that matters, and reports back a three-line summary. The orchestrator's context stays clean: it accumulates conclusions, not raw data. That's the whole trick, and it's why the orchestrator can run for dozens of steps without falling over.

Anatomy of a worker skill

A worker skill is just a folder with a SKILL.md file. The front matter does the heavy lifting:

---
name: find-usages
description: "Find every place a symbol, function, or pattern is used across the codebase. Use when tracing how a change ripples through the code."
context: fork
allowed-tools: Grep, Read
argument-hint: <symbol> [--path=<dir>]
---
# Find Usages
## Input
- **Symbol** (required): the function, class, or pattern to trace.
- **Path** (optional): limit the search to a directory.
- **Output file**: write your full report here.
## Procedure
1. Search the codebase for the symbol.
2. For each hit, read enough surrounding code to classify it
   (definition, call site, test, re-export).
3. Write the full annotated list to the output file.
4. Return a 3-line summary: total count, and the 1–2 most relevant sites.
Enter fullscreen mode Exit fullscreen mode

Four things are worth calling out, because they're what make it composable:

  • description — this is the skill's résumé. It's how the orchestrator decides whether to call this worker at all (more on that below). Write it for selection, not just documentation.
  • context: fork — run this skill in an isolated context. This is what makes it a sub-agent instead of inline instructions.
  • allowed-tools — scope the worker down to only what it needs. A read-only search worker gets Grep, Read and nothing that can modify files. This is your safety boundary, enforced per-skill.
  • The output contract — the worker writes its full findings to a file and returns only a summary. This is the single most important convention in the whole system. Get it right and everything else falls into place.

The summary-vs-report split

Say it plainly, because it's the load-bearing idea:

A sub-agent writes its full report to a file, and returns only a short summary to the orchestrator.

The orchestrator reads the summary immediately. If it needs the details — the exact line, the full list — it reads the file on demand. Most of the time it doesn't need to. So the expensive raw data lives on disk, referenced by path, instead of clogging the reasoning layer's context.

How the orchestrator picks workers (without reading them all)

Here's a subtlety that trips people up. If the orchestrator has twenty worker skills available, you might think it needs to read all twenty SKILL.md files to know what they do. That would blow its context before the work even starts.
It doesn't. The agent already sees every skill's name and one-line description — that's the progressive-disclosure model skills are built on. So the orchestrator picks workers by description alone. Make this an explicit rule in the orchestrator:

Do NOT read the full worker SKILL.md files — they are large and will pollute your context. The skill name and description are enough to pick.
This is why that description field is so important. It's not documentation for humans; it's the interface the orchestrator selects against.

The orchestration loop

Put it together and the orchestrator runs a loop that should feel familiar if you've seen the basic agent loop — just one level up. Underneath any specific task it's the same four beats:

PLAN (once)
  Confirm the request, scope it, restate the goal.
repeat:
  DISPATCH    → spawn worker sub-agents in parallel to do the next chunk
  INTEGRATE   → read each summary as it returns, update your working picture
  DECIDE      → goal met? finish. gaps remain? dispatch more.
DELIVER
  Present the result (with alternatives called out if you're unsure) and stop.
Enter fullscreen mode Exit fullscreen mode

That's the general shape. Each kind of task just fills in the blanks differently:

Task type DISPATCH gathers… The "working picture" is… DELIVER produces…
Debugging evidence about a failure competing hypotheses root cause + fix
Research facts from sources an outline of the answer a synthesized report
Migration which files need changing a checklist of edits a completed, verified change
Audit findings per rule/area a running list of issues a prioritized report

The vocabulary changes; the loop doesn't. Whenever a task is "break it into independent chunks, do them, combine the results, repeat until done," this is the pattern.
A few things make it work well in practice:
Fan out, then integrate as results arrive. The orchestrator spawns workers as background sub-agents and doesn't block waiting for all of them. As each summary comes back, it folds it into the picture and — crucially — can dispatch a follow-up immediately. A usage search turns up a suspicious call site? Fire off a git-history worker on that file now, while the other workers are still running. The loop is interleaved, not batch-then-wait.
Cap the expensive workers. Cheap, read-only workers (a quick search, a file read) have no concurrency limit — the cost of running one you didn't need is near zero. Heavier workers (running the full test suite, deep analysis) get a hard cap, e.g. "at most 2 at a time." Match the limit to the cost.
Refresh the instructions each phase. On a long run, the orchestrator's own SKILL.md scrolls out of its context. So each phase's detailed rules live in a separate file (phase-collect.md, phase-synthesize.md…) that the orchestrator re-reads every time it enters that phase. Don't rely on the agent remembering a rule it read forty steps ago — put the rule back in front of it when it's about to act on it.
Keep an append-only log. One file is the run's source of truth: every dispatch, every result, every decision. Append-only — if an earlier note was wrong, you add a correction rather than editing history. It's the audit trail and the memory that survives context churn.

Why this beats one big prompt

  • Context stays clean. Raw data is quarantined in worker contexts. The reasoning layer only ever sees distilled conclusions, so it can go deep and long without degrading.
  • Real parallelism. Ten independent checks run at once instead of one after another. A job that would take an agent twenty sequential steps finishes in a handful of rounds.
  • Specialization. Each worker is small, testable, and good at exactly one thing. You can improve the usage-finder without touching anything else.
  • Safety per worker. allowed-tools means your read-only workers cannot modify files, no matter what the model decides. The boundary is structural, not a polite request in a prompt.
  • Composability. Adding a capability means adding a skill folder. The orchestrator picks it up by description — no changes to the loop.

Setting up your own

The pattern transfers to any complex task — debugging, research, migrations, audits. A starting recipe:

  1. Write the workers first. For each concrete sub-task, make a skill folder with a SKILL.md. Nail the description (for selection), set context: fork, scope allowed-tools, and define the input params + output-file contract.
  2. Enforce summary-vs-report everywhere. Every worker writes full output to a file, returns a short summary. Non-negotiable — it's what keeps the orchestrator's context alive.
  3. Write the orchestrator as pure reasoning. Tell it, in the strongest terms, to delegate all the actual work. Give it a loop (gather → interpret → decide) and the rule to pick workers by description, not by reading them.
  4. Externalize per-phase rules. Put detailed checklists in phase files the orchestrator re-reads on entry, so they stay in its attention on long runs.
  5. Cap the expensive workers and log everything. Concurrency limits by cost; an append-only log as the source of truth. That's the architecture. One agent that thinks, many that fetch — each in its own context, each doing one job well. Once you stop cramming everything into a single prompt and start treating skills as sub-agents, the ceiling on what an agent can reliably handle goes way, way up.

Top comments (0)