12. Subagent

Send Out a Clone: Context Isolation, Final Review, and Why Understanding Must Converge

The last post's Plan Mode solved "an agent thinking things through before it acts" — but that's still one agent, planning alone. This post's Subagent is a different kind of organization entirely: farming out work that's heavy, self-contained, and prone to derailing the main thread, to a "clone" that does it in isolation.

👆 You are here
👆 You are here

🪧 The Main Agent's Context Is a Desk That Shouldn't Get Cluttered

Say I want the agent to run a round of research before writing — dig through dozens of source documents, dozens of web pages, hunting for evidence and examples.

Here's the problem: what happens once all that raw material piles into the main writing context? First, noise drowns out the actual work — the model's attention gets diluted across a mountain of detail, and the writing itself gets blurrier as a result. Second, it burns tokens — stuffing dozens of source documents in sends this round's cost through the roof. Third, and more subtle: the context gets pushed far enough to trigger compaction, and compaction has a real chance of squeezing out the exact profile content posts 2 and 4 worked so hard to protect (that old problem again).

The main agent's context needs to be guarded like a clean writing desk. Research needs to happen, but the pile of messy scratch paper that comes with it shouldn't be spread across that same desk. The ideal move: send a clone into the next room to dig through the material, and once it's done, only hand back one tidied-up page of summary. However messy that other room gets is its own business — my desk stays clean, always.

That "clone in the next room" is the Subagent.

🧭 The Core Idea: A Subagent Is a Clone Running in an Isolated Room

A subagent is an independent agent instance, running in a completely fresh, isolated context, and once it's done working, only hands back the final result (that one final message) to the main agent. Its entire value comes down to one word: isolation.

Worth drawing a clean line against Skill from post 6 here, since I once conflated the two myself:

Light versus heavy, shared versus isolated — the use cases split apart naturally. Skill fits methodology you use in place, like "teach the agent how to write this kind of piece." Subagent fits self-contained, heavy work where the process itself is all noise and only the conclusion matters — digging through source material, running a quality check.

So what does "isolation" actually buy you that's worth this much effort? The Harness Book makes a point about multi-agent design that stuck with me: what multiple clones actually solve isn't "faster" — it's "failures become locatable." An agent that stews research, writing, and checking together in one context makes it genuinely hard to debug layer by layer when something goes wrong; split into isolated clones, and research stays research, checking stays checking — whichever layer broke is obvious at a glance. So using a subagent isn't really about speed (pure serial delegation is actually slower) — the real point is containing chaos inside its own isolated room, so the whole system stays decomposable and debuggable.

Main agent & sub agent
Main agent & sub agent

🔩 Claude Code: The Hard Rules for a Main Agent Delegating to a Sub-agent

With the value of "isolation" clear, how does it actually get built? Start with how Claude Code natively designs this collaboration mechanism. Claude Code has a mature multi-agent collaboration system — the Harness Book devotes an entire chapter to it, titled "Taming Instability Through Division of Labor." The main agent uses a "delegate" tool to hand a chunk of work off to a sub-agent; the sub-agent runs it to completion in an isolated context, and the result comes back to the main agent as that tool's return value. The sixth post in the Learn Claude Code source-walkthrough series also takes the subagent mechanism apart in detail — genuinely useful for understanding this isolation model.

A few hard rules baked into this mechanism form the foundation for everything I customized on top of it later — worth stating clearly upfront:

The Harness Book keeps hammering on a point I think is genuinely right: a well-designed clone mechanism keeps sub-agent state cleanly isolated by default — the transient, messy decisions made inside a sub-agent are never allowed to flow back and contaminate the parent thread. In its own words, a sub-agent's value lies exactly in its capacity to "contain local chaos." Same idea as the "whatever mess happens in the next room is its own business" analogy above — just phrased from a systems-design angle instead.

🛠 The SDK: How to Define a Clone, and Why I Chose "Define It in Code"

Everything above is how Claude Code runs natively; but SmartWriter integrates the writing agent through the SDK, so the same isolation mechanism still needs configuring and calling by hand, on the developer's side. At the SDK level, there are two ways to define a sub-agent:

This project uses programmatic definition, because it supports building a clone dynamically, at runtime, based on the situation — say, giving it a different toolset depending on the current genre, or a different model depending on how demanding a task is. A file definition is fixed — it can't do this kind of "assemble on the spot, as needed."

Once defined, you still need to make sure the main agent actually delegates when it should. Three points here, all non-negotiable:

  1. The "delegate" tool has to be on the allowlist, or the main agent can't delegate even if it wants to (it falls into approval, or gets rejected outright, and then it just grinds through the work by itself).
  2. Each clone's description has to spell out clearly when it should be used, because the main agent's entire decision of whether to delegate runs on that one line. A vague description means the clone was built for nothing.
  3. Critical checkpoints need to be explicitly named, then backstopped by the flow itself. For anything that "absolutely must be delegated," don't rely solely on the model's own discipline — trigger it forcibly in the flow (echoing post 10's "weld critical steps down with a hook").

One more small config setting, and it's an easy one to trip on: as of SDK v2.1.198, the default for "does delegation block the main thread" changed from blocking to non-blocking, matching Claude Code itself — the main agent doesn't wait for the clone to finish before continuing. That default isn't wrong in itself: for a complex coding task, having the main agent delegate an independent sub-problem to a clone and go handle something else while it runs, coming back once the clone reports in, is genuinely more efficient. But writing is different — research, quality checks, fact verification all need their conclusion in hand before anything downstream can proceed. So I explicitly set every clone definition back to blocking, and layered a PreToolUse hook on top that forcibly enforces blocking as a second line of defense (explicit config alone isn't guaranteed to take effect — belt and suspenders).

👥 SmartWriter's Three Clones: Each in Its Own Lane

SmartWriter keeps three clones, each doing its own thing:

Clone What it does Tools it gets Model it runs on What it hands back
research-assistant Research: digs through a large volume of material / web pages, hunting for evidence and examples Read / WebSearch / WebFetch Sonnet, by default A free-text summary
reflection-checker Final review: full-text quality check (typos / logic / style / profile alignment / technical accuracy / structure) Read only Sonnet, by default A structured JSON report
fact-checker Fact verification: checks data and citations in the piece against live sources online Read / WebSearch / WebFetch Sonnet, by default Structured JSON

A few decisions in this table are worth unpacking.

One: the model assigned matches how demanding the work is. All three default to Claude Sonnet 5 / DeepSeek V4 Flash — research needs synthesis, verification needs judgment, final review needs to catch subtle issues, all of which require a real baseline of reasoning ability, and early testing showed both models were comfortably sufficient. And because these are programmatic definitions, if I later want to get more precise — dynamically assigning models based on task weight — that's completely doable; a file definition couldn't support that kind of on-the-fly adjustment.

Two: the final-review clone gets exactly one tool — Read. Its job is a pre-publish backstop check, not "editing." So it gets zero tools that can change a file (no Write, no Edit), and nothing that runs commands (Bash) or delegates further (Agent). Underneath this is the Harness Book's principle that "verification must be independent of implementation": the ones editing the draft and the ones checking it need to be two different "people" — read-only, never allowed to step in and edit itself, is what keeps it independent enough to function as a real second quality gate. There's a wide river between "I made a change" and "I made the right change," and a model is remarkably good at building a paper bridge across that river to fool itself — which is exactly why I lean toward an independent Subagent for anything verification-shaped.

Three: final review doesn't do live fact-checking — I split that out on purpose. Early on, for convenience, I wanted final review to also verify data and citations online while it was at it. I ended up cutting that, and built fact-checking as its own standalone /fact_check instead. Three reasons: overlapping functionality, redundant token spend, and it being outside the user's control (going online mid-review, unprompted, isn't something the user expects). Final review keeps only the lightweight, non-networked checks — the kind of thing you notice just from reading, like an obvious date inconsistency or self-contradicting numbers. A clone should do exactly one clearly defined job, not dabble a bit in everything — that's also a precondition for "failures stay locatable."

By the way, you might've noticed profile analysis isn't in this table. It looks like it should be a fourth clone, but I never registered it as an SDK subagent — it runs on a business-layer concurrent query() instead (a fixed flow at first launch, or user-triggered; it doesn't need context isolation, and calling query directly for a structured result is enough). Not every piece of "delegated work" needs to become an SDK subagent — only pay for that overhead when context isolation is genuinely needed.

🎯 Research Can Be Distributed. Understanding Must Converge.

Three clones set up — but what actually deepened my understanding of "division of labor" was one best-practice line from the Harness Book: research can be distributed, but understanding must converge.

You can hand "investigation, research" style work off to multiple clones running in parallel — that's fine. But taking the pile of findings those clones bring back and digesting it into one coherent, actionable next step — that act of "understanding" must converge back into the hands of one single agent.

The first time I read that line, honestly, I didn't think much of it — not until I actually hit the gotcha it describes that I understood why it mattered.

🔧 Gotcha · I mistakenly delegated the "understanding" step to the user

Early on, the research and fact-checking pipeline worked like this: the clone finishes, produces a report, the frontend displays that report as-is to the user, and that's it — nothing after.

But then what does the user do with that report? They have to read the whole thing themselves, work out what each point means, and then manually type out a prompt: "please revise my piece based on points 2 and 5 above..." I had effectively handed off the single most mentally demanding step — "digest a report into concrete edits to the draft" — to the user.

This is exactly what "understanding must converge" warns against. The user got forced into becoming the coordinator, doing the digestion work my main agent should have been doing itself — tiring, and a genuinely high barrier. A normal writer isn't going to want to work out how to translate a report into an effective revision instruction.

The fix was pulling that step back where it belonged: once the main agent gets the sub-agent's report, it converts that itself into an executable "revision prompt" (with each issue's exact location, description, suggestion, and evidence, plus explicit "what to change, and how" instructions), and the frontend gives the user one "Apply" button — click it, and the edit lands on the draft. Research's noise stays contained in the clone; understanding's convergence lands with the main agent; and the user is left with exactly one action: glance at it, hit Apply.

Get that "convergence" right, and delegation genuinely saves effort. Skip it, and all you've done is shift complexity from the model onto the user — treating the symptom, not the cause.

🛡 Getting an LLM to Reliably Output JSON: Three Layers of Defense, and One Upstream Bug You Can't Shake

Back to "conclusions meant to be consumed by code": as mentioned above, final review, fact verification, and profile analysis all hand back "structured JSON." But LLMs have a well-earned reputation for being loose and unpredictable — how do you get a subagent to reliably, every single time, produce correctly formatted JSON?

The SDK does come with a built-in mechanism for this: on a structured query, you can attach a JSON schema, and the SDK will automatically re-prompt and ask the model to try again whenever the returned JSON doesn't validate against that schema, until it gets it right. That's a real safety net, out of the box. The problem is that subagents use AgentDefinition, which currently doesn't support attaching a schema directly — so it can only be nudged toward JSON through the prompt text, and in practice, that's noticeably unreliable.

So I layered three application-layer defenses on top of the SDK, to handle the backstop myself:

Subagent JSON output validation
Subagent JSON output validation

📐 Going deeper · the CLI's "wrapping" bug, and how a whitelist approach finally beat it

The CLI underneath has a publicly known bug (there's a community issue tracking it): it occasionally misbehaves and takes the entire structured output the model was supposed to return cleanly, and serializes it into a string, stuffed inside a single placeholder key. Worse, the name of that placeholder key isn't fixed — I've seen $PARAMETER_NAME, __unparsedToolInput, output, description... every kind of strange variant. In practice, the trigger rate wasn't trivial — roughly one in three on one CLI version — and switching to a newer version actually made it worse. Since this is an upstream bug, there's no telling when (or whether) it gets fixed.

My first response was maintaining a blacklist of "known bad key names" to match against. Turned out fast that this was a game of whack-a-mole: the upstream key names are all over the place, and new ones keep showing up — a blacklist can never be complete.

Eventually I flipped the logic around: instead of guessing what a "bad key" looks like, only trust what a "good key" looks like. I know exactly which fields my schema is supposed to have. So the rule became: if what comes back is "a dict with exactly one key, and that key isn't in my schema's field list at all," it's almost certainly been wrapped — unwrap it, and pull out the real content inside. With that, no matter how bizarre a key name the upstream throws at me, it gets caught.

I think this approach generalizes well: facing an upstream bug you don't control, instead of passively trying to enumerate every way it might break (an unwinnable game), stand on the thing you're actually certain about (your own schema), and use it to reason backward about the thing you're not certain about (whatever garbled key shows up) — taking control back for yourself.

🧯 Clones Fail Too: Take Every Kind of Failure Seriously

One more point that's easy to overlook, but genuinely matters for a real product: clones fail. They can time out, come back empty, or produce something that just refuses to parse no matter what.

These failures can't be allowed to get silently filtered away. My approach was to lay out every failure mode a clone can hit as a proper matrix (timeout, agent error, no output, empty output, parse failure...), and give each one a friendly, user-facing error state. Final review, research, and fact verification are all repeated, iterative actions too (revise, review again, revise again), so I store every single run's result as its own file, numbered in sequence, instead of overwriting the last one — so every past final-review report stays traceable. This sounds like a small detail, but "when a clone chokes, does the user get an actual human explanation" is exactly the line between a demo and a real product.

⚖️ Where This Gets Vertical: Delegation Isn't a Flex — It's "Isolate Noise, Converge Understanding"

To close out, back to the comparison running through this whole series:

⚖️ The tradeoff · using clones, generic vs. vertical

The "lazy" default SmartWriter Why
Heavy work like research The main agent digs through it in its own main context Delegated to an isolated clone, only one page of summary comes back Protects the main writing desk from getting buried in noise
A clone's report Shown to the user as-is The main agent digests it into a one-click "Apply" edit Understanding must converge — never outsource it to the user
Structured conclusions Trust the LLM to get it right Three-layer defense + schema-based whitelist recovery LLMs get loose, and there's a real upstream bug on top of that
Verification / final review Whoever wrote it checks it themselves An independent clone, read-only, never steps in to edit Verification has to be independent of implementation to actually be trustworthy

That closes out "Orchestration & Delegation" as a whole. Looking back at these two posts, they're really fighting the same underlying problem — a single agent flailing under a complex task — from two different angles: Plan Mode is orchestration in time, thinking through the order of things before acting; Subagent is division of labor in space, isolating self-contained, noisy work elsewhere and only converging the clean conclusion back. One governs "before and after," the other governs "inside and outside" — together, they're what lets a single agent take on a genuinely complex, long-form piece of writing calmly, instead of flailing from start to finish.

With this post, all 12 mechanisms — from engine, to memory, to steering, to interaction, all the way through orchestration and delegation — are fully taken apart. Every single component of this machine has now been examined closely. But here's the somewhat brutal truth: none of this carefully worked-out detail is something the user ever perceives. They open the app, and all they see is an interface, an input box, a draft. How do you take this precise, complex machine and package it into a product that someone who doesn't even know what an "agent" is can just pick up and use?

That's exactly what the last section, "Vertical Business," answers. Starting next post, we get into product flow — how these mechanisms actually get strung together into a real writing workflow. Let's keep going.