Module 15B of 30 · BUILD
Build an Agent +
Subagent System

A hands-on lab compressed into the patterns behind it. One coordinator, two specialists, isolated context, structured handoffs — the design that scales when one agent juggling everything stops working.

Track 4: Agent Architectures Hands-on Lab ⏱ ~15 min read 15B / 30
Concept 1 of 5

One mind cannot juggle every job.

The desktop lab assembles a complete UCC filing research system: a coordinator agent at the top, two specialist subagents below it (research and risk analysis), and three tools split between them. The user asks one question; the coordinator decides who handles what, hands them a task, collects results, and writes a single answer.

This is the smallest pattern that earns the name "agent system." It is bigger than a single ReAct loop and smaller than a fleet of agents on a queue. Once you have built it once, every more complex architecture is the same pattern repeated — more spokes, more layers, same hub.

You learn it because production agents almost never live in a single loop. The moment you cross 4–5 tools, accuracy collapses and you reach for this pattern.

Concept 1 of 5

The law-firm partner

BEFORE: Imagine a solo lawyer doing every part of a case — intake interviews, discovery, motion drafting, witness prep, courtroom argument. One brain, one calendar, one mouth.

PAIN: Above a certain caseload, quality drops. The lawyer forgets which client said what, mixes up filing deadlines across jurisdictions, and pastes boilerplate from the wrong matter. Every additional skill steals attention from the others.

MAPPING: A real firm has a lead partner who interviews the client, then delegates: "Associate, run discovery on this entity. Paralegal, pull all liens in Texas. Bring the results back to me, I'll synthesize the strategy memo." That partner is the coordinator. Each specialist runs their job in isolation and returns a clean deliverable. The partner stitches it into one answer for the client.

Concept 1 of 5

Five moving parts

  1. Coordinator agentOwns the user-facing conversation. Decides which subagent to call. Synthesizes their structured outputs into one coherent reply.
  2. Research subagentSpecialist for finding UCC filings. Has only two tools: search_filings, get_filing_details. Returns structured JSON.
  3. Analysis subagentSpecialist for risk scoring. Has one tool: check_risk_score. Receives explicit context from the coordinator (debtor IDs, filing summaries).
  4. Mock data layerRealistic sample filings — just enough variety to exercise every branch. Replaces a real DB so you can build without infra.
  5. Test harnessHappy path, edge cases, error scenarios. Proves each part works in isolation before you wire them together.
Lab on desktop: The hands-on build walks 10 sequential steps to assemble all five parts — mock data, tools, single-agent baseline, two subagents, the coordinator, the wiring, and an end-to-end test suite. Roughly 60–90 minutes at the keyboard.
Concept 1 of 5

Pseudocode

The whole system in one shape — coordinator routes, subagents specialize:

# THE WHOLE SYSTEM IN ~12 LINES

FUNCTION ucc_research_system(user_question):
  # 1. Coordinator decides what to do
  plan = coordinator.understand(user_question)

  # 2. Coordinator delegates to specialists, in order
  research = run_subagent("research",
                         task=plan.research_brief)

  risk     = run_subagent("analysis",
                         task=plan.risk_brief
                              + research.summary)  # explicit handoff

  # 3. Coordinator stitches a single answer
  RETURN coordinator.synthesize(research, risk)

The lab spends most of its time on three things: what each subagent receives, what each returns, and how the coordinator threads results together. Get those right and the rest is plumbing.

Concept 1 of 5

Misconceptions

"This is overkill for three tools — just put them on one agent."
For three tools, a single agent does work fine. The point of building it now is the pattern. By the time you reach 10+ tools (and you will), the single-agent code is a rewrite. Start with the pattern that scales.
"Multi-agent always beats single-agent."
Not always. Multi-agent costs more API calls, adds latency, and introduces handoff bugs. A single agent with 2–3 tools is simpler and usually good enough. The lab builds the single-agent baseline first precisely so you can feel the trade-off.

The whole lab teaches one shape: hub-and-spoke. Coordinator at the centre, specialists on the edges, structured handoffs in between. Three tools today, fifteen tomorrow — same shape.

Mobile teaches the design. Desktop builds it line by line.

Concept 2 of 5

Tool boundaries shape the agents.

Architecture in this system means one decision repeated three times: which tool belongs to which agent. The coordinator gets delegation tools (call research, call analysis). The research subagent gets search tools. The analysis subagent gets scoring tools. Nothing overlaps.

The other half of architecture is the context pipe. Each subagent starts with a blank context window. The only thing it knows about the world is whatever the coordinator types into the task prompt. So the architecture isn't just "who has which tools" — it's also "who explicitly hands what to whom."

Get tool boundaries right and the agents stay focused. Get the context pipe right and they actually cooperate.

Concept 2 of 5

The IVR-routed call centre

BEFORE: Pre-IVR call centres had one operator handling every call — billing, tech support, returns, scheduling. That operator had to know everything and toggle between five different systems while you waited on hold.

PAIN: Slow and error-prone. Add a sixth product line and the wheels come off — wrong manual, mixed-up customers, forgotten context. Same failure mode as a single agent with too many tools.

MAPPING: Modern call centres route you to a specialist: "Press 1 for billing, press 2 for tech support." The IVR is the coordinator. Each specialist sees only their script and tools (their tool boundary) and gets a clean transfer summary from the previous agent (the context pipe). Your agent system has the same shape: routing at the front, specialists in the middle, explicit context handoffs along the way.

Concept 2 of 5

The three design decisions

  1. Why split when 3 tools fit on one agent?Because the pattern matters more than the count. Tool selection accuracy degrades above 4–5 tools per agent. Establishing the split now means the system holds up at 10, 20, 50 tools without a rewrite.
  2. Why pass context explicitly?Subagents start with a blank context window. They never see the coordinator's chat history. If the user said "their TX filings" and the coordinator forwards that raw, the subagent has no clue who "their" is. The coordinator must resolve every reference and pack it into the task string.
  3. Why structured results, not prose?So the coordinator can pull values reliably. research.debtor_id is a field lookup. Trying to regex "Acme's debtor ID is ACME-001" out of a paragraph breaks the moment Claude rephrases. JSON in, JSON out.

Three decisions. Every other architectural question in the lab is a corollary of one of them.

Concept 2 of 5

When to split into subagents

# Should this be one agent or many?

IF tool_count <= 3 AND tools share one domain:
  USE single agent           # cheaper, faster, simpler

ELIF tool_count IN 4..5 AND domains overlap:
  USE single agent
  PLAN the split for v2     # write subagent boundaries now

ELIF tool_count >= 5 OR domains diverge:
  USE coordinator + subagents # this lab
  # 1 spec per subagent, 1-3 tools each

ELIF subagent_count > 5:
  GO hierarchical          # sub-coordinators per group
  # avoids the same selection problem at the next level

Heuristic: if a teammate could explain the agent's job in one sentence, it's well-scoped. If you need three sentences and a "but," split it.

Concept 2 of 5

Misconceptions

"Subagents inherit the coordinator's conversation."
They don't. Each subagent gets a fresh, empty context. The only thing it knows is what the coordinator typed into the task prompt. This is the #1 source of multi-agent bugs.
"Subagents run in parallel automatically."
Not in this lab. The coordinator calls them sequentially. Parallel calls require explicit asyncio.gather or Promise.all — covered as a stretch goal in cluster 5.

Architecture is two questions: "who has which tools?" and "who hands what to whom?" Get both right and the rest is implementation.

The exam likes hub-and-spoke for the same reasons engineers do: single coordination point, isolated contexts, structured handoffs, auditable flow.

Concept 3 of 5

A subagent is a fresh, focused agent.

A subagent is not a sub-prompt or a function call — it's a complete agent invocation with its own system prompt, its own small tool set, and a brand-new context window. It runs its own ReAct-style loop until it has an answer, then returns that answer to whoever called it.

The key word is focused. The research subagent's system prompt says "you find UCC filings" and lists two tools. It does not know about risk scoring; the tool isn't even in its schema. That tight scope means better tool selection, cleaner context, and no domain bleed. Each subagent is a one-job specialist by construction.

Concept 3 of 5

The contractor with a sealed envelope

BEFORE: Imagine a contractor who shows up at a job site, opens whatever's already there, fixes whatever they want, and signs it off. They've seen everything you've ever done; they assume context that may not be relevant.

PAIN: Inconsistent work. The contractor "helps" by touching things outside the brief. They overhear a conversation about a different job and apply that decision to this one. You can't audit what they did because it leaks across boundaries.

MAPPING: A subagent is the opposite contractor — they arrive on site with a sealed envelope. The envelope contains the entire brief: what to do, what tools they're allowed to use, what known facts to assume. They never see anything outside that envelope. When they're done, they slide a signed report back. Auditable, repeatable, no bleed.

Concept 3 of 5

What runs inside a subagent

  1. Receive taskThe coordinator passes one string: a fully self-contained brief. "Find all UCC filings for Acme Corporation in TX" — not "look up their TX filings."
  2. Boot fresh contextBuild a brand-new message list: [{system: spec.role}, {user: task}]. Nothing else. No history, no shared state.
  3. Run the inner ReAct loopCall Claude with the subagent's specialist system prompt and small tool set. If stop_reason is "tool_use", run the tool, append the result, repeat. If "end_turn", exit.
  4. Parse and returnExtract the final text. Try JSON.parse; if it fails, wrap as {"summary": text}. Return the structured object back to the coordinator.
  5. Cap with max_turnsA safety limit (e.g., 6 turns). If the subagent loops without finishing, return an error rather than burning your API quota.
Concept 3 of 5

Pseudocode

One generic run_subagent handles every specialist — it's the spec that varies, not the loop:

# Each subagent has a spec (declarative, in a file)
research_spec = {
  role:  "You find UCC filings. Return JSON.",
  tools: [search_filings, get_filing_details],
  max_turns: 6
}

FUNCTION run_subagent(spec, task_string):
  # Fresh, isolated context every call
  ctx = [system: spec.role, user: task_string]

  FOR turn IN 1..spec.max_turns:
    reply = CALL Claude(ctx, tools=spec.tools)

    IF reply.stop_reason == "tool_use":
      FOR call IN reply.tool_uses:
        result = run_tool(call.name, call.input)
        ctx.append(tool_result(call.id, result))
      CONTINUE

    IF reply.stop_reason == "end_turn":
      RETURN parse_json(reply.text)
                  or {summary: reply.text}

  RETURN {error: "max turns reached"}

Same shape as a single ReAct loop — just packaged so it can be called by another agent. In Claude Code style, the spec lives at .claude/agents/<name>.md: declarative, version-controlled, no glue code per subagent.

Concept 3 of 5

Misconceptions

"A subagent is just a sub-prompt with different instructions."
No. It's a separate agent invocation with its own system prompt, its own tools, and a blank context window. Same model, fresh slate. The isolation is the feature.
"Subagents can call each other directly to skip the coordinator."
Don't let them. Cross-talk between specialists destroys the auditable single-coordination-point property. Subagents return to the coordinator; the coordinator decides what comes next. Otherwise you get tangled multi-agent spaghetti with no one in charge.
"Returning prose is fine — the coordinator is smart, it'll figure it out."
It will, until it doesn't. Prose handoffs break the moment Claude rephrases. Force JSON output in the system prompt, parse it on return, and only fall back to prose as an emergency. This single rule eliminates an entire class of bugs.

A subagent is a fresh agent invocation, not a sub-prompt. Own system prompt, own small toolset, blank context. Returns structured data on the way out.

Define each subagent declaratively (Claude Code style: .claude/agents/<name>.md). One file per specialist. Version-controlled, reviewable, swappable.

Concept 4 of 5

The coordinator's tools are other agents.

The coordinator does not search filings. It does not score risk. It has zero domain tools. Its tools are delegation tools: research_filings(task) and analyze_risk(task). Each one launches a subagent and returns its structured result.

From Claude's perspective, calling a subagent looks identical to calling any other tool — same JSON Schema, same stop_reason == "tool_use" dance. The difference is what happens server-side: instead of querying a database, your code spins up a complete subagent invocation, runs its inner loop, and pipes the result back.

So the coordinator is doing four things in a loop: route, brief, collect, synthesize. That's the entire pattern.

Concept 4 of 5

The newsroom editor

BEFORE: A reporter who tries to do everything — investigate, photograph, fact-check, write headlines, lay out the page, copy-edit. The article ships, but it's mediocre on every axis.

PAIN: Time fragments across crafts. The reporter forgets the lead while picking a font. Quality suffers in proportion to the number of hats.

MAPPING: An editor doesn't write or take photos — they assign. "Reporter, get the quotes by 4pm. Photographer, headshot of the source. Fact-checker, verify the timeline. Bring it back to me, I'll wire it together for the front page." That's the coordinator: a router, briefer, collector, and synthesizer. Their value is judgment, not labour.

Concept 4 of 5

Route, brief, collect, synthesize

  1. RouteRead the user's question and decide which subagent(s) to call and in what order. "Risk for Acme?" → research first, then analysis.
  2. BriefBuild the task string for each subagent. Resolve pronouns ("their" → "Acme Corporation"). Pack in IDs, scope, and known facts. The subagent will see only what's in this string.
  3. CollectReceive the structured result. Validate status == "success". Pull the fields you need (debtor_id, summary) for the next handoff. Handle errors gracefully — partial results beat no results.
  4. SynthesizeCombine results into one user-facing answer. Cite specifics (filing numbers, dollar amounts, risk scores). Be explicit about anything that failed.

Multi-turn bonus: the coordinator also owns conversation history. Pronoun resolution lives at this layer, never inside the subagents.

Concept 4 of 5

Pseudocode

The coordinator's tools are the subagents:

# Coordinator tools = delegation calls, not DB queries
COORDINATOR_TOOLS = [
  {name: "research_filings", input: {task: string}},
  {name: "analyze_risk",    input: {task: string}}
]

FUNCTION dispatch(tool_name, tool_input):
  task = tool_input.task        # the brief
  IF tool_name == "research_filings":
    RETURN run_subagent(research_spec, task)
  IF tool_name == "analyze_risk":
    RETURN run_subagent(analysis_spec, task)

FUNCTION coordinator(user_msg, history):
  ctx = history + [user: user_msg]
  LOOP:
    reply = CALL Claude(ctx,
                  system=COORDINATOR_PROMPT,
                  tools=COORDINATOR_TOOLS)

    IF reply.stop_reason == "tool_use":
      FOR call IN reply.tool_uses:
        result = dispatch(call.name, call.input)
        ctx.append(tool_result(call.id, result))
      CONTINUE

    IF reply.stop_reason == "end_turn":
      RETURN reply.text, ctx

Notice: identical loop shape to a regular tool-using agent. Subagents are tools that happen to be smart.

Concept 4 of 5

Misconceptions

"The coordinator should also have the search tools, just in case."
No. Give the coordinator only delegation tools. The moment it has a domain tool, it stops routing and starts doing the work itself — defeating the split. Strict separation: coordinator routes, specialists execute.
"More subagents = more powerful coordinator."
Not above 4–5. The coordinator treats subagents as tools, and tool selection accuracy degrades just like any other agent. Beyond five specialists, the coordinator picks wrong — that's when you go hierarchical (sub-coordinators per group).
"Pronoun resolution belongs in the subagent prompt — just teach it to ask."
Wrong layer. The subagent has no history to resolve against. Pronoun resolution is a coordinator responsibility — rewrite "their TX filings" as "Acme Corporation in TX" before sending. Brief, then dispatch.

The coordinator routes, briefs, collects, synthesizes. Its tools are subagents. It owns conversation history and pronoun resolution. Without it, specialists can't cooperate.

If the coordinator is doing domain work, the split is wrong. If a subagent is asking the user clarifying questions, the brief was incomplete. Both signals point back to the coordinator.

Concept 5 of 5

The lab system is the engine, not the car.

What you build in the lab works on your laptop. It can answer real-ish questions about real-ish data with real Claude calls. But it's missing every layer that makes an agent safe, observable, and shippable: input validation, output guardrails, tracing, rate limits, persistence, deployment, evals.

"Going Further" is the bridge from "works in dev" to "runs in production." Some of it lives in later modules (M16 input guardrails, M19 tracing, M21 deployment). Some are stretch goals you can layer on top of the lab today — parallel subagents, an entity-resolution specialist, a caching layer, an interactive CLI.

The most important "going further" is the architectural one: rebuild this same agent in M26 using the Agent SDK. About 50 lines instead of 250. Same output, one fifth the code, all the abstractions you just wrote by hand.

Concept 5 of 5

The car you built in your driveway

BEFORE: You spent the weekend assembling a working engine in your garage. It turns over. The pistons fire in the right order. You put it on a wooden dolly and pushed it up the driveway under its own power. Real progress.

PAIN: But it's not a car. There are no seatbelts, no brakes, no dashboard, no headlights, no chassis, no road. Drive it as-is and the first bump kills you.

MAPPING: The lab system is the engine. M16/M17 add seatbelts (guardrails). M19/M20 add the dashboard (tracing and monitoring). M21/M22 add chassis and road (deployment). The capstone bolts them together. Don't skip the engine — but don't try to drive it home either.

Concept 5 of 5

Five practical extensions

  1. Add an entity-resolution subagentNormalize "Acme Corp", "ACME CORPORATION", "Acme Inc" to one canonical entity. Wire it in before research, so downstream subagents always work with a clean ID.
  2. Run subagents in parallelWhen research and analysis are independent (different entities, different jurisdictions), fan out with asyncio.gather / Promise.all. Latency drops with no logic change.
  3. Cache subagent resultsRepeated queries for the same debtor shouldn't hit the API twice. A simple dict/Map keyed on (subagent, task_hash) saves money and time.
  4. Stream tokens as they arriveWrap each Claude call with stream=True. The user sees text appear in real time instead of waiting 60 seconds for a synthesized blob.
  5. Build an eval suiteCodify your test cases as graded scenarios. "For 'risk on Acme', expect HIGH and at least 2 cited filings." Run on every code change to catch regressions.

Each extension preserves the hub-and-spoke shape — you're adding capabilities, not changing the architecture.

Concept 5 of 5

What to add, when

# From lab system to production agent

IF users are anyone other than you:
  ADD input guardrails (M16)
  # prompt injection, scope checks, PII

IF outputs go to a UI or downstream system:
  ADD output guardrails (M17)
  # schema validation, hallucination checks

IF bugs need to be diagnosed without re-running:
  ADD tracing & logs (M19, M20)
  # every coordinator decision, every subagent call

IF users are not on your laptop:
  ADD an HTTP API (M21) AND deploy (M22)

IF the code is >200 lines of agent loop:
  CONSIDER Agent SDK (M26)
  # same agent, ~50 lines, hooks & sessions built in

IF you ship to prod and need to refactor confidently:
  ADD evals (M18)
  # otherwise every change is a coin flip

Add layers in the order failures hurt you. Most teams hit guardrails first, observability second, deployment third.

Concept 5 of 5

Misconceptions

"The lab is enough for production — just add a Dockerfile."
A Dockerfile ships your code; it doesn't make it safe. Without input guardrails, the first user with a prompt-injection attack walks past every "rule" in your system prompt. Without observability, you can't diagnose failures. The lab is the first 30%.
"If the SDK does the same thing in 50 lines, why bother building it by hand?"
Because when the SDK does something unexpected, you need to know what it abstracts. Build the loop yourself once and the SDK becomes a productivity tool, not a black box. M26 explicitly compares the two.

The lab system is the engine. Real production needs guardrails, tracing, deployment, and evals — the next several modules in the course.

The most valuable "going further" move: rebuild this exact agent with the Agent SDK in M26. Then build it a third time from a spec in CAPSTONE-7. Three approaches, same agent, different trade-offs — you choose what fits your team.

One question per concept

Tap a card to reveal the answer.

The hands-on build is on desktop

You now know the architecture: coordinator + specialists, isolated contexts, structured handoffs, the four coordinator jobs. The desktop module turns that understanding into a working multi-agent system — ten sequential steps, both Python and Node.js, ~60–90 minutes at the keyboard.

What you'll do there: scaffold the project, build mock UCC data, write three tools with JSON Schema, run a single-agent baseline, build the two specialist subagents, wire the coordinator with delegation tools, test multi-turn pronoun resolution, and run an end-to-end test suite. By the end you have ~250 lines of code you wrote yourself and a system that answers real questions.

Open M15B Lab on Desktop → 10 sequential steps · Python + Node.js · ~60–90 min at the keyboard

Module 15B of 30 · Track 4: Agent Architectures · BUILD