ANTHROPIC / TECHNICAL EXPLAINER

Anthropic CCAR-F: Claude Agent SDK Loops, Stop Reasons, and Hook Boundaries

How Claude Agent SDK tool loops, stop reasons, hooks, bounded retries and safe execution boundaries fit together.

In this article

An agent can look competent in a demonstration while still failing at the boundary between model reasoning and application control. The difference is often a detail the user never sees: whether a tool request is allowed to execute, whether the agent recognizes that a response is incomplete, or whether a failed step can safely be repeated.

For systems built around Claude, the agent loop needs a clear owner outside the model. The model may propose a next step, but application code decides what tools exist, which credentials they receive, and when the workflow is complete.

This distinction matters when choosing between the Claude Messages API and the higher-level Claude Agent SDK. With the Messages API, the application ordinarily drives tool-execution turns explicitly. A managed agent runtime can take on much of that loop management, but it does not remove the need to control permissions, state, costs, and failure recovery.

Architects should understand both levels because operational failures often cross their boundary. In either runtime, trace each model request to the actual tool invocation, its authorization decision, returned stop reason and any durable side effect. A response that sounds complete is not evidence that a requested operation executed—or that an external change was committed safely.

Start with an explicit state machine

A dependable agent loop is not simply “call Claude until it gives an answer.” It has states such as receiving a request, collecting authorized evidence, proposing a tool call, validating that call, executing it, returning its result, asking Claude to interpret the result, and recording a final answer. Transitions should be observable, with the actor responsible for each transition identified. In particular, execution must never follow from the mere appearance of a persuasive instruction in retrieved text.

Suppose a support agent is asked to investigate an account problem. Its read-only tools can retrieve account status and recent support events. A separate privileged operation may create a service adjustment. The agent is free to propose the adjustment, but the application should move into a pending-approval state rather than immediately making the change. When the user approves, the application resumes from verified state and executes the bounded operation. The model’s recommendation is input to the transition, not its authorization.

Define explicit stop conditions before writing the loop: successful task completion, a need for clarification, denied permission, unavailable data, exceeded cost or step budget, and a recoverable infrastructure problem. Without these conditions, an agent can keep “trying one more thing” after all useful options are exhausted. A budget should constrain model turns, tool invocations, elapsed time, and repeated identical actions. A total-token allowance alone cannot prevent expensive or destructive tool loops.

Read stop reasons before interpreting response text

At the Messages API level, Claude exposes a stop_reason indicating why generation paused or ended. The meaning changes the next application action. An end_turn generally signals that the assistant finished the turn.

A tool_use means that a client tool request needs handling: inspect the requested tool and arguments, apply application policy, execute the approved operation, and return a corresponding tool result before expecting the model to continue. Do not treat the text alongside a tool request as a final response merely because it sounds complete.

A max_tokens stop indicates output truncation, not successful task completion. The handler may need to raise the output allowance or continue generation with the existing conversation state. A stop_sequence means an application-configured sequence triggered, which may be intentional or may have cut off necessary content.

A pause_turn can appear when an Anthropic-managed server-side tool loop pauses; the application should follow the documented continuation pattern rather than interpret that condition as a denial. Refusals and context-window exhaustion require their own treatment, and production code should account for changes to the documented set of reasons over time.

Consider a tool-use turn containing a proposed charge adjustment. If the application checks only for a friendly natural-language response, it may tell the user that the adjustment occurred before the billing API ran. A state-driven handler records the request as proposed, validates and executes it under an authorized identity, and checks the outcome. Anthropic’s stop reasons describe how a model turn ended; they do not prove a downstream billing transaction committed. The application must confirm the authoritative result before making that claim.

Separate model decisions from tool execution

An LLM-generated tool argument is untrusted application input. Validation must occur at the tool boundary, even when the tool declaration includes a JSON schema. Schema validation answers whether a field has an allowed shape; authorization answers whether the requester may use that field to affect the named resource. Both are necessary. An account ID can be syntactically valid but belong to a different customer; a dollar amount can be a number while exceeding an approved adjustment limit.

Build tool endpoints around narrow business capabilities instead of general-purpose commands. get_invoice_status or propose_ticket_update is easier to authorize than an unrestricted SQL or shell executor. Resolve user authority from trusted session context, not from a model-supplied user ID. A mutating tool should support an idempotency key so retries after timeouts do not create duplicate records. Deny unknown arguments, validate resource ownership, and enforce state transitions in the target service.

A model may chain read-only inspection with a proposed modification, but the change from observation to mutation is a trust-boundary crossing. In architectures with Model Context Protocol integrations, MCP tool contracts should preserve exactly the same separation. Standardized discovery is valuable for integration, yet a discovered tool is not automatically an approved tool for every user and every agent.

Use hooks to enforce invariants outside model reasoning

Hooks and lifecycle callbacks are useful at predictable points in the agent’s work. They can inspect planned actions, apply policy, collect logs, or block a prohibited operation before it reaches an external system. The key design question is what the hook can guarantee.

A hook that merely asks the model to reconsider a dangerous command has a weaker guarantee than one that rejects the command through deterministic application rules. For critical constraints, prefer controls that do not depend on the model’s willingness to follow another instruction.

For a repository assistant, a pre-execution policy might restrict writes to a designated workspace, deny network access to unknown endpoints, and require approval before modifying deployment configuration. A post-execution event can record the changed files and test results. Teams should keep these policies in reviewed configuration, with changes subject to the same scrutiny as other production code. If a hook fails or times out, a sensitive action should not silently proceed as though the check succeeded.

Different Claude products expose different hook, tool, and permission surfaces. Claude Code repository hooks, Claude Agent SDK lifecycle events, and application-owned middleware are related patterns, not interchangeable APIs. Verify the exact event contract and enforcement semantics in the current product documentation before choosing an implementation. The team’s Claude Code configuration also needs to preserve which checks are project rules and which apply only to one developer’s local environment.

Make retry and timeout behavior explicit

A tool returning an error does not necessarily mean the operation had no effect. The network might have timed out after the target service committed a change. Blindly retrying the entire agent step can duplicate an order, ticket, email, or deployment. Use stable operation identifiers and design recovery around the distinction between an operation not attempted, attempted with a confirmed failure, committed successfully, and attempted with an unknown outcome.

For read-only operations, a bounded retry with backoff may be acceptable when failures are transient. For writes, the application should first query the operation’s recorded state or send the same idempotency key. Retrying a model response is not equivalent to retrying a database operation: the model might choose different arguments on the next turn. Persist the authorized proposal separately from the model transcript so recovery can use the same intended action.

Do not expose arbitrary backend errors as authoritative model instructions. Return structured tool results with a stable error category, safe diagnostic details, and a retryability decision. A denied-permission result should tell the agent that approval or a different task path is required, not encourage it to discover a more privileged tool. A temporary service outage may permit a limited retry; missing required customer evidence should trigger a clarification or escalation instead. Failures are part of the workflow design, not rare exceptional prose.

Observe the loop as a transaction with evidence

A final response alone is insufficient evidence that an agent acted correctly. Record the request identifier, selected tool, application-resolved principal, validated parameters, authorization decision, tool outcome, retries, model turn count, and any human approval. Keep sensitive prompt content out of logs unless a clear retention and access policy justifies storing it. Link events into one trace so a reviewer can reconstruct the path from user intent to final action.

Useful evaluation scenarios include an ambiguous request, a denied tool operation, malicious instructions inside a retrieved document, a partial tool failure, a model turn stopped by an output limit, and a timeout after a write may have committed. The expected behavior should be specified before running the test. A system passes because it makes the correct state transition, not because its final wording is plausible. This connects agent-loop testing with the broader question of recoverable workflow state.

Metrics should distinguish useful task completion from activity. Fewer tool calls are not always better if they conceal an incomplete investigation; more reasoning turns are not better if they repeat the same evidence. Track completed authorized tasks, avoidable retries, denied actions, human escalations, latency, and cost by task type. Changes to a model, tool description, or orchestration library can alter these distributions, so keep representative regression cases in the release process.

Review the architecture through a real support incident

Imagine that a customer asks why a service credit was not applied. The agent retrieves the account’s billing state, checks the relevant policy, and identifies a discrepancy. A strong architecture records the evidence and proposes a bounded correction. If the policy requires a supervisor, the workflow enters an approval state. The system then verifies the supervisor’s identity, checks that the account has not changed since the proposal, and attempts the credit with a stable idempotency key.

Now inject failure. The billing service times out after receiving the command. The agent should not generate a second, slightly different credit request simply because it did not receive a success message. The application queries the operation ID, confirms whether the credit exists, and resumes from durable state. If it cannot determine the result, it escalates without claiming that the adjustment occurred. If the user withdraws approval, the pending action expires instead of remaining runnable indefinitely.

The incident is resolved only when the application can show four separate facts: the model requested a tool, the host authorized and executed it, the returned result was checked against the case record, and the workflow either committed or safely refused the next step. Stop reasons, hooks and retries help maintain that separation, but none makes an unauthorized mutation acceptable. A useful recovery test deliberately interrupts the run between tool execution and acknowledgement to check that resumption cannot repeat the write.