Agents and the loop

An agent is a model, a set of tools and some instructions, run as a loop: the model either answers or asks for tool calls; the tools run; their results go back to the model; and so on until the model answers without calling a tool. Read this page to know what the engine does on your behalf, so that results, pauses and errors make sense.

The loop, step by step#

Inside, every agent is a small workflow with three nodes, run by the same engine as your own workflows:

            ┌───────────────── tool results ─────────────────┐
            ▼                                                 │
 input ─► model ──(tool calls)──► review ──(approved)──► tools
            │                     (only if a called tool
            │                      needs approval)
            └──(answer, no tool calls)──► end

One turn of the loop:

  1. Before the model call. Long-term memory is recalled, redaction and the before_model hooks run, and the limits are checked.
  2. The model call. The transcript, the instructions and the tool schemas go to the provider. Transient errors (rate limits, overload, 5xx, timeouts) are retried with backoff, then fallback models are tried (Errors and retries).
  3. The reply. If it has no tool calls, the run ends: result.text is the answer. If it has tool calls, the loop goes on.
  4. Review. If any called tool is in approve, the run pauses before any tool of this turn runs, and returns the waiting calls (needs_approval). It continues when someone decides (Human approval).
  5. The tools. The engine checks each call's arguments against the tool's schema, then runs the calls concurrently (up to 8 at a time), each with a time limit (60 s by default). Results go back to the model in the order the model asked for them, whatever order they finished in.
  6. Back to step 1.

Every step is a step of the engine: with storage, the state is saved after each one and after each tool call, so a crash loses at most the call in progress.

What a tool call can come back as#

The model always gets a result for every call it made, so the transcript stays valid for every provider:

What happenedWhat the model seesStatus
The tool returned a valueThe value (JSON or text)success
Arguments did not match the schemaThe problems, e.g. amount: requirederror (invalid_arguments)
The model named a tool that does not existThe names of the tools it haserror (unknown_tool)
The tool raised ToolErrorYour messageerror (tool_error)
The tool raised anything elseThe exception's type and message (configurable)error (tool_failed)
The tool took too longA timeouterror
A reviewer rejected the callThe reviewer's reasonrejected

The model then decides what to do: correct its arguments, try another tool, ask the user, or explain. A failing tool does not stop the run unless you ask for that (on_tool_exception="fail"); see Tools.

How a run ends#

Endingstop_reason / stopReasonYou get
The model answered without calling a toolfinal_answerresult.text
Structured output was acceptedoutputresult.output (checked against your schema)
A hook ended the runhook_endthe hook's output
The provider's safety filter refusedcontent_filterno answer text
A tool needs approval(the run is paused)needs_approval and approvals
A limit was reached(an error)LimitExceededError, code = "limit_exceeded"
Anything failed for good(an error)an error with a stable code

Limits#

Every agent has limits; there is no way to run without them. When one is reached the run fails with limit_exceeded, and details["limit"] names it.

LimitDefaultSet with (Python / TypeScript)
Model calls per run (retries of one call do not count)25limits={"model_calls": n} / limits: { modelCalls: n }
Tool calls per run200"tool_calls" / toolCalls
Tokens (input plus output) per run, checked before each model callnone"tokens" / tokens
Time per tool call60 s"tool_timeout_secs" / toolTimeoutSecs
Tool calls taken from one reply (the rest are skipped)32fixed in the SDKs
Tool calls running at once8fixed in the SDKs
Invalid structured answers before output_invalid2fixed in the SDKs
Depth of agents calling agents (as_tool)8fixed in the SDKs
Characters of one tool result (longer ones are cut)100,000fixed in the SDKs

"Per run" means per message: run and send start the counts at zero; approve, reject, decide and retry continue the same run, and its counts. The next message of a thread starts again at zero.

Runs and threads#

  • agent.run(input) is one run with no history. Use it for one-off tasks.
  • agent.thread(id).send(input) adds a turn to a stored conversation. The model sees everything said before on that thread. Use it for chats, tickets, cases, anything a person comes back to.

Both use the same loop. A thread adds saving, one-run-at-a-time, pauses that wait in the database, retry, history and rewind; see State, storage and threads.

What the agent remembers, and where#

WhatKept forWhere
The transcript of one runThe runresult.messages
The transcript of a conversationUntil you delete the thread or retention removes itThe thread, in storage
Facts about a personUntil you forget them or they expireLong-term memory, in storage
Your documentsUntil you remove themA collection, in storage

Next#