Home / Articles

Subagents in Practice: Roles, Budgets, Bans and Silent Failures

Claude Code subagents that last: roles, handoff files, run state, budgets, bans and silent-failure checks, plus how skills vs subagents differ in practice.

Publisher: REACTAPP.TOP35 min read8048 words

This article is published in English.

Sources: The Subagents Guide I Wish I’d Had

Skills fix one kind of forgetting: the agent stops losing track of what your codebase is like. A second kind remains, and it is about identity. Every new session, the model walks in as the same eager generalist, happy to hold a reasonable and unremarkable opinion on whatever you put in front of it.

Knowledge is the skill problem. Role is the subagent problem, and that is what this piece covers.

The short version, before the length of it puts you off: a skill tells the model what your world looks like, while a subagent tells it which part to play. Having both is better than either alone, and for a solo developer juggling several half-finished projects at once the pairing matters even more. You are the only specialist on the team, and you cannot occupy five seats at the same time.

The first half below is the conceptual groundwork: what these files are, where they sit on disk, how to write one. That much could be assembled from documentation. The second half comes only from running two subagents for months across a dozen repositories and watching them fail in ways no best-practices post warned about. Both agents are open source, and the real files are worth reading, because a prompt that has been hammered into shape by actual runs teaches more than any advice does.

The gap a subagent fills

A coding agent ships with one default personality, the helpful generalist. Ask for a pull request review and you get feedback that is reasonable in a generic way. Ask why an app feels slow and it starts prodding at whichever file you touched last. Ask for a database migration plan and the result looks sensible while skipping three things that would hurt in production.

None of that is the model being dim. What it lacks is a role. Someone doing a security pass thinks about different things than a security engineer does, and someone chasing a bug examines different evidence than a person who has been on call for that exact failure three times. The intelligence is already present. The framing is what's missing.

The giveaway is typing the same preamble over and over. Before letting Claude Code look at a change, you might paste in something like: review this like a paranoid security person, check auth boundaries first, tell me what leaks into logs, skip the style nitpicks. The third time that happens within a fortnight, the realization lands. That preamble is a role, and a role you keep retyping is a subagent nobody has written yet.

Repeated typing is the trigger every guide mentions. It is not what produced the two agents that lasted, which came from two other pressures.

  • Context economics. Research is messy: twenty searches, a dozen fetched pages, half of them useless. If that happens in your main session, it fills with junk you never wanted to read. The work needs to happen somewhere else and come back as a summary. That is the whole reason the web-search agent exists. It is not cleverer than the main thread, and the point is simply to keep what it read out of your context.
  • Role separation. During design work you want to stay inside the design conversation. Being pulled out to approve the eleventh mechanical edit, one that follows plainly from a decision already made, is a waste. So decisions stay with the human and the typing goes to an implementer agent. Capability has nothing to do with the split. What it settles is who should be thinking about what.

A reviewer is a pleasant first subagent. These two are different: they are what turn the feature from a convenience into the way a project is actually run.

Skills versus subagents: knowing against behaving

A skill packages things the agent has to know: your odd internal library, the environment quirk that breaks builds, the domain facts a smart new hire must be told because nobody could guess them. It loads on condition, when relevant.

A subagent packages how the agent should act: what it optimizes for, what it inspects first, which trade-offs it picks by default, and what form the answer takes. It teaches the model nothing new. It tilts the intelligence already inside toward one job.

A handy test is to picture calling in a specialist colleague. If what they'd bring is mostly information you lack (domain knowledge, context, tribal wisdom), you want a skill. If what they'd bring is mostly a way of approaching the problem, meaning what they look at first, what they weigh heavily, what they refuse to approve without checking and what they hand back, you want a subagent.

Take a security engineer reading your code. They don't just know more than you. They work differently: auth boundaries first, a privilege-escalation path counted far above an ugly variable name, no sign-off without a comment on secrets, and findings delivered in a structured list. That ordering of attention is what a subagent encodes.

Knowledge belongs in a skill. Methodology belongs in a subagent.

What a subagent is in Claude Code

Claude Code is the reference tool here because it is the daily driver. Other tools get their own section further down, quirks included.

Inside Claude Code, a subagent turns out to be nothing more exotic than a Markdown document that starts with a short block of YAML. Two directories can hold it:

  • ~/.claude/agents/ is the personal library, available in every project on the machine. Treat it as a sandbox.
  • .claude/agents/ inside a repository is scoped to that project and travels with the repo once committed.

The frontmatter is short. Here is a complete working example:

---
name: migration-risk-reviewer
description: Reviews database migration plans and schema changes for rollback risk, lock contention, and data integrity problems. Use before running any migration against real data.
tools: Read, Grep, Glob
model: sonnet
---

You are a senior database engineer reviewing a migration for production risk.

## What you look at first

- Can this be rolled back without losing data?
- Does it hold locks on a busy table during deploy?
- Is the ordering safe for a multi-step change?
- Will existing data violate any new constraint?

## Output shape

Findings in order of severity:

1. Blockers — deploy will fail or data will be lost
2. High risk — real production risk, needs a mitigation
3. Medium risk — should fix, won't necessarily block
4. Notes — worth tracking

For each finding: what it is, why it matters, what to do about it.

Several things about how Claude Code handles a file like this are useful to know.

  • The body is the system prompt. Everything under the frontmatter becomes the subagent's instructions when it runs. It is not documentation the model reads and then acts on. The model reads it and takes it on as its identity.
  • The description carries a lot of weight. Claude Code consults it to decide when to hand work over automatically, so write it like an API that someone else must discover from context. "Use before running any migration" can be found. "helps with db stuff" can't. Include the negative half as well: both of the agents discussed here spend a clause on what they are not for, since routing will cheerfully pass an agent work it has no business doing.
  • tools is an allowlist. Omit it and the subagent inherits everything. Narrow it and you get a reviewer physically unable to edit your files even if it gets ideas. For a review agent that is a feature, because a "just tell me what's wrong" pass should not rewrite things on a whim.
  • model is a budget choice rather than a detail. A cheap model suits mechanical work and an expensive one suits judgment. A later section covers this at length, because it explains most of why delegation pays for itself.
  • It has its own context window. People undervalue this: the subagent works in a separate context and returns a summary, so your main session isn't flooded with everything it read. The same fact is also where things go wrong, and a later section shows how.
  • You don't need to write the file by hand. Describe the role to Claude Code and it will create the file in the right place. The /agents command manages them too.

You can call a subagent by asking for it, by letting Claude route to it based on the description, or by wiring it into a bigger flow where it runs as a delegated worker. The behavior is the same in each case.

The word "agent" is doing too much

The term gets stuck onto four different things, and mixing them up causes constant confusion. Pulling them apart:

  • A subagent (a file in .claude/agents/, or a .agent.md file in Copilot territory) is a reusable file defining a specialist role. This is what the article is about.
  • Agent mode or an autonomous run is a capability switch. You let the tool run commands and edit files without approving each line. That is a permission setting and has nothing to do with specialists.
  • A background or cloud coding agent is a service that takes a task, works on it out of sight, and returns a branch or pull request. It is also not a specialist file.
  • A repo-level instructions file is always-on guidance, the house rules every agent obeys in that codebase. Think of it as the employee handbook, not a coworker you call by name.

Whenever "subagent" appears in what follows, it means the reusable specialist written once and invoked on purpose. Keeping those four straight clears up about half the muddle.

Writing a first one

Begin with the thinnest thing that does real work. A first subagent has two tasks: declare the role, and spell out what the role means in practice. The migration reviewer above already does that. It has a clear role, specific heuristics and a predictable output shape, and it is deliberately narrow. More gets added after you run it on real work and see what it misses.

First subagents most often fail through a fuzzy role. "Security helper" isn't a role, since nothing in it tells the model what to optimize or what to inspect first. A real role works as a behavioral contract covering the job itself, the first thing examined, and the things never waved through. If the job can't be stated in one sentence, it is too vague.

A concrete version spells out what the model should care about and what it should disregard. Ignoring is half the value. A reviewer that gives a typo the same weight as a privilege boundary is just an averaging machine.

What goes into the body

With the role settled, a useful body covers five things. Day one doesn't need all five.

  1. What it optimizes for. The one thing this role is trying hardest to get right. When a trade-off appears, this is what it trades toward.
  2. What it inspects first. The top-priority signals any real specialist reads before anything else.
  3. Failure modes. The particular ways this class of work goes wrong. Real expertise lives here, and most of it only turns up by watching the agent mess up actual tasks.
  4. Output shape. Not "some feedback" but findings ordered by severity, a phased plan, or a ranked list of hypotheses. Predictable output is the biggest practical gain over plain prompting.
  5. What it must not do. This section used to count as optional, and it isn't. In the most-used agent here it is the longest part of the file, and every line in it traces back to a single incident.

Begin with the role, the first checks and the output shape, and let mistakes supply the rest.

Example: a bug hunter that ignores the latest commit

Here is one that came out of a repeated annoyance. Something breaks, the error gets pasted into Claude Code, and its first instinct is to go stare at whichever file was edited most recently. Sometimes that is right. Often the last change had nothing to do with it.

An experienced debugger doesn't begin with code. They begin with evidence, meaning what is actually failing, what the logs say and what changed in the environment, and open the source only when a hypothesis is worth testing. That is a behavior, so it got saved as an agent:

---
name: bug-investigator
description: Investigates a bug or failure starting from evidence, not from recent code changes. Produces ranked hypotheses with the next diagnostic step for each. Use for "why is this broken" before touching the source.
tools: Read, Grep, Glob, Bash
---

You are a senior engineer investigating a failure. Your job is to find the most
likely cause, rule out the alternatives, and say what to check next.

## Investigation order

1. Start from the symptom — what is actually failing, and how does it show up?
2. Get the error output and any logs from around the time it broke — ask the caller for them if they weren't provided.
3. Only then look at recent changes, and only if the evidence points there.
4. Read source last, once there's a symptom-to-cause hypothesis worth testing.

## Output shape

**Symptoms:** what's failing and how it manifests.
**Likely causes (ranked):** for each — the hypothesis, the evidence for it, the next diagnostic step.
**Ruled out:** what you checked and why it isn't the cause.
**Next steps:** in priority order.
**Open questions:** what you still can't confirm.

## Failure modes to avoid

- Don't assume the last change is the cause without evidence.
- Don't propose a fix before you can name the mechanism.

Then comes the step people skip: test it on a real bug that has already been solved, not a toy. Hand it the symptom and the logs you had back then, and watch whether its investigation path matches the one that actually found the problem. Wherever it drifts, the drift becomes a new line in the body.

That file is where most subagent write-ups end. Everything from here on describes what happens after months of use, which was the part that needed telling.

The reverse of what was expected: a subagent starts cold

Everyone pitches the isolated context window as a pure win. It cuts both ways.

A sentence went into the implementer agent after one handoff broke in three separate ways. It describes the agent as knowing the project well while knowing nothing of the planning talk that preceded the task.

Cold on the conversation means it never saw the two hours in which you ruled out the obvious approach, talked yourself out of an abstraction, and settled on a strange-looking solution for a good reason. For the subagent none of that happened. Warm on the project means it has CLAUDE.md and the codebase, so it already knows the house rules and idioms without being told.

Misjudge either half and a predictable mess results. Assume the agent shares your conversation, and it will happily rebuild the design you threw out an hour ago, because from where it stands that design looks perfectly fine. Assume it knows nothing of the project, and you spend a thousand tokens narrating a repo it could have read quicker than you can summarize it.

The conclusion no subagent guide offered: the handoff is a file, and that file is the contract. It is not a paragraph typed into the task prompt. It is a document on disk with fixed sections, because an improvised prompt quietly omits a different critical section each time.

In this setup the handoff is a TASKS.md at the repo root, produced by a planning skill. The active task has these sections and none others:

  • Goal. What is true once the work is finished.
  • Why (pointer). A link to the decision recorded in PLAN.md, not a fresh argument for it. A pointer, not prose.
  • Run state. The agent keeps this current. More on it just below, since it is the section that saves you.
  • Design as numbered pieces. A serial queue where [ ] means not started, [x] done and [!] blocked, with dependencies noted inline, such as depends on #2.
  • Files. Where the work happens.
  • Tests. The checks standing in for review.
  • Out of scope. The single most valuable section in the whole document.
  • Report back. What the final message must contain.

What makes it work is a rule: fill every section before launching, and put a dash in one only if it truly doesn't apply. An unfilled section is where the agent improvises.

Then there's the split that took longest to notice: two documents with different lifespans. Standing knowledge about how work is done here belongs in the agent definition, not repeated per task. The agent file carries the protocol, meaning how to read a task, deal with a blocker, which things are never touched and what a report contains. The task file carries the current job. Pasting one instruction into three tasks in a row is a sign it was never task content, and it should move into the agent.

Run state, because sessions die

One rule has rescued more real work than any clever prompt engineering here. Whenever the agent stops, whether finished or blocked, it must record where things stand: tick each piece's status box as it goes, and update a run-state note covering what is done, what is blocked and why, what remains and where to resume. Editing TASKS.md for that purpose is explicitly in scope, because the file is the recovery point if the session dies.

Sessions do die. The laptop closes, the connection drops, a usage limit hits, or you get bored and press Ctrl-C on something nearly done. When that happens to a subagent, everything it knew vanishes. If the only record of progress sat in its head, you now compare your repo against your memory to guess how far it got.

Hence the agent records progress on disk as it goes, inside the very file that assigned the work. A later session opens the file and continues from the marked spot. It adds a few lines to the agent definition and converts a dead session from lost work into a paused one.

A companion rule concerns throughput instead of recovery. A blocked piece does not stop the queue. When a piece is underspecified or runs into an unresolvable dependency, the agent marks it [!] with a one-line reason and carries on with any remaining piece that doesn't depend on it, halting only when nothing else can proceed. It never guesses a design, and any blocked fork goes into the report for a human to settle.

Default behavior on a snag is to stop and ask, so a queue of eight tasks yields one task and a question. Default behavior with no guardrails is worse: the agent guesses a design and keeps going, leaving seven pieces built on an invention nobody approved. Skip and continue, never guess, report the fork. That combination is what lets you queue work and walk away.

Who pays for which kind of thinking

Here the model field earns its keep, and the economics of the setup hinge on it.

The implementer's description, taken from a real project, says roughly this: a Sonnet worker that carries out a fully specified, mechanical coding task from the task file while the main Opus planning thread carries on; not for design decisions, open questions or live quality testing, all of which stay in the main thread.

The expensive model makes decisions and the cheap model does the typing. A narrow scope is exactly what makes the cheap model dependable: a Sonnet worker handed a fully specified numbered queue inside a codebase it can read behaves reliably.

The split also gives two working rhythms from the same machinery.

  • Paced. Line up two or three pieces, send the agent off, and plan the next batch while it works. Neither side sits idle waiting for the other.
  • Unattended. Queue plenty, then walk away, and return to a diff, a report and a run-state note saying which piece blocked and why.

Underneath all this sits the permission layer that actually enforces things. The unattended implementer has its file and shell commands allowlisted in project settings, and anything beyond that list stops for approval. What the prompt says is policy; the allowlist is the fence.

One more line in that agent exists because of the costliest mistake: work the numbered pieces as a serial queue, top to bottom, in a single pass, with no parallel workers and no stopping to report after each piece.

Parallel agents sound like free speed and are nothing of the sort. Each one ends by dumping its full output into the context that spawned it, and a clever-looking fan-out can bury the session that must read all of it. Real work has been lost that way. Running things in parallel over separate files is something to opt into for one particular burst, never a default. The default is one worker going through one queue in order.

Without a budget it spends everything

Any subagent with tools such as search, fetch or a shell will keep grinding until something halts it, and "that's enough" is not a conclusion a model reliably reaches by itself. A research agent with no budget burns an afternoon on a heap of hidden browser tabs and returns a summary the first three results could have supplied.

So the web-search agent opens with hard limits, placed above its methodology and above everything else. The per-source playbooks it loads at runtime, which hold source lists and query tactics for each domain, get their own section further down.

Four lessons from that table of limits matter more than whatever numbers it holds, which you should adjust to suit your own work.

Offer a depth dial instead of one fixed cap. Any single setting is wrong most of the time: too tight for a hard question, too generous for a quick fact. With three named levels and a declared default, asking for deep becomes a conscious choice instead of an accident. The order of precedence is spelled out too: figures given by the caller win, followed by a level named in the prompt, then project config, then standard.

A budget invites gaming, since a model treats it as a target, so limits need clauses against that. The clauses that matter say one fetch per URL, never re-read a page already read, stop as soon as the caller's questions are answered, and treat leftover budget as something other than a quota. A deep run that finishes in six searches counts as a success and not a waste.

That last sentence went in after a run answered the question at search four and kept going anyway, because twenty was the number it had been handed. You have to say out loud that finishing early is winning.

Count the things that cost something. The fetch budget includes every network retrieval, from native fetches to each escalation rung for blocked pages to each HTTP request inside a helper script, with every 429 retry in that helper counted on its own. An early version counted only "fetch calls" and the agent slipped through the gap unintentionally. That wasn't cheating; it followed a badly written rule faithfully.

Make it announce spending. After each phase the agent reports a line such as [deep: 3/20 searches, 5/30 fetches]. Without it, you can't tell a careful run from a runaway until it's over. If the budget runs out with questions unanswered, the agent stops and states exactly what remains unknown, which URL would most likely answer it, and that rerunning deeper is an option. The one forbidden move is to keep going past the limit without saying so, since the caller picked the level and overspending overrides that pick.

Bans make up most of a mature agent

Open the most heavily used agent file and the thing that jumps out is the ratio. The description of the role takes one paragraph while the prohibitions fill a whole list. That lopsidedness isn't padding. An agent with real tools has many more ways to comply on a technicality than anyone can predict.

One example shows why the parenthetical is the entire point. The agent is told never to use browser automation: no simple browser, no embedded preview, no Playwright, no Chrome extension tools, no opening tabs or windows, and if a browser tool is offered it isn't meant for this task. The reason is stated right there: opening tabs to read pages once produced around a hundred of them and wrecked a run.

Another comes from the implementer in the research-agent repo, where the mechanism is the whole reason anyone would respect the rule. It must never create files under agents/ or skills/, because agents/ is shipped payload and the package manager flattens every .md below it into a separate top-level agent at install time. A stray file there would land in every consumer project.

That is the principle to extract: every ban names its mechanism. The agent doesn't need persuading. The reason has to be written down because a constraint without one gets edited away. Six weeks on, while tightening prose, you'll hit a line saying "never create files here", it will look arbitrary, and you'll helpfully generalize it. The clause about the installer flattening things into every consumer project is what makes your future self leave it alone.

A few more shapes these constraints take after enough time at it:

Ban the category and list the instances. A bare "no browser automation" leaves the agent deciding if a preview pane qualifies. Listing six particular tools it may not touch shuts the door it would otherwise argue its way through.

Name the behavior, not just the tool. The best line in the file prohibits a tendency rather than a command: if you catch yourself writing a scraper or a report generator, stop, because you are working around the task and not doing it. Capable agents route around constraints by building tools. That is the category, and it is what has to be banned.

Give an escape hatch conditions, or it turns into the highway. A few pages truly can't be fetched, which is why a ladder of fallbacks exists for blocked URLs. It only opens once the ordinary route has failed on that very URL. It never earns an extra fetch slot, it checks for an installed tool without installing one, and it caps output so a page dump can't swamp the context. After that comes the hard stop: if the URL stays out of reach after every rung available, it's done. Log it and move along, with no further tries, no extra helper, no inventive fourth idea.

Spell out why the exception isn't a loophole. A short paragraph in the file explains that a one-shot headless fetch printing plain text doesn't breach the browser ban, because that ban targets driving a browser and leaving tabs behind for a person to close. Leave it out and the exception and the ban seem to collide, and a model facing a collision chooses the reading that permits more.

They fail silently, and with confidence

Two research runs once came back looking not merely fine but good: clean findings, real sources, a coherent report. Both named Reddit as the primary source. Neither had retrieved a single thing from Reddit.

What happened was that a WebSearch query restricted with site:reddit.com returned ten clean, plausible results from other domains, among them a craft-marketplace community forum, a government small-business site and a slide-sharing site, with no error whatsoever. No 403, no empty set, no warning. The domain filter had been silently ignored and nothing in the output said so. The agent had a tidy list of usable pages from the wrong places, and it did what any sensible worker does with such a list. It used them and reported success.

The rule written down that same day is now the base of the whole project: if a named source can't be reached, the run has to say so, and it must never go on quietly with whatever the tool gave back instead. A report naming Reddit as its main source while claiming success from marketplace forums is worse than an outright failure, since nobody reading it can tell.

A failed run costs a rerun. A run that quietly swaps its sources costs a decision built on evidence you assume came from somewhere it didn't. The isolated context window, the very feature, is why you can't see it happening: no transcript reaches you, only whatever summary the agent decided to write.

Three remedies fix this, and none of them amounts to telling the agent to be careful.

Hand it a mechanical test. No error was raised, so the agent saw no cause to inspect anything. The remedy is one sentence that needs no judgment: when a search was restricted to a domain and none of the URLs that came back belong to it, treat that as zero results rather than a result set. It costs no extra tool, fetch or permission, because the URLs are already there. "Be suspicious of your sources" gives the agent nothing to do. "Check the hostname against the one you filtered on" does.

Treat provenance as a required output channel with a schema. This is where output shape stops being a set of section headings and starts behaving like a type. Two arrays are pinned down to their key names.

  • unreachable[] holds one entry per wall, with keys exactly source, url and reason. It covers both hard fetch failure and silent substitution.
  • sources[] holds one entry per source that actually supported an answer, with keys exactly source, url and fields. A page that was opened but informed nothing isn't recorded. A source that answered four fields is one entry listing four names, not four entries.

The subtle call, which was wrong at first: provenance annotates and never blocks. If a documented substitute answered the question, the field counts as answered, and the unreachable entry merely records that the answer didn't come from the named source. Letting a wall fail the field would make the pipeline break loudly in exactly the situation where a workaround exists.

One failure carries a lesson worth borrowing. The first draft of this agent told it to "record it as unreachable" in four separate places, yet never named a field, an array or an output slot. The instruction led nowhere. The agent had to record something with no place to put it, so it recorded nothing, and nothing complained. Whenever you write an instruction for a subagent, confirm the action it describes has a destination. "Report X" with no named slot for X does nothing and feels fine about it.

Verify from outside the agent. Whether the agent honored its output contract is decided by a script run afterward, not by the agent's assurance. About 180 lines of Python check the JSON against the declared fields, and that is worth more than any amount of emphasis inside the prompt.

The same idea reappears in the implementer for a repo with no test suite to lean on. Its instruction says there is no suite; the Tests section of the task lists the checks that stand in for one, each must be run, and results reported concretely. Where a check reads "this URL resolves", that means actually fetching it. A URL that couldn't be verified doesn't go into a file. It gets reported as unverified, along with what happened, since a reader who trusts a link nobody checked will waste a fetch on a 404 that a plain search would have avoided, which makes an unchecked URL a worse choice than leaving it out.

Agents readily claim verification they never performed, because a believable claim and a verified one read the same in a summary. For each check that matters, name the concrete physical action that counts as having done it.

One job per agent, and the habits that bloat the file

The best subagents have a single job. You'll object here, since the files quoted above aren't thin, and the research agent runs to 188 lines. The distinction that settles it: the job stayed one sentence while the file grew. What piled up was constraints, detection rules and output contract, scar tissue around a fixed skeleton, and not one new responsibility in a year. Lines added because something broke mean the file is doing its job. Lines added to cover a new kind of work are bloat, and the cure there is to split the agent, not to bolt on another section.

The bloated versions all look alike, and every one has shipped at some point. There is the everything-reviewer, which averages across five concerns and excels at none. There is the "senior engineer" label with no behavior behind it. There is the agent that restates your CLAUDE.md, adding token cost rather than behavior, and the one that duplicates a skill until the two drift apart. All share a root mistake: a job that got wider than a sentence.

The obvious next question is what, after a year of this, actually remains on the bench.

The two that survived

Most posts on the topic hand you an org chart of agent types to build. The real bench after a year is two agents, and that's all.

One disclosure, since the article's own rule applies to it: the web-search agent began as a fork of an open-source deep-research skills project. The output-contract skeleton, meaning the summary sections and the always-required sources list, comes from upstream substantially verbatim. The control plane layered on top, covering budgets, bans, the provenance schema and the escalation ladder, is original and amounts to half the line count. Everything about scars in this piece concerns that half.

The web-search agent carries out limited web research and returns findings together with their sources. It is installed across a dozen repos today, among them a static blog site, a couple of pipelines and a few research projects. All the material here on budgets, bans and silent substitution came out of the revision history of that one file.

The implementer executes a fully specified task queue while planning continues elsewhere. Four projects have one. Those four supplied the lessons on handoff contracts, run state and the economics of delegation.

The rest of what sits in the agents folders arrived bundled with frameworks, and it works as a junk drawer with better branding than a real one. The specialists that were predicted to be necessary (a flaky-test investigator, a PR-polish agent, a docs drafter, a codebase-orientation agent for projects since abandoned) all sounded reasonable, and roughly none was built. The ones that stick turn out not to be the ones that sound useful. They are the ones where the same specific pain of work happening in the wrong context, or the wrong head, kept recurring.

Skip the org chart, then. Pay attention to the work you wish ran somewhere else, and save that one.

Template first, then specialize

The pattern that made the second, third and fourth implementers cheap is this: the agent is generated from a template and then diverges wherever the project differs.

The planning skill ships an implementer template with placeholders, and scaffolding a new project writes the agent, the plan document and the task document together. What you get is the protocol: read the contract, take the pieces in order, skip anything blocked, keep the run state current, stay within scope and report at the end. Those rules never change between projects, since they describe delegation itself and say nothing about your code.

After that, every copy develops its own section for its own failure surface, and putting two side by side is instructive. The template tells the agent to keep the test suite green. One project has no suite whatsoever. It is a repository of prompts, and its only executable file is the validator. Its implementer therefore says something else entirely: the repo holds prompts and data rather than code; editing a file is editing a prompt; wording, order and emphasis are the implementation; and a rewrite that reads better while dropping a hard constraint is a regression that nothing will flag, since neither build nor tests exist.

Another project's copy gained an instruction to study whichever PLAN.md decisions a task points to before coding, along with a report-back line for anything requiring an Opus or human call. The protocol is the same and the hazards differ.

That's the split to remember. A subagent's reusable portion is its protocol, and its project-specific portion is its failure surface. Template the former, write the latter by hand, and don't try to make one file cover both.

Point the agent at knowledge and don't paste it in

This is where skills and subagents shake hands, and the idea got much more specific once a real pipeline depended on it.

My earlier advice would have been to name the relevant skill inside the agent body. What works better is stricter: the agent reads the knowledge at runtime before it may act, and the prompt explains the reason.

The research agent can't run a single search until it has read a routing table kept inside a skill. The instruction is labeled mandatory and says: before any search or fetch, read the table at the first path below that exists; do not skip this; do not route from recollection, since the modules shift while the prompt stays the same: one you recall might be gone, and one you need might not have existed yet.

That remark about the list changing while the prompt stays put is the full case for the pattern. The knowledge (which sources answer which sorts of question, how to query each, what is known to be blocked) shifts monthly, while the method stays fixed. Bake a source list into the agent body and each new source forces an edit to a prompt that would otherwise rest, and the agent will route with confidence to a module deleted back in March.

Two mechanics mattered more than expected.

Candidates in order, rather than one path. The agent runs in projects laid out differently, across two machines under different usernames, set up by different installers. The instruction therefore lists candidate locations in sequence and says to take the first that exists, project-local copies before user-level ones. Hardcode a single path and everything works on one machine only.

Literal names tie the layers together, and nothing verifies them. Three tiers make up the design: skills orchestrate, an agent does the work, and data modules store the knowledge. No imports exist anywhere. A skill kicks off the agent under its precise registered name, while the agent finds modules by path. That gives the repo its most ordinary and most dangerous sentence, a reminder not to rename the web-search agent since existing consumers call it directly.

An agent's name works as a public API, and no tooling says so: no compiler, no test, no warning. A rename looks like a tidy commit and breaks every caller in every repo that installed the agent, and the discovery comes the next time you request research and a generalist answers.

Where the files live: personal, project, packaged

There are three rungs, and the third is where all the real agents live.

  • Personal (~/.claude/agents/) is for general specialists wanted in every project. Treat it as the workshop, where breaking things is fine.
  • Project (.claude/agents/, committed) is for specialists that only make sense in one codebase, like an implementer aware that this project lacks tests. Commit it and it will be waiting whenever you return, even four months later after the project has slipped your mind.
  • Packaged (installed by a package manager and pinned to a commit) makes an agent a dependency. This is the rung nobody knows they need until the same agent sits in a dozen repos and thirteen copies are drifting apart.

The third rung changes a few things, and it's better to know them before you get there.

Bundle the agent with the skills that invoke it. The package carries both, as one unit, for a plain reason: skills installed without their agents produce a pipeline that breaks on first use. A skill that dispatches by name to a missing agent is a broken install, with a tidy error message at best. They're one unit because they're one contract.

Learn what your installer does with files. This package installs through Microsoft's APM, which flattens each .md beneath an agents/ directory into its own top-level agent. That default is sensible and still stings. Park a data file in that tree and every consumer project gets a bogus registered agent. Write a project-specific agent there and it installs itself into everyone's repos. For this reason the search-strategy modules sit under skills/ and not in the agents/ folder where they'd seem to belong: under agents/ they'd lose their structure on install and register five phantom agents per consuming project.

Pin a commit, and remember consumers lag behind. Consumers depend on a SHA, so a fix isn't live for anybody until the pin moves and the install reruns. Writing a fix is not shipping a fix. That sounds obvious on the page and is anything but at 11pm, when the bug fixed last week reappears.

Still, prune. A folder stuffed with overlapping, half-forgotten agents generates noise instead of tooling. Merge two that have drifted into the same job. Delete one that hasn't run in months, because git history keeps it if the deletion proves wrong. That is decent life advice as well, and it goes ignored for the actual junk drawer at home.

The same idea in other tools

Claude Code isn't the only home for this. If you bounce between tools, the ideas port even where the file formats don't.

GitHub Copilot and VS Code call these custom agents, stored as .agent.md files, typically under .github/agents/, with an option for a personal library in the user directory. The frontmatter has more fields than Claude's: past name, description and tools you get argument-hint, model, user-invocable, disable-model-invocation and, best of all, handoffs. A finished agent can show a button that passes the work to another agent with the next prompt already filled in, so planning, implementing and reviewing chain together without merging into one muddled agent. A trap if you read older examples: the former infer field became two separate fields, user-invocable and disable-model-invocation, a change that's tidy in hindsight and confusing at first.

Another claim you'll meet is that VS Code detects .claude/agents/ files and translates tool names, so one agent serves both tools at no cost. It's true enough to mislead. Tool vocabularies don't match, and a prompt that bans tools by name, as every mature agent here does, doesn't come through automatic translation unharmed.

What ships instead is one canonical prompt plus a thin native shim for each tool. The entire Copilot-side agent in the package is eleven lines:

---
name: Web Research Writer
description: "Use for bounded web research that must read a local schema, search and fetch current sources, write one designated result file, and validate it with a local command."
tools: [read, search, web, edit, execute]
user-invocable: false
disable-model-invocation: false
---

Load the installed canonical research prompt from the first existing candidate:

1. `.github/agents/web-search-agent.agent.md`
2. `.claude/agents/web-search-agent.md`

Follow its search budgets, module routing, source standards, tool discipline, output, and validation rules. Ignore that file's incompatible `tools` frontmatter; this wrapper's Copilot-native tool categories govern this session.

That's the entire pattern. Native frontmatter lets the host register the agent properly, a ladder of paths locates the real prompt, and one blunt sentence declares which tool vocabulary takes priority. All 188 lines of accumulated behavior sit in a single file, and the second tool receives a pointer rather than a fork. Two copies of a prompt make two prompts, and the day one gets a bug fix without the other is the day they begin misrepresenting themselves as one agent.

The project's README flags one catch. Aim the loader at .github/agents/ and the canonical prompt registers as an agent in its own right, so Copilot may show duplicate names: the "Web Research Writer" shim and, beneath it, the canonical file. Choose a single home per tool, or keep the canonical file under .claude/agents/, where Copilot's auto-detection ignores it.

Everything else, Cursor and Windsurf included, is heading toward the same thing under labels like rules, modes or agents. Details move often enough that sending you to each tool's current documentation beats confidently stating something that changed last month. The core move doesn't vary: a reusable file that defines a role and gets invoked deliberately. Learn that once and what remains is finding where each tool hides its folder.

How good ones really get built

Every subagent here began too broad. There was a "reviewer" that reviewed everything, a "planner" with no opinion on what a plan should look like, and an "investigator" that approached every bug like the previous one. That isn't failure. It is the starting point.

The useful part, the one that would have helped a year ago, isn't "iterate". Everybody says iterate. It is what the iterations are made of.

Reread the agent files quoted above and notice where the words are. A paragraph for the role. A page of constraints. A schema with its key names written out. A mechanical rule for detection. A numeric budget with a clause against gaming it. A remark that paid fetches must be disclosed. A ban that explains what the installer does with strays. Nearly all of it records one specific thing that went wrong at some point.

After a disappointing run, then, the productive question isn't how to phrase the role better. Ask what exactly the agent did, which part of the file allowed it, and what sentence rules it out next time. A hundred browser tabs. Ten off-domain results with no error. An instruction that had nowhere to put its answer. A verification claimed and never done. Each of these becomes a line, and that line outlasts the session that taught it, which is the whole point of keeping it in a file.

The same test applies before keeping an agent as before keeping a skill: can its job be stated in a sentence? If not, it's too broad, and two smaller agents are standing in one trenchcoat. Judging whether it's actually good takes a different test: when it errs, can you point to the line that let it? If you can't, there's no specialist yet, only a mood with frontmatter.

Combine them and you stop prompting one very capable generalist again and again. You maintain a small bench of specialists who already understand your world and their own jobs. For one person aiming to ship more than a single person sensibly should, it's the nearest thing to hiring help that involves no hiring.

Look at the work you wish happened somewhere else. That's a subagent still waiting to be saved. And the last time a subagent let you down, that was a line still waiting to be written.