stop_reason Reference Table
Only stop_reason controls the loop. Content type, content text, and phrase-matching ("task complete") are never reliable signals - Claude can emit explanatory text in the same response as a tool_use block.
| stop_reason | What it means | Correct loop action |
|---|---|---|
end_turn | Genuinely finished | Stop the loop, surface the result. The only value that means "done." |
tool_use | Model wants one or more tools executed | Execute every tool_use block, append one tool_result per tool_use_id, continue |
max_tokens | Cut off by your max_tokens setting | Truncated, not finished - don't display it; retry with more room |
model_context_window_exceeded | Cut off by the conversation filling the context window | Same as max_tokens: don't display as final; address context growth (see 3.4) |
stop_sequence | Hit a custom stop sequence you configured | Intentional, controlled stop - not an error, not the same as the model deciding it's done |
pause_turn | Long-running turn with certain server-side tools | Resubmit the conversation unchanged to continue - not an error, not completion |
refusal | Model declined on an otherwise normal response | Surface distinctly (human review, fallback path) - never retry blindly or show as a normal answer |
Agentic Loop Anti-Patterns
- Checking
response.content[0].type === "text"(or any content-presence check) to decide the loop is done - text andtool_usecan share one response. - Using a fixed iteration cap as the primary stop signal instead of a safety net.
stop_reasondecides; the cap only guards against a runaway loop and should never fire on a normal task. - Treating every non-
tool_usestop_reasonas equivalent toend_turn- ships truncated, paused, or refused responses as if they were finished. - Appending only one
tool_resultwhen a response contained multipletool_useblocks from a parallel tool call - breaks the next API call. - Forgetting that both the assistant's raw
content(with itstool_useblocks) and the user message with matchingtool_resultblocks must be appended before the next call.
Orchestration Pattern Selector
Default to single-agent. Reach for multi-agent only when you can name a concrete isolation or parallelism benefit that outweighs the coordination overhead and the roughly-multiplied token cost (N subagents ≈ N× the context each has to re-derive).
| Signal | Pattern | Why |
|---|---|---|
| One well-scoped task, right tools available | Single agent | Cheapest, simplest, no coordination overhead - the default |
| Heterogeneous subtasks (search, then analyse, then synthesise) | Coordinator-subagent | Different tools/context/jobs per subagent; coordinator integrates |
| Uniform, independent, parallelisable subtasks (same check, many files) | Hub-and-spoke, spawned in parallel | Throughput win; no specialisation needed |
| Independent subtasks run one at a time across sequential turns | Anti-pattern - parallelise instead | Sequential invocation of independent work adds latency for no benefit |
Tracing Multi-Agent Failures
Check the coordinator first. A subagent that executed correctly on what it was given still produces a bad-looking final result if the coordinator's decomposition or context-passing was the actual fault.
| Symptom | Likely root cause | Fix |
|---|---|---|
| Whole subtopics missing from the merged output | Coordinator's decomposition never named them | Fix the decomposition to enumerate full topic breadth before delegating - not "add more subagents" |
| A subagent can't cite sources / cite prior findings | Coordinator stripped metadata or didn't pass prior results | Pass findings in full, with attribution, explicitly into the subagent's prompt |
| A subagent references "the previous file's issues" it was never shown | Isolation violated - nothing inherited automatically | Pass the actual prior content explicitly as part of the prompt |
| One subagent's output is factually wrong on its assigned, well-scoped task | Genuine subagent failure | The only case where fixing the subagent itself is the right move |
Decomposition Strategy Cheat Sheet
| Situation | Strategy | Notes |
|---|---|---|
| Step order knowable upfront | Fixed sequential pipeline | Simple to build/test - each step's prompt & output are stable and checkable in isolation |
| Next step depends on what was just found (unfamiliar codebase, open research) | Dynamic adaptive decomposition | This is what an agentic loop already gives you - each tool choice is a decomposition decision |
| Several distinct objectives bundled in one prompt (summarise + extract + translate + flag) | Decompose into sequential single-objective steps | Fixes attention dilution - rewording the bundled prompt does not |
| A single coherent objective split at an arbitrary boundary (sentence 1, then sentence 2) | Don't split further | Over-decomposition: adds latency/cost/lost context with no attention-dilution problem to justify it |
Granularity rule of thumb: one step = one checkable objective - something verifiable as done correctly or not from just that step's output. Always add a lightweight verification checkpoint between pipeline steps (schema check, count check, completeness pass) - an unchecked step just trusts bad upstream output and passes the error downstream.
Session Memory: Three Options
| Option | When to use | Trap it's not |
|---|---|---|
| Full carry-forward (keep appending) | Task is genuinely continuing | Not for unrelated follow-up topics - pays token cost and risks distraction from stale context |
| Fresh session, nothing carried | New request unrelated to what came before | Still isolated like a subagent - nothing recalled unless explicitly reinjected |
| Compact summary into a fresh session | Some continuity matters (what was tried, what's open) but the raw transcript doesn't need to survive | Not the same as truncation - a summary is a judgment call about what matters, not a cut by position |
- Prune, don't hoard: once a tool result has been read and acted on, replace its content with a placeholder - raw JSON dumps, log excerpts, and file contents are usually the single largest driver of history size.
- Pruning ≠ truncation. Pruning targets specific superseded content by judgment; truncating the last N messages cuts blindly and can remove a still-load-bearing early instruction.
- Platform equivalents: context editing (auto-clears aging tool interactions) and the memory tool (persists durable facts outside the conversation) do the same job as hand-rolled pruning/summarising.
- Prompt caching lowers the cost of resending an unchanged prefix - it does not make an ever-growing prefix free, and does nothing about the model reading past stale content to find what's current.
Gate or No Gate? Decision Table
The question is never "is the model usually reliable enough?" It's whether the consequence of one single failure justifies a deterministic guarantee over a probabilistic one.
| Consequence of one failure | Control | Example |
|---|---|---|
| Real money, legal/regulatory exposure, or irreversible action | Code-level deterministic gate | Refund threshold requiring human sign-off; AML check before a transfer |
| Formatting preference, style guideline, occasional deviation doesn't matter | Prompt-based guidance is fine | "Prefer bullet points for lists" - a gate here is unneeded overhead |
- Distractor to reject: rewording the prompt more emphatically, or upgrading model tier, as the fix for a hard compliance rule - both stay probabilistic.
- Distractor to reject: lowering the dollar threshold - shrinks exposure but doesn't fix the underlying reliability gap just above the new line.
Pre- vs Post-Execution Checks
| Check timing | Can it prevent the action? | Legitimate job |
|---|---|---|
Pre-execution (e.g. PreToolUse hook) | Yes - blocks or redirects before the tool runs | Enforcement of a hard rule; the only kind that counts as prevention |
Post-execution (e.g. PostToolUse hook) | No - the action already happened | Normalisation (consistent date/status/currency shape) and audit/detection (logging, flag for review) |
- A blocked call must still return a
tool_result(withis_errorand a clear reason) for itstool_use_id- silently dropping it invalidates the next API call and leaves the model with no way to react sensibly. - Exam trap: proposing a post-execution flag/log/review-queue as "the fix" for a requirement that must never be violated - that's detection dressed up as prevention.
- A prerequisite-style gate (e.g. block
transfer_fundsunlessaml_checkalready passed this session) needs session-scoped state, not just a per-call input check.