DEV Community

syncore
syncore

Posted on

Multi-Agent Workflows with Claude: Patterns and Pitfalls

3 min read · 604 words

Multi-agent systems are currently all the rage. Splitting a monolithic task—like building a web app or writing a research paper—into specialized agents (e.g., a Planner, a Coder, a Reviewer) sounds like the ultimate scaling trick.

When applied to modern LLMs, multi-agent architectures can be powerful, but they also introduce silent failure modes, runaway token usage, and frustrating debugging sessions.

Here is how to build practical, production-ready multi-agent workflows using claude-opus-5 and claude-sonnet-5, along with the patterns you should follow and the pitfalls to avoid.


The Architecture: Router-Worker Pattern

Instead of creating a tangled web where every agent talks to every other agent, stick to deterministic orchestrations. The Router-Worker pattern is one of the most reliable architectures. A primary router analyzes the user request and delegates sub-tasks to specialized worker agents.

Let's implement a clean, two-tier workflow using the official Python SDK: a Research Agent that gathers data, followed by a Synthesis Agent that writes the final report.

import anthropic

client = anthropic.Anthropic()

def run_researcher(topic: str) -> str:
    """Worker Agent 1: Gathers technical details."""
    print(f"🤖 [Researcher] Investigating: {topic}")

    response = client.messages.create(
        model="claude-sonnet-5",
        max_tokens=8000,
        output_config={"effort": "medium"},
        messages=[
            {
                "role": "user",
                "content": f"Provide 3 key technical challenges and solutions for: {topic}"
            }
        ]
    )
    return response.content[0].text

def run_synthesizer(topic: str, research_data: str) -> str:
    """Worker Agent 2: Synthesizes findings into a final document."""
    print(f"🤖 [Synthesizer] Formatting final report...")

    response = client.messages.create(
        model="claude-opus-5",
        max_tokens=16000,
        output_config={"effort": "high"},
        messages=[
            {
                "role": "user",
                "content": f"Topic: {topic}\n\nResearch Data:\n{research_data}\n\nWrite a polished markdown report."
            }
        ]
    )
    return response.content[0].text

# Execute the workflow
topic = "Scaling WebSockets in Distributed Systems"
raw_research = run_researcher(topic)
final_report = run_synthesizer(topic, raw_research)

print("\n--- FINAL OUTPUT ---\n")
print(final_report[:500] + "\n[...truncated for display...]")
Enter fullscreen mode Exit fullscreen mode

Note: Notice how we omit parameters like temperature, top_p, and top_k. Modern API standards require us to steer behavior purely through prompt engineering and output_config={"effort": "..."}.


Structured JSON Outputs for Inter-Agent Communication

When agents pass data to each other, brittle string parsing will break your pipeline. Instead of letting agents reply in free-form text, enforce strict JSON schemas using output_config={"format": {...}}.

Here is how you can ensure an evaluator agent always returns a parseable evaluation object before passing it downstream:

import json
import anthropic

client = anthropic.Anthropic()

eval_schema = {
    "type": "object",
    "properties": {
        "score": {"type": "integer", "description": "Score from 1 to 10"},
        "approved": {"type": "boolean"},
        "feedback": {"type": "string"}
    },
    "required": ["score", "approved", "feedback"]
}

def evaluate_code(code_snippet: str) -> dict:
    response = client.messages.create(
        model="claude-haiku-4-5",
        max_tokens=4000,
        output_config={"format": {"type": "json_schema", "schema": eval_schema}},
        messages=[
            {
                "role": "user",
                "content": f"Review this code for security vulnerabilities:\n\n{code_snippet}"
            }
        ]
    )

    # Guaranteed to match the JSON schema
    return json.loads(response.content[0].text)

result = evaluate_code("eval(user_input)")
print(result)
Enter fullscreen mode Exit fullscreen mode

Major Pitfalls to Avoid

  1. Infinite Agent Loops: If Agent A reviews Agent B's work, and Agent B fixes Agent A's feedback, they can get stuck looping forever. Always set a hard step limit (e.g., maximum 3 iterations) in your orchestration code.
  2. Over-Engineering: Don't use five agents when one well-prompted call to claude-opus-5 will do. Every handoff introduces latency, token costs, and a new point of failure.
  3. Ignoring Context Windows: While models like claude-opus-5 boast massive 1M token contexts, passing bloated conversation histories between multiple workers degrades reasoning focus. Summarize intermediate outputs before handing them off.

Summary Checklist

  • Model Selection: Use claude-sonnet-5 for high-volume worker tasks, claude-opus-5 for deep reasoning/synthesis, and claude-haiku-4-5 for fast routing or validation.
  • Control Depth: Use output_config={"effort": "low"|"medium"|"high"} instead of legacy parameters.
  • Enforce Schemas: Use native JSON formatting configs rather than relying on regex to parse agent conversations.

What multi-agent patterns are you building? Drop a comment below or share your experiences!

Top comments (0)