2026-07-30T06:42:02.033Z

AI SDK Agent Loop: Prove Why It Stopped

Audit Vercel AI SDK loop termination with explicit stop causes, bounded execution, routed approval waits, and independent outcome receipts.

An AI SDK agent loop is not healthy merely because it returned. A return proves that one control flow path ended: the model finished without another tool call, a tool could not execute, approval was required, or a configured stop condition fired. None of those facts proves that the invoice was created, the ticket was updated, or the report reached its destination. The practical default is to keep the SDK's bounded loop, record one explicit stop cause, and verify the intended external outcome separately. Treat an approval request as waiting, a step or budget cap as a bounded stop, and a natural finish without an outcome receipt as false completion. This guide pins the behavior to the released [email protected] package and Node.js 22.23.1. The accompanying content free replay exercised the actual exported stop condition functions across eleven operational cases. All eleven classifications and four direct SDK assertions passed. The SDK can stop for several legitimate reasons The AI SDK loop control guide names four termination routes: 1. the model returns a finish reason other than tool calls ; 2. a called tool has no execute function; 3. a tool call needs approval; 4. a configured stop condition returns true. Those routes should not collapse into one completed: true field. They imply different operator actions. A natural non tool finish can be perfectly valid for a research answer, yet incomplete for a workflow whose contract requires a file in object storage. A tool without execute can deliberately act as a structured done signal, but the signal contains what the model claimed—not independent evidence that a side effect succeeded. An approval request is an intentional pause. A step cap means the safety boundary worked, not that the task failed or succeeded. The current documentation says ToolLoopAgent defaults to isStepCount(20) . Replacing it with isLoopFinished() removes that step count stop condition. That can be reasonable for a tightly controlled local experiment, but it also removes a simple bound on model calls and cost. If the application cannot explain its independent deadline, budget, and cancellation controls, retaining the default cap is the safer decision. Three small implementation details change the diagnosis The version pinned stop condition source is short enough to audit directly: isStepCount(n) is true when steps.length === n , not when the count is greater than or equal to n . hasToolCall(name) inspects tool calls in the most recent completed step. isLoopFinished() always returns false as a stop condition, leaving natural termination, an unexecuted tool, or approval to end the loop. These semantics matter when reconstructing an incident. Suppose an application persists only the final text and total step count. A three step run that called done on its second step cannot later prove that hasToolCall("done") caused termination, because the relevant condition checks the last step. Likewise, an observation of 21 steps does not show that isStepCount(20) fired; it is evidence that the configured policy, recorded count, or run boundary differs from the assumption. Keep the condition inputs and the selected policy version with the run. Do not infer them from a dashboard after the fact. Build a stop receipt before choosing a health state A useful receipt is small. It does not need prompts, model responses, or raw tool payloads: The stopCause should come from the integration boundary, not a guess based on final prose. Record whether the run reached a non tool finish, matched a named stop condition, emitted an approval request, invoked an unexecuted completion tool, was aborted, timed out, or failed in tool execution. Then apply a precedence rule: Evidence State Operator decision Tool execution failed FAILED Diagnose the tool boundary; do not retry an uncertain side effect blindly. Approval is pending with owner, deadline, and resume token WAITING Route the decision and preserve resumability. Approval is pending without routing data WAITING UNROUTED Add an owner and escalation path before the wait becomes invisible. Timeout expired without useful progress STUCK Inspect the last durable progress and choose one bounded recovery. Step, token, or user abort bound fired BOUNDED STOP Preserve partial work; decide whether a new bounded run is justified. Natural finish or explicit done plus outcome receipt VERIFIED COMPLETE Close the run. Natural finish or explicit done without outcome receipt FALSE COMPLETE Verify the destination or reopen the task. Signals disagree or the cause was not recorded UNCERTAIN Ask before acting. Order matters. A timeout on the fourth step is still a timeout even if the step count happens to equal four. An approval request should remain waiting rather than being swept into a generic incomplete state. A natural finish with no deliverable should remain false complete even when its text sounds confident. Replay the policy without calling a model The audit imported the released isStepCount , hasToolCall , and isLoopFinished functions. It passed content free arrays of completed step records and joined their output to the receipt classifier. No model call, prompt, secret, or external tool effect was needed. Four assertions established the SDK boundary: The eleven case fixture then covered natural finish with and without an outcome, done with and without an outcome, step cap, token budget, routed and unrouted approval, tool error, timeout, and user abort. The revealing pairs were not exotic failures. Both natural finish fixtures had identical control flow causes; only the one carrying a destination receipt became VERIFIED COMPLETE . The same split appeared for the done tool. That is the core falsifiable result: if loop termination alone proved useful completion, those paired fixtures should have received the same healthy verdict. They did not. Verify the destination after control flow stops The ToolLoopAgent reference exposes completed steps on the generated result and accepts abortSignal and timeout controls. Those fields are useful evidence, but the application still owns the definition of success. Choose the cheapest deterministic check that answers the user's actual request: for a file, verify the expected path or object key, content type, size floor, and a task specific hash or schema; for a database mutation, read the destination record and compare the intended fields; for a message, retain the provider receipt and destination identity; for a deployment, verify the immutable version, public health response, and the user facing route; for an analysis, validate required sections, source coverage, and machine readable output before accepting prose. Do not make the outcome receipt a second copy of the model's assertion. {"status":"done"} emitted by the same loop is not independent verification. The receipt should come from the destination, a deterministic validator, or a human decision when the outcome cannot be checked safely by code. Approval also needs a separate boundary. The SDK documentation shows that an approval request can be collected, appended to the conversation as an approval response, and passed into a subsequent call. Operationally, that means the wait must retain enough context to resume the same decision. An owner without a resume token creates manual reconstruction work; a token without an owner creates an invisible queue. What this replay does not prove The experiment did not invoke a provider model, stream partial output, or execute an external tool. It therefore does not establish provider specific finish reason behavior, network cancellation timing, or side effect idempotency. Those belong in integration tests around the actual model, tools, and destination. It also does not recommend one universal step or token limit. A four step lookup and a forty step migration have different envelopes. The operational requirement is that the selected bounds are explicit, recorded, and tied to a safe action when reached. The rule is narrower and more durable: preserve why the loop stopped, keep legitimate waits distinct from failures, and require destination evidence before declaring useful completion. Sidewisp is an AI agent health platform intended to make evidence, waiting states, bounded recovery, and outcome verification easier to inspect across existing runtimes. Production agent health collection and AI SDK monitoring are not generally shipped today. Sidewisp is currently in private preview. If this termination receipt matches a failure mode in your own agents, the private preview waitlist is the appropriate place to share the runtime and evidence boundary you need.