Agent Architectures.
Production-ready agent patterns — from the foundational ReAct loop to multi-agent orchestration, RAG, memory systems, and critic-refinement. Each template includes pseudocode, strengths, stack recommendations, and cost estimates.
Core Patterns
The foundational loop. The model reasons about the task, picks a tool, observes the result, and reasons again until complete.
while not done:
thought = llm.think(history)
if thought.action:
result = tools[thought.action](thought.input)
history.append(observation=result)
else:
return thought.final_answer
Orchestrator breaks task into subtasks, dispatches to specialist agents, aggregates results.
User → Orchestrator → [Agent A | Agent B | Agent C]
→ Aggregator → Response
Agent decides when to retrieve context. Retrieval is a tool call, not a preprocessing step.
Query → Embedding → Vector Search → Context Injection → Generation Key params: chunk_size=512, overlap=64, top_k=5, reranker=cross-encoder
Short-term = conversation context window. Long-term = vector store of past interactions. On each turn: retrieve relevant memories → inject into context → generate → store new memory.
Generator produces output → Critic evaluates against criteria → Refiner incorporates feedback → repeat until pass.
output = generator.generate(task) for i in range(max_iterations): feedback = critic.evaluate(output, criteria) if feedback.passes: break output = refiner.improve(output, feedback) return output
Model identifies all independent tool calls and executes them in parallel. Use tool_choice: "auto" with multiple tools defined. Model emits multiple tool_use blocks in one response.
# Simultaneously fetch GitHub stats + Stripe revenue + PostHog analytics # → compose dashboard # Result: 3–5× speedup vs sequential; fewer total LLM turns
Supervisor classifies intent → routes to specialized subagent → returns to supervisor for output handling.
Input → Supervisor (classify)
→ [CodeAgent | ResearchAgent | DataAgent]
→ Supervisor (format) → Output
Agent wakes on external event (webhook, schedule, file change). Performs task. Goes back to sleep. No persistent conversation — each invocation is stateless (state lives in external store).
Agent catches its own errors and attempts automated recovery. On tool failure: retry with modified input, try alternative tool, or escalate to human.
try:
result = agent.execute(task)
except ToolError as e:
strategy = error_classifier.classify(e)
if strategy == 'retry':
result = agent.execute(task, hint=e.message)
elif strategy == 'fallback':
result = fallback_tool.execute(task)
else:
return escalate_to_human(task, e)
Planner LLM generates a full task plan upfront. Executor LLM carries out each step. Planner can revise the plan based on execution results.
plan = planner_llm.create_plan(task)
results = []
for step in plan.steps:
result = executor_llm.run(step, context=results)
results.append(result)
if result.needs_replan:
plan = planner_llm.revise(plan, result)
return synthesizer.combine(results)
Agent pauses at defined checkpoints to request human approval before proceeding. Supports async approval via Slack, email, or UI.
for step in plan:
agent.execute(step)
if step.requires_approval:
approval = await human_gateway.request(
action=step.description,
timeout=3600
)
if not approval.granted:
return abort_with_explanation(approval.reason)
Specialized agent for transforming unstructured data (documents, emails, web pages) into typed, validated schemas using forced JSON output.
const schema = z.object({
title: z.string(),
date: z.string().datetime(),
items: z.array(z.object({
name: z.string(),
amount: z.number(),
})),
})
const result = await anthropic.messages.create({
tools: [{ name: 'extract', input_schema: zodToJsonSchema(schema) }],
tool_choice: { type: 'tool', name: 'extract' },
messages: [{ role: 'user', content: rawDocument }],
})
Agent writes code, executes it in a sandbox, reads the output, and iterates until the result matches the goal. Combines code generation with tool-based execution.
goal = "Analyze sales_data.csv and find the top 5 products by revenue"
while not satisfied:
code = llm.write_code(goal, context=previous_outputs)
stdout, stderr = sandbox.execute(code)
if stderr:
context.add(error=stderr)
elif not satisfies_goal(stdout, goal):
context.add(output=stdout, feedback="Not quite — refine")
else:
return format_result(stdout)
Cost & Latency Profile
Estimates based on Claude API pricing at 1,000 runs/month. Token counts include input + output. Costs vary significantly with prompt complexity and tool call frequency.
| Pattern | Avg Tokens/Run | Latency | Model Recommendation | Cost/1k runs |
|---|---|---|---|---|
| ReAct | 2k–8k | <3s | Sonnet 4.6 | $5–20 |
| Multi-Agent | 10k–50k | 10–60s | Sonnet (supervisor) + Haiku (workers) | $50–200 |
| RAG Agent | 4k–12k | <5s | Sonnet 4.6 | $10–40 |
| Memory Agent | 6k–20k | 3–10s | Sonnet 4.6 | $20–60 |
| Critic-Refinement Loop | 8k–30k | 10–30s | Sonnet (2× calls) | $25–90 |
| Tool-Use (Parallel) | 3k–10k | <5s | Sonnet 4.6 | $8–30 |
| Supervisor / Router | 4k–15k | 5–15s | Haiku (routing) + Sonnet | $10–45 |
| Event-Driven | 1k–5k | <2s | Haiku 4.5 | $1–10 |
| Self-Healing | 4k–20k | 5–20s | Sonnet 4.6 | $12–60 |
| Plan-and-Execute | 6k–30k | 15–60s | Opus (plan) + Haiku (exec) | $30–150 |
| Human-in-the-Loop | 3k–10k | async | Sonnet 4.6 | $8–30 |
| Structured Extraction | 1k–4k | <2s | Haiku 4.5 | $2–10 |
| Code Interpreter | 5k–25k | 10–60s | Sonnet 4.6 | $15–80 |
Model Selection Guide
haiku-4-5
Best for
- Classification
- Routing
- Simple extraction
- High-volume tasks
- Event-driven triggers
Avoid when
- Complex multi-step reasoning
- Long-horizon planning
- Creative generation
sonnet-4-6
Best for
- Most agent tasks
- Tool use
- Code generation
- Structured output
- Multi-step reasoning
Avoid when
- Extreme cost constraints at scale
- Tasks solvable by Haiku
opus-4-7
Best for
- Complex reasoning
- Long-horizon planning
- Planner in Plan-and-Execute
- Highest accuracy tasks
Avoid when
- High-volume workloads
- Simple routing or classification
- Any task Sonnet handles well
Model Selection Decision Tree
Is the task simple?
(classification, routing, extraction)
│
├─ YES → Use Haiku 4.5
│ Fast + cheap
│
└─ NO → Does it require deep reasoning
or long-horizon planning?
│
├─ YES → Use Opus 4.7
│ Best accuracy
│
└─ NO → Use Sonnet 4.6
Best balance
TypeScript · Complete Example
Complete Agent Loop · TypeScript
// Complete Claude agentic loop in TypeScript
import Anthropic from '@anthropic-ai/sdk'
const client = new Anthropic()
const tools: Anthropic.Tool[] = [
{
name: 'read_file',
description: 'Read a file from the filesystem',
input_schema: {
type: 'object' as const,
properties: {
path: { type: 'string', description: 'Absolute file path' },
},
required: ['path'],
},
},
{
name: 'write_file',
description: 'Write content to a file',
input_schema: {
type: 'object' as const,
properties: {
path: { type: 'string' },
content: { type: 'string' },
},
required: ['path', 'content'],
},
},
]
async function runAgent(task: string): Promise<string> {
const messages: Anthropic.MessageParam[] = [
{ role: 'user', content: task },
]
while (true) {
const response = await client.messages.create({
model: 'claude-sonnet-4-6',
max_tokens: 4096,
tools,
messages,
})
messages.push({ role: 'assistant', content: response.content })
if (response.stop_reason === 'end_turn') {
const textBlock = response.content.find(b => b.type === 'text')
return textBlock?.type === 'text' ? textBlock.text : ''
}
// Process all tool calls in parallel
const toolUses = response.content.filter(b => b.type === 'tool_use')
const toolResults = await Promise.all(
toolUses.map(async (block) => {
if (block.type !== 'tool_use') return null
const result = await executeToolCall(block.name, block.input)
return {
type: 'tool_result' as const,
tool_use_id: block.id,
content: result,
}
})
)
messages.push({
role: 'user',
content: toolResults.filter(Boolean) as Anthropic.ToolResultBlockParam[],
})
}
}
Frameworks Comparison
| Framework | Language | Best For | Abstraction | Agent Type |
|---|---|---|---|---|
| LangChain | Python/JS | Rapid prototyping, RAG | High | Single/Multi |
| LangGraph | Python | Complex stateful agents | Medium | Multi-agent |
| AutoGen | Python | Multi-agent debate | Medium | Multi-agent |
| CrewAI | Python | Role-based crews | High | Multi-agent |
| Llama Index | Python | Data-focused RAG | High | RAG/Tool |
| Pydantic AI | Python | Type-safe agents | Low | Single |
| Claude SDK | Any | Direct API, full control | Low | Any |
Common Mistakes · Anti-Patterns
Agent for simple tasks — If a single LLM call solves it, don't add an agent loop. Agents add latency, cost, and failure modes.
Unbounded loops — Always set max_iterations. An agent that loops forever is a runaway cost bill.
No error handling on tool calls — Tool failures should be caught and injected back as observations, not thrown.
Shared mutable state between agents — Each agent should receive inputs and return outputs. Side effects across agents cause race conditions.
Over-trusting agent output — For high-stakes actions (delete, send, deploy), always validate tool call inputs before execution.