Skip to content

Agentic Design Patterns

Working, tested LangGraph implementations of the six agentic design patterns from Anthropic's "Building Effective Agents" — five fixed workflows and one open-ended agent loop — plus a seventh, human-in-the-loop. Each pattern is a small, self-contained LangGraph StateGraph you can read start to finish in a couple of minutes, run from the CLI, and poke at in tests.

No API key required

Every pattern runs and is unit-tested offline, against a small deterministic fake chat model. Point it at a real model with one environment variable when you want to — see Running against a real model below.

Quick start View on GitHub

Patterns

  • Prompt Chaining


    Linear chain + a gate — decomposing a task into sequential LLM calls with a programmatic checkpoint between them.

    prompt_chaining

  • Routing


    Classify → dispatch — sending different inputs to specialized prompts instead of one generic one.

    routing

  • Parallelization


    Fan-out → fan-in — running independent LLM calls concurrently and joining the results.

    parallelization

  • Orchestrator-Workers


    Dynamic fan-out via Send — when the number of parallel subtasks is decided by the model at runtime.

    orchestrator_workers

  • Evaluator-Optimizer


    Generate ⇄ evaluate loop — iterative refinement against explicit pass/fail criteria.

    evaluator_optimizer

  • ReAct Agent


    Tool-call loop — the open-ended "agent": the model decides whether to act or answer, turn by turn.

    react_agent

  • Human-in-the-Loop


    Agent loop + approval gate — pausing before a side-effecting tool call until a human approves or denies it.

    human_in_the_loop

Start with prompt_chaining (simplest) and end with human_in_the_loop (built on react_agent, least predictable plus a human in the mix). Each pattern's own page has a Mermaid diagram of its graph plus notes on where it fits, where it doesn't, architectural tradeoffs, production infra choices, production readiness, and relevant open-source components.

Beyond the patterns

  • Architecture Overview


    The cross-pattern view: a decision tree for picking between the seven patterns and a latency/cost/risk comparison table.

    Read more

  • Harnesses and Loops


    How these patterns map onto real agent harnesses — Claude Code included — plus memory, guardrails, MCP tools, and evals.

    Read more

  • Real-World Examples


    The decision tree applied to concrete scenarios, including a couple of cases where the tempting first choice is wrong.

    Read more

  • Under deepagents


    The same patterns re-expressed in LangChain's deepagents — what a framework supplies for free and what it costs you. Now lives in its own repo, ai-harnesses.

    Read more

Quick start

Requires Python 3.13+ and uv.

uv sync
uv run main.py list
uv run main.py run react-agent "What is (12 + 8) * 3?"
uv run main.py run prompt-chaining "Why LangGraph is a good fit for agents"

run prints a narrated trace — how many LLM calls, in what shape, which branch ran, how many loop turns — from each pattern's run.py::render:

orchestrator-workers · orchestrator picks the subtask count at runtime → dynamic fan-out via Send
────────────────────────────────────────────────────────────
  1. orchestrator (LLM)  → planned 3 subtasks: pricing; onboarding; support quality
  2. fan-out via Send    → 3 worker runs in parallel (each sees only its own subtask)
  3. synthesizer (LLM)  → final_report  (worker results merged via the operator.add reducer)

Add --raw for the untouched state dict, or --otel for the same run as an OpenTelemetry GenAI span tree (uv sync --group otel first) — see docs/observability.md.

Or activate the venv and use python directly:

source .venv/bin/activate
python main.py run evaluator-optimizer "Write a tagline for Nimbus"
python -m patterns.orchestrator_workers.run "Write a report covering pricing, onboarding, and support quality."
python main.py run human-in-the-loop "Send a message to Alice: the report is ready."

Running against a real model

By default every pattern uses shared/llm/fake.py's FakeChatModel — a scripted stand-in that lets the whole repo run offline. To use a real model instead, set an API key and, optionally, LLM_PROVIDER:

export ANTHROPIC_API_KEY=sk-ant-...   # or OPENAI_API_KEY=sk-...
export LLM_PROVIDER=anthropic         # or "openai"; auto-detected from the key if omitted
uv run main.py run react-agent "What's a good LangGraph pattern for a support bot?"

See shared/llm/factory.py for provider selection and ANTHROPIC_MODEL / OPENAI_MODEL overrides.

Testing

uv run pytest            # every pattern, all offline
uv run pytest patterns/orchestrator_workers -v

Project layout

agentic-design-patterns/
├── main.py                        # Typer CLI: list/run patterns
├── docs/
│   ├── architecture-overview.md   # cross-pattern decision tree + comparison table
│   ├── harnesses-and-loops.md     # pointer to the essay, now in the ai-harnesses repo
│   ├── observability.md           # the narrated trace vs. the --otel span tree
│   └── real-world-examples.md     # the decision tree applied to concrete scenarios
├── patterns/
│   ├── prompt_chaining/
│   ├── routing/
│   ├── parallelization/
│   ├── orchestrator_workers/
│   ├── evaluator_optimizer/
│   ├── react_agent/
│   └── human_in_the_loop/
│       ├── graph.py                # StateGraph wiring: nodes + edges
│       ├── nodes.py                # node functions + the fake-LLM script for this pattern
│       ├── run.py                  # `main()` + CLI entry point
│       ├── tests/test_*.py         # deterministic end-to-end tests
│       └── README.md               # pattern-specific diagram + notes
└── shared/
    ├── llm/
    │   ├── factory.py               # get_chat_model(provider=...) — fake / openai / anthropic
    │   └── fake.py                  # FakeChatModel: scripted BaseChatModel, no network needed
    ├── render.py                    # formatting helpers for each run.py's narrated trace
    ├── obs.py                       # opt-in OpenTelemetry wiring for `run ... --otel`
    └── tools/
        ├── basic.py                 # calculator, search_docs, word_count, send_message
        └── mcp_server.py            # those same tools served over MCP, for any harness

Every pattern follows the same shape: graph.py defines the StateGraph and its edges, nodes.py holds the node functions (each backed by shared.llm.factory.get_chat_model), run.py exposes a main(...) the CLI calls into plus a render(result) that narrates the run, and tests/ exercises the graph end to end against the fake LLM.

Adding a new pattern

  1. mkdir -p patterns/<name>/tests with __init__.py files.
  2. Write nodes.py: node functions plus a _fake_responder that gives deterministic behavior for tests (see any existing pattern for the shape), wired up via get_chat_model(responder=_fake_responder).
  3. Write graph.py: a TypedDict state and a build_graph() that wires nodes with add_edge / add_conditional_edges.
  4. Write run.py with a main(...) function, a render(result) -> str that narrates the pattern's mechanic (use shared.render helpers), and a CLI-runnable if __name__ == "__main__" block that prints render(main(...)).
  5. Add tests in tests/test_<name>.py and register the pattern in main.py's PATTERNS dict.

Resources

License

This project is licensed under the MIT License.