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
stopReasonloop 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
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"))
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
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
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)
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)