CCAR-F Claude Certified Architect — Foundations

Cheatsheet 4 — Agentic Patterns

Domain 1 (27%) — the largest domain. Plus the Domain 4 output patterns.


The agentic loop

messages = [{"role": "user", "content": user_input}]

while True:
    response = client.messages.create(model="claude-opus-5", tools=TOOLS,
                                      messages=messages, max_tokens=4096)

    if response.stop_reason != "tool_use":       # "end_turn" → done
        break

    messages.append({"role": "assistant", "content": response.content})  # verbatim

    results = [
        {"type": "tool_result", "tool_use_id": b.id, "content": execute(b)}
        for b in response.content if b.type == "tool_use"
    ]
    messages.append({"role": "user", "content": results})   # ALL results, ONE message

Four facts

  1. stop_reason == "tool_use" means continue. "end_turn" means stop. That is the termination condition.
  2. Append the assistant turn verbatim — every block, unmodified.
  3. All tool_result blocks for one assistant turn go in ONE user message, each keyed by its tool_use_id.
  4. The loop is driven by the API's structured signal, never by text interpretation.

🚫 The four loop anti-patterns

Anti-pattern Why it breaks
Parsing natural-language termination signals ("I'm done") Phrasing varies; false positives and false negatives
An arbitrary iteration cap as the primary stop condition Truncates legitimate work; a cap is a safety net, not a termination condition
Checking whether the assistant text is non-empty Text and tool calls co-occur in one turn
Terminating on the first tool result Most real work needs several turns

Model-driven vs pre-configured

Use
Model-driven decisions Inputs vary; the path can't be enumerated; judgement required
Pre-configured decision tree Path is fixed, auditable, and must be identical every time

Support triage → model-driven. Compliance-mandated approval sequence → pre-configured (or a hook, below).


Coordinator ↔ subagent (hub-and-spoke)

                   ┌──────────────┐
          ┌────────│ COORDINATOR  │────────┐
          │        └──────┬───────┘        │
          ▼               ▼                ▼
   ┌────────────┐  ┌────────────┐  ┌────────────┐
   │ web-search │  │ doc-analysis│  │ synthesis  │
   └────────────┘  └────────────┘  └────────────┘
   isolated ctx     isolated ctx     isolated ctx

Two structural rules:

  1. All inter-subagent communication goes through the coordinator. Subagents do not talk to each other.
  2. Subagents have isolated context and inherit nothing. Not the conversation, not prior findings, not the original question.

Consequence: every delegating prompt must carry, literally, every identifier, path, constraint, and prior finding the subagent needs. "The company we discussed" means nothing to it.

Delegation mechanics

  • Delegate with the Task tool. The coordinator's allowedTools must include "Task" — omit it and the coordinator silently does the work itself.
  • Parallel subagents = multiple Task calls in a single coordinator response. There is no parallel: true flag.
  • AgentDefinition carries three things: a description (used by the coordinator for routing), a prompt, and scoped tools.

The four decomposition patterns

Pattern What it fixes
Scope partitioning Duplicated work — give each subagent a disjoint scope and tell it what others cover
Dynamic subagent selection Rigid pipelines — choose subagents based on what the task actually needs
Iterative refinement Gaps — check coverage and re-delegate for what's missing
Goal-oriented prompts Brittleness — state the goal and quality criteria, not procedural steps

🎯 Narrow decomposition is the signature Domain 1 bug. Symptom: the coordinator's subtasks all cluster in one or two dimensions of a multi-dimensional question. Fix: explicit scope partitioning across named dimensions + a coverage check that re-delegates. Blame the coordinator's decomposition, not the subagents that worked correctly within the scope they were given.


Enforcement beats guidance

Deterministic (hooks, gates, code) Probabilistic (prompt instructions)
Runs every time Non-zero failure rate
Correct choice when errors are financial, compliance, or irreversible Fine for style and preference
def pre_tool_use(tool_name, tool_input, state):
    if tool_name == "process_refund" and not state.get("verified_customer_id"):
        return {"block": True, "reason": "Call get_customer first and confirm a single match."}
    if tool_name == "process_refund" and tool_input["amount"] > 500:
        return {"block": True, "reason": "Refunds over $500 require escalate_to_human."}
    return {"block": False}
Hook Use for
PreToolUse Block or redirect a call — prerequisite gates, limits, permissions
PostToolUse Transform a result — normalize heterogeneous formats, trim verbose payloads

The canonical PostToolUse case: one tool returns Unix epoch timestamps, another ISO 8601, another numeric status codes. Normalize them all before they reach the model rather than explaining the formats in the prompt.


Structured handoffs

When escalating to a human or handing off between agents, pass a structured summary, not the transcript:

customer_id · root_cause · refund_amount · recommended_action

Same principle at all three boundaries: agent→human, agent→agent, tool→model. Structured, minimal, complete.


Attention dilution

Symptom: a review or refactor across many files misses issues it would catch in one file.

Fix: per-file passes + a separate cross-file integration pass.

  • Per-file passes catch local issues dilution would hide.
  • The integration pass catches what per-file passes structurally cannot — a changed signature and its unchanged callers.

🚫 A larger context window does not fix attention dilution. It's an attention-distribution problem, not a capacity problem.


Prompt chaining vs dynamic decomposition

Use
Prompt chaining Fixed sequence of known steps
Dynamic adaptive decomposition The steps depend on what earlier steps discover

Independent review

Ranked, as the guide ranks it:

  1. A separate instance with fresh context
  2. ❌ The generating instance reviewing its own work
  3. ❌ Extended thinking on the generating instance

Independence, not depth. More reasoning inside a contaminated context doesn't help.


Session state

Situation Mechanism
Continuing, history valid --resume <session-name>
Branching to try a variant fork_session
Old tool results now stale Fresh session + structured summary
Context filling, same session /compact