The pattern catalog

Before the end-to-end builds, one reference: every agentic pattern in one place, each with a diagram, a one-line "when," and a pointer to where the book uses it. The topology chapter covered multi-agent shapes; this catalog is broader, it includes the single-agent reasoning patterns, the reliability patterns, and the retrieval patterns too, so that the four capstone builds that follow can say "this uses reflection plus map-reduce plus a verifier panel" and you can look each up. Think of it as the vocabulary of agentic design; the builds are sentences in it.

Lab 14.1 implements the seven core control-flow patterns as runnable skeletons; the catalog below is the full set, families and all.

Family 1: single-agent reasoning

How one agent structures its own thinking.

ReAct (reason + act). The base loop: the model reasons, acts (calls a tool), observes the result, and repeats. Every agent in this book is ReAct at bottom (Chapter 7).

  reason -> act(tool) -> observe -> reason -> ... -> answer

Reflection (self-critique). Generate a draft, critique it, revise. Cheap quality lift for a single agent; the seed of verification.

  draft -> critique -> revise -> (repeat) -> output

Plan-and-execute. Plan the whole sequence first, then execute the steps. More token-efficient than re-deciding each step; good when the shape is knowable up front (the workflow end of the spectrum).

  plan[s1,s2,s3] -> exec s1 -> exec s2 -> exec s3 -> done

Evaluator-optimizer. Generate, score against a bar, and if it falls short, retry with the score as feedback, looping until good enough. The single-agent cousin of the verification mesh.

  generate -> score --below bar--> generate(+feedback) -> score --ok--> accept

Family 2: multi-agent orchestration

How several agents divide work. These are the topologies and the three idioms.

Orchestrator-workers (map-reduce). Split the task into independent units, run workers in parallel, merge. The workhorse; Capstones A, C, D.

              orchestrator
             /     |      \
        worker  worker  worker
             \     |      /
                merge

Pipeline (crew). Role-specialists in sequence, each consuming the last one's output. Capstone C's extract-then-judge.

  extract -> judge -> route -> ...

Handoff. An agent transfers control to a specialist it selects. Capstone B's triage.

  triage --hands off--> specialist -> answer

Debate / judge panel (ensemble-vote). N agents attempt or critique the same item; a majority or judge decides. The verification mesh (Chapter 26); Capstones A and D.

  finder --> [refuter, refuter, refuter] --majority--> keep / kill

Hierarchical delegation. Workers spawn sub-workers, recursively, under the parent's budget. For dynamically-decomposing problems (Chapter 22).

Blackboard. Agents react to a shared workspace rather than a coordinator. For coordination data (dedup indexes, claim registries), not control (Chapter 27).

Family 3: retrieval and knowledge

How an agent brings in information.

RAG (retrieve-augment-generate). Retrieve chunks, stuff them into the prompt, answer. The chat-shaped one-shot (Chapter 34).

Agentic retrieval. Retrieval as an iterating tool: search, reformulate, search again, switch between vector and keyword per sub-question. The agent-shaped one (Chapter 34).

  search -> (weak) -> reformulate -> search -> grep(exact) -> read -> answer

Memory-augmented. Consult a durable memory of past runs before acting; write learnings after (Chapter 35).

Family 4: reliability and control

How an agent stays safe, bounded, and correct.

Guardrail. A filter on text crossing a boundary, at the tool boundary for injected content (Chapter 13, Chapter 37).

Human-in-the-loop (approval gate). A durable pause for a human decision on a consequential action (Chapter 38); Capstone B.

  ...propose effect -> [PARK for human] --approve--> execute
                                         --deny-----> revise

Budget-bounded (admission control). Admit work only if the budget covers it; land as a clean partial when exhausted (Chapter 23); Capstone D's per-request budget.

Checkpoint-resume. Persist progress so a crash resumes the failed slice, not the whole run (Chapter 25).

Router. Classify the request and dispatch to the right handler, model, or agent. A cost lever (small model routes, big model works) and an organizing pattern.

  request -> classify -> {perf | data | security} handler

The lab

Seven of these run as deterministic skeletons, so the shapes are concrete before any framework:

python3 patterns.py
ReAct                answer: latency rose ~13x (from 2 reason/act steps)
Reflection           draft -> critique('too vague: name the mechanism and the evidence') -> revised: v841 shrank the payments-db pool 50->10 at 01:55; logs show pool exhaustion at 02:03; p99 rose 14x. Roll back v841.
Plan-and-execute     plan=pull metrics -> list deploys -> search logs -> conclude | executed 4
Router               routed 'checkout latency spike' -> perf -> PERF handler: check latency + deploys
Evaluator-optimizer  accepted after 3 tries (score 0.4, score 0.7, score 0.95): v841 shrank the payments-db pool 50->10;...
Map-reduce           mapped 6 shards -> 6 findings -> 1 merged report
Ensemble-vote        votes=['v841', 'v841', 'network', 'v841', 'v840'] -> majority 'v841' (3/5)

The closing line is the catalog's real lesson: patterns compose. A production system is rarely one pattern; it is several nested. The capstones that follow are each a specific composition, and the "patterns used" box in each names them, so you can read a real build as an assembly of catalog entries.

Don't be confused: a pattern vs a framework. A pattern is a shape of control flow (ReAct, map-reduce, reflection); a framework is a library that makes some patterns easy to express (LangGraph makes graphs easy, CrewAI makes crews easy). You choose patterns by the problem and a framework by which patterns it expresses well, and the same pattern runs in any framework or none, which is why this catalog is framework-neutral and the idioms lab showed three of them in plain Python. Learn the patterns; the frameworks are how you type them faster.

Full source

"""Lab 14.1: the agentic pattern catalog, runnable.

Every agentic system is built from a small set of recurring patterns.
This lab implements the core ones as tiny, deterministic skeletons over
a trivial model stand-in, so the SHAPE of each is visible and runnable
before you dress it in a framework:

  1. ReAct            reason -> act -> observe, loop until done
  2. Reflection       draft -> self-critique -> revise
  3. Plan-and-execute plan the steps, then run each
  4. Router           classify the request, dispatch to a specialist
  5. Evaluator-optimizer  generate -> score -> retry with feedback until good
  6. Map-reduce       fan out over items, then merge
  7. Ensemble-vote    N attempts, majority wins

The real systems add a real model, real tools, and a framework; the
control-flow idea is exactly what you see here. Standard library only.
Deterministic.
"""

from __future__ import annotations

from collections import Counter


# --- 1. ReAct: reason, act, observe, loop -----------------------------------

def react(task: str) -> str:
    tools = {"search": lambda q: f"[3 hits for {q!r}]",
             "calc": lambda e: str(eval(e, {"__builtins__": {}}))}
    scratch, steps = [], [("search", "checkout errors"), ("calc", "2930/210")]
    for name, arg in steps:                       # a real model picks these
        obs = tools[name](arg)
        scratch.append(f"thought: need {name}; act: {name}({arg}); obs: {obs}")
    return f"answer: latency rose ~{tools['calc']('2930//210')}x " \
           f"(from {len(scratch)} reason/act steps)"


# --- 2. Reflection: draft, critique, revise ---------------------------------

def reflect(task: str) -> str:
    draft = "v841 caused it."                      # thin first pass
    critique = "too vague: name the mechanism and the evidence"
    revised = ("v841 shrank the payments-db pool 50->10 at 01:55; logs show "
               "pool exhaustion at 02:03; p99 rose 14x. Roll back v841.")
    return f"draft -> critique({critique!r}) -> revised: {revised}"


# --- 3. Plan-and-execute: plan then run each step ---------------------------

def plan_execute(task: str) -> str:
    plan = ["pull metrics", "list deploys", "search logs", "conclude"]
    done = [f"[{i+1}/{len(plan)}] {s}: ok" for i, s in enumerate(plan)]
    return "plan=" + " -> ".join(plan) + " | executed " + str(len(done))


# --- 4. Router: classify, then dispatch to a specialist ---------------------

def route(request: str) -> str:
    def perf(r): return "PERF handler: check latency + deploys"
    def data(r): return "DATA handler: check schema + freshness"
    def sec(r):  return "SEC handler: check auth + injection"
    label = ("perf" if any(w in request for w in ("latency", "slow", "spike"))
             else "sec" if "auth" in request or "attack" in request
             else "data")
    return f"routed {request!r} -> {label} -> " + \
           {"perf": perf, "data": data, "sec": sec}[label](request)


# --- 5. Evaluator-optimizer: generate, score, retry with feedback -----------

def evaluator_optimizer(task: str, bar: float = 0.9) -> str:
    attempts = [("v841 broke it", 0.4),
                ("v841 shrank the db pool", 0.7),
                ("v841 shrank the payments-db pool 50->10; logs confirm; "
                 "p99 14x; roll back", 0.95)]      # each retry uses feedback
    trace = []
    for text, score in attempts:
        trace.append(f"score {score}")
        if score >= bar:
            return f"accepted after {len(trace)} tries ({', '.join(trace)}): " \
                   f"{text[:40]}..."
    return "gave up (max tries)"


# --- 6. Map-reduce: fan out over items, then merge --------------------------

def map_reduce(items: list[str]) -> str:
    mapped = [(it, len(it) % 3) for it in items]   # a per-item finding count
    total = sum(n for _, n in mapped)
    return f"mapped {len(items)} shards -> {total} findings -> 1 merged report"


# --- 7. Ensemble-vote: N attempts, majority wins ----------------------------

def ensemble(task: str) -> str:
    votes = ["v841", "v841", "network", "v841", "v840"]   # 5 finders
    winner, n = Counter(votes).most_common(1)[0]
    return f"votes={votes} -> majority {winner!r} ({n}/{len(votes)})"


if __name__ == "__main__":
    task = "why did checkout p99 spike overnight?"
    demos = [
        ("ReAct               ", react(task)),
        ("Reflection          ", reflect(task)),
        ("Plan-and-execute    ", plan_execute(task)),
        ("Router              ", route("checkout latency spike")),
        ("Evaluator-optimizer ", evaluator_optimizer(task)),
        ("Map-reduce          ", map_reduce([f"shard-{i}" for i in range(6)])),
        ("Ensemble-vote       ", ensemble(task)),
    ]
    for name, out in demos:
        print(f"{name} {out}")
    print("\nseven patterns, one primitive: a step that transforms state. "
          "Real agentic systems compose these (a swarm is map-reduce + "
          "ensemble-vote; a research agent is plan-execute + reflection + "
          "evaluator-optimizer) over a real model and real tools.")

👉 Next: Capstone A, built end to end, the repository-audit fleet from project kickoff through validation and troubleshooting, on Bedrock with an open-source framework and every pattern this catalog just named.