DEV Community

Cover image for The coordinator-subagent pattern: the foundation every Claude multi-agent system is built on
Aj
Aj

Posted on • Originally published at cloudedventures.com

The coordinator-subagent pattern: the foundation every Claude multi-agent system is built on

Agent Teams landed in Claude Opus 4.6. Everyone's excited. But before you touch experimental features, understand the foundational pattern everything is built on.

TL;DR

  • Coordinator receives task, delegates to specialists via tool calls
  • Each subagent gets its own isolated context window and system prompt
  • Subagents cannot talk to each other — everything routes through coordinator
  • Same stopReason loop as single-agent, tool calls just dispatch to separate API calls
  • 3–4x token cost vs single agent — only use when specialist quality justifies it

Pattern 1 — Coordinator-subagent (pure API)

The flow looks like this:

User Request
     ↓
Coordinator Agent  ←── stopReason: tool_use
     ↓
  Route to specialist
     ↓
┌──────────────────────────────────┐
│ research_agent │ writer_agent    │  ← Each: isolated context,
│ reviewer_agent │ any_specialist  │    own system prompt, own tools
└──────────────────────────────────┘
     ↓
Tool result back to coordinator
     ↓
stopReason: end_turn → return answer
Enter fullscreen mode Exit fullscreen mode

Here's the full working implementation:

import boto3

client = boto3.client("bedrock-runtime", region_name="us-east-1")

subagent_tools = [
    {
        "toolSpec": {
            "name": "research_agent",
            "description": "Research specialist. Use for finding and analyzing information.",
            "inputSchema": {
                "json": {
                    "type": "object",
                    "properties": {"task": {"type": "string"}},
                    "required": ["task"]
                }
            }
        }
    },
    {
        "toolSpec": {
            "name": "writer_agent",
            "description": "Writing specialist. Always pass research as context.",
            "inputSchema": {
                "json": {
                    "type": "object",
                    "properties": {
                        "task": {"type": "string"},
                        "context": {"type": "string"}
                    },
                    "required": ["task", "context"]
                }
            }
        }
    }
]

def run_subagent(name: str, **kwargs) -> str:
    prompts = {
        "research_agent": "You are a research specialist. Be factual and concise.",
        "writer_agent": "You are a writing specialist. Use only facts from the context provided."
    }
    content = "\n".join(f"{k}: {v}" for k, v in kwargs.items())
    response = client.converse(
        modelId="anthropic.claude-3-sonnet-20240229-v1:0",
        system=[{"text": prompts[name]}],
        messages=[{"role": "user", "content": [{"text": content}]}]
    )
    return response["output"]["message"]["content"][0]["text"]

def run_coordinator(request: str) -> str:
    messages = [{"role": "user", "content": [{"text": request}]}]
    system = [{"text": "Coordinate specialists. Research first, then write."}]

    for _ in range(10):  # Safety cap
        response = client.converse(
            modelId="anthropic.claude-3-sonnet-20240229-v1:0",
            system=system,
            messages=messages,
            toolConfig={"tools": subagent_tools}
        )
        output = response["output"]["message"]
        messages.append(output)

        if response["stopReason"] == "end_turn":
            return output["content"][0]["text"]

        tool_results = []
        for block in output["content"]:
            if "toolUse" not in block:
                continue
            tool = block["toolUse"]
            if tool["name"] == "research_agent":
                result = run_subagent("research_agent", task=tool["input"]["task"])
            elif tool["name"] == "writer_agent":
                result = run_subagent("writer_agent", **tool["input"])
            tool_results.append({
                "toolResult": {
                    "toolUseId": tool["toolUseId"],
                    "content": [{"text": result}]
                }
            })
        messages.append({"role": "user", "content": tool_results})

    return "Safety cap reached"

print(run_coordinator("Write a post about why hands-on labs beat certifications alone"))
Enter fullscreen mode Exit fullscreen mode

Pattern 2 — Claude Code subagents

Claude Code Session
      ↓
Main agent → Task tool → specialist-1  (isolated context)
           → Task tool → specialist-2  (isolated context)
           → Task tool → specialist-3  (isolated context)
      ↓
All report back to main agent only — no sideways talk
      ↓
Main agent synthesizes output
Enter fullscreen mode Exit fullscreen mode

Same one-way reporting as Pattern 1, but running inside Claude Code via the Task tool. Use for local dev workflows — security reviews, test runs, documentation. Stable and production-ready today.


Pattern 3 — Agent Teams (experimental)

Team lead
   ↙         ↓         ↘
Researcher ⟺ Writer ⟺ Reviewer
   ↘         ↓         ↙
Team lead synthesizes

⟺ = direct peer-to-peer communication
Enter fullscreen mode Exit fullscreen mode

The key difference: teammates message each other directly without going through the lead. Requires CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1. Known issues with session resumption and shutdown. Keep off production.


Three patterns compared

Pattern Peer talk Production Token cost Use when
Coordinator-subagent (API) No ✅ Yes 3–4x Production API agents
Claude Code subagents No ✅ Yes 3–4x Local dev workflows
Agent Teams Yes ⚠️ Experimental 3–4x+ Peers need mid-task sharing

The critical mistake

Subagents share nothing. If your writer needs the research output, you must pass it explicitly.

# WRONG — writer has zero context
research = run_subagent("research_agent", task="research Lambda limits")
writer = run_subagent("writer_agent", task="write about Lambda")

# RIGHT — explicitly pass context through coordinator
research = run_subagent("research_agent", task="research Lambda limits")
writer = run_subagent("writer_agent", task="write about Lambda", context=research)
Enter fullscreen mode Exit fullscreen mode

Isolation is the feature, not the bug. Design for it explicitly.


What comes next

  • Retry logic — coordinator decides whether to retry a failed subagent or escalate to a human
  • Parallel subagents — multiple tool calls in one response run simultaneously, cutting latency
  • Decision routing — confidence scores from one subagent pick the next specialist

Build it hands-on

For hands-on labs covering the full multi-agent coordinator pattern in real AWS Bedrock sandboxes — Cloud Edventures CCA-001 track, 22 labs, automated validation, no AWS account needed.

👉 cloudedventures.com/labs/track/claude-certified-architect-cca-001

Are you building production API agents or Claude Code workflows? Drop a comment.

Top comments (0)