- A different example, on purpose
- The Task tool and AgentDefinition
- Context is never inherited — you pass it, explicitly
- Parallel spawning: multiple Task calls, one response
- Coordinator prompts: goals, not steps
- Fresh spawn vs. forked session
- Critical design decisions
- Consequences
Lessons Learned While Preparing for the Claude Architect Certification
A different example, on purpose
Parts 1 and 2 stayed with one example the whole time: a customer support agent, split into order-status, pricing, and returns subagents. This part switches to a different one — a small research pipeline, with a coordinator that spawns a web-search subagent, a document-analysis subagent, and a synthesis subagent.
That’s not a random change. The official objective for this part is written in exactly those terms: passing web search results and document analysis outputs into a synthesis subagent’s prompt. It’s closer to a research pipeline than to a support ticket, so the example follows the objective instead of forcing the old one to fit.
What doesn’t change is the underlying question. Part 2 answered why you’d split into a coordinator and subagents, and what design decisions that split creates. This part answers something narrower and more mechanical: once you’ve decided to spawn a subagent, what exactly do you configure, and what do you have to hand it explicitly, for that spawn to actually work?
The Task tool and AgentDefinition
A coordinator spawns a subagent through the Task tool. For that to work at all, Task has to be in the coordinator’s own list of allowed tools — without it, the coordinator can still reason about delegating, it just has no way to actually do it. Nothing crashes. The coordinator simply never spawns anyone, and from the outside that can look like a model that “doesn’t want” to delegate, when the real problem is one missing entry in its own tool list.
Each subagent it spawns is described by an AgentDefinition: a name, a description, a system prompt, and the tools it’s allowed to use. This is the same object from Part 2’s design decisions, written out as actual config instead of a design question.
@dataclass
class AgentDefinition:
name: str
description: str
system_prompt: str
tools: list[dict[str, Any]]
tool_impls: dict[str, Callable[..., str]]
max_turns: int = DEFAULT_MAX_ITERATIONS
def spawn_subagent(agent: AgentDefinition, task_prompt: str) -> str:
return run_agentic_loop(
user_message=task_prompt,
system=agent.system_prompt,
tools=agent.tools,
tool_impls=agent.tool_impls,
label=agent.name,
max_iterations=agent.max_turns,
)
The example project’s coordinator stands in for Task with three named tools instead of one generic one — consult_web_search, consult_document_analysis, consult_synthesis — so the demo stays runnable without a real SDK dependency. The idea is the same either way: one config object per subagent, one function that spawns it. You can find the full file on GitHub.
A PHP port is available too, built on Anthropic’s official PHP SDK. AgentDefinition becomes a small readonly class instead of a dataclass, but it holds the exact same four things: name, description, system prompt, and tool restrictions.
Context is never inherited — you pass it, explicitly
This is the part that actually trips people up: a subagent does not automatically see the coordinator’s conversation. It doesn’t remember being spawned last time, either. Every task_prompt you hand spawn_subagent() is the entire world that subagent knows about. If the coordinator already found two sources and read both of them, and it wants a synthesis subagent to write about them, it has to put what it found directly into that subagent’s prompt. Nothing carries over on its own.
The document-analysis subagent in the example project is written to make this easy to do right. Its system prompt asks for one fixed line of output: CLAIM: <the claim> | SOURCE: <url> | PAGE: <page number>. That’s a structured format, not free prose — it separates the actual claim from the metadata that lets someone check it later. When the coordinator calls consult_synthesis, it passes those lines through as-is:
"consult_synthesis": lambda question, findings: spawn_subagent(
SYNTHESIS_AGENT, f"Research question: {question}\n\nFindings:\n{findings}"
),
The synthesis subagent has no tools at all. Whatever arrives in that findings string is everything it has to work with — which is exactly the point. There’s nowhere else for the information to come from.
Parallel spawning: multiple Task calls, one response
Nothing about the agentic loop forces subagents to run one at a time. A single coordinator response can contain more than one tool_use block, and the loop from Part 1 already collects every one of them before calling the model again. So if the coordinator wants to read two sources, it can ask for both consult_document_analysis calls in the same turn instead of spacing them across two separate turns:
calls = [b for b in response.content if b.type == "tool_use"]
if len(calls) > 1:
print(f" [{label}] {len(calls)} subagents spawned in this turn (parallel)")
This isn’t new machinery. It’s the same mechanism Part 2 used for the coordinator delegating to more than one subagent per question. What’s different here is the reason: two independent document reads don’t depend on each other, so there’s no reason to make one wait for the other to finish before it even starts.
Coordinator prompts: goals, not steps
The example project’s coordinator system prompt doesn’t say “call web-search, then call document-analysis on each result, then call synthesis.” It says the final report has to cite at least two independent sources and flag any disagreement between them, and leaves the actual sequence up to the model.
A procedural version would look like this instead:
PROCEDURAL_COORDINATOR_SYSTEM = (
"You are a research coordinator. Step 1: call consult_web_search once. "
"Step 2: call consult_document_analysis on every URL from step 1. "
"Step 3: call consult_synthesis with the combined findings. "
"Always follow these steps in this exact order."
)
That version works fine as long as the research goes exactly as planned. The moment one step doesn’t — a source turns out to be unreadable, or one search isn’t enough to answer the question — a fixed procedure has nothing to fall back on. It was told steps, not what a good outcome looks like, so it can’t adapt when the steps stop making sense. A goal-and-criteria prompt can still decide to search again, skip a broken source, or ask for a third document, because it was told what “done” means instead of what to type next.
Fresh spawn vs. forked session
Every spawn_subagent() call in this example starts a subagent with nothing behind it — no history, no shared state, a blank slate that only knows what’s in its prompt. That’s the right default when a subagent’s job is genuinely independent, like reading one source.
Sometimes it isn’t what you want. If two subagents both need to build on the same analysis so far, and then try different approaches from that shared point, starting each one from zero means re-explaining everything they already have in common. Forking a session solves that differently: instead of a fresh subagent, you branch an existing one, so both copies start from the same baseline and only diverge from there. Task 1.3 only asks you to know this option exists as the alternative to a fresh spawn; managing it in practice — resuming, forking, deciding when prior context has gone stale — is its own task statement.
Critical design decisions
- Task in allowedTools: this is a prerequisite, not a preference. A coordinator without it can’t spawn anything, no matter how well everything else is configured.
- What to pass, explicitly: the complete findings from prior subagents, not a summary of them. A coordinator that paraphrases before handing off is already losing information before the next subagent even starts.
- Structured over free-text handoffs: a fixed format (claim, source, page) survives being passed from one subagent’s output into another subagent’s input. Prose doesn’t — it invites paraphrasing at every step it passes through.
- Parallel emission: independent subagent calls belong in the same turn. Spacing them across separate turns adds latency for no benefit, the same way an unnecessary sequential dependency would.
- Goals over steps in the coordinator’s prompt: a fixed procedure only works when nothing unexpected happens. A prompt that states the quality bar, not the sequence, is what lets the coordinator adapt when it doesn’t.
- Fresh vs. forked: spawn fresh when a subagent’s task is independent of anything else in flight; fork when it needs to continue from work already done and try a variant.
Consequences
- No
Taskin allowedTools -> the coordinator can reason its way toward delegating and never actually manage it. Nothing errors. It just quietly never spawns a subagent, and that’s easy to mistake for a prompting problem instead of a missing permission. - Assumed context inheritance -> a subagent spawned with a short instruction and none of the coordinator’s findings either answers off information it doesn’t have, or asks a question the coordinator already knew the answer to. Either way, the coordinator paid for a subagent call that couldn’t succeed.
- Unstructured handoffs -> the example project’s lossy-handoff demo shows this directly: the same synthesis subagent, given the same findings but paraphrased into plain sentences first, has nothing left to cite. The claim survives. The attribution doesn’t.
- Sequential-only spawning -> independent subagent calls wait on each other for no reason, adding latency a single coordinator turn could have avoided.
- A procedural coordinator prompt -> the coordinator executes its steps even when the situation no longer matches them — a missing source, an unanswerable sub-question — because it was never given a goal to fall back on, only a sequence to finish.
None of this replaces what Part 2 already covers. A subagent still needs a reason to exist and a clear slice of the domain. This part is what makes a subagent’s invocation actually correct: the tool that spawns it, the context it’s handed, and the prompt that tells it — and the coordinator — what it’s actually there to do.