Skip to main content

Agent run lifecycle

Every AI agent processes work as a run: one inbound message handled end to end, one API turn you requested through POST /api/v1/agents/:id/chat, invoke, or chat-stream, or one task accepted from an A2A peer. Each run carries a status that advances as the runtime executes it, and it closes in exactly one of three terminal states. This page explains that state machine so you can build dashboards, retries, and approval flows against it. This is the agent counterpart to Voice call lifecycle and Delivery lifecycle. The per-event payloads for streaming runs live in the webhook events reference; this page stays at the concept level.

What a run is

A run is the unit of execution between two turns of a conversation:
  • A conversation turn — one inbound message arrives (chat, SMS, WhatsApp, voice transcript) and the agent thinks, calls tools, and replies. That is one run, regardless of how many tool calls happened inside it.
  • An A2A task — when a peer tasks your agent over the A2A protocol, or your agent tasks a remote peer, the task itself follows the same lifecycle on both ends: working → completed | failed | cancelled, with input_required while it waits on a caller.
Runs within one conversation share a budget context — the per-conversation cost counter and token aggregate accumulate across runs — but each run has its own status and its own terminal outcome.

The state machine

A successful run moves through: queued → working → completed
Note: The internal status event names (started, thinking, done) you see on a streaming response are progress signals emitted while the run is in working. They are not distinct run states, and done simply marks the end of the working stream — the record advances to completed at that moment.

Interrupting transitions

Three classes of actor can push a live run out of the happy path:
  • Guardrails (run → failed). The per-run and per-conversation caps terminate a run with a specific error code — see Cost controls for the full code list: COST_LIMIT, CONVERSATION_COST_CAP_REACHED, API_CALL_LIMIT, TOKEN_LIMIT, TOOL_ITERATION_LIMIT. A cost_limit failure on run N does not reset the conversation counter — the next run against the same conversation_id probes the same accumulated cost.
  • Approval gate (run → input_requiredworking). A tool marked confirmation: "always" holds the run in a pending-approval state while a human decides. Approving re-queues the deferred turn and the run resumes in working; rejecting ends that attempt with a blocked result, and the run continues to its next turn or fails. See Human-in-the-loop oversight.
  • Handoff (conversation-level pause). A human take-over pauses the agent at the conversation level — the runtime checks agent_active before every turn and skips while a human holds it. This is a conversation state, not a run status: the run that triggered the escalation has already closed, and no new run starts until control returns. The same applies to AI→AI handoff through Handoff targets, where the receiving agent starts its own fresh run.

Context-window behavior per run

Each run reads the conversation history, the agent’s system prompt, any retrieved knowledge, and the current user message into a bounded context window:
  • The run’s token usage accumulates against the conversation’s max_tokens_per_conversation aggregate. When a run would push the conversation past that aggregate it terminates with TOKEN_LIMIT — the run fails, not just the reply truncation.
  • The runtime caps the message history it loads per run (the window is bounded even when the stored history is long), so a long-lived conversation does not inflate every turn linearly. Older context is summarized or dropped from the window; new messages always win.
  • tool_loop_limit bounds how many tool-call iterations one run may perform — a run that keeps calling tools past the limit terminates with TOOL_ITERATION_LIMIT instead of looping indefinitely.
  • Supervision whispers sent on a live conversation are picked up at the start of the next run — they enter the context window as a steering note, not as a user message.

Observability: status, events, and error codes

Use the status field and the error codes to drive your integration — never parse display text: Two pitfalls to avoid:
  1. Do not poll for a terminal agent.run.aborted-style webhook — none exists. The terminal signal is the run status itself plus the final error event’s code.
  2. Do not treat input_required as failure. It is a parked, resumable state. Alerting on it as an error floods your on-call with approvals that will resolve themselves when a human clicks approve.
  • Handoff targets — the allowlist an agent needs before it can route a conversation to another agent (each handoff starts a fresh incoming run on the target).
  • Human-in-the-loop oversight — the whisper/approve/take-over surface that parks and resumes runs.
  • Cost controls — the three ceilings that turn a run from working to failed.
  • Agent versions — versions freeze the prompt/tools a run executes against; a run never mixes configuration across versions.
  • A2A federation — the A2A task lifecycle echoes these states on both your side and the peer’s.