Agent Loop Lifecycle, Streams, and Wait Semantics

This page explains the per-session agent loop execution, including entry points, run sequence, and streaming behavior. It is intended for developers integrating or debugging agent runs.

Read this when

  • You need an exact walkthrough of the agent loop or lifecycle events
  • You are changing session queueing, writer claims, or transcript write fencing

The agent loop is the per-session, serialized execution that converts a message into actions and a response: intake, context assembly, model inference, tool execution, streaming, and persistence.

Entry points

  • Gateway RPC: agent and agent.wait.
  • CLI: openclaw agent.

Run sequence

  1. agent RPC validates parameters, resolves the session (sessionKey/sessionId), stores session metadata, and promptly returns { runId, acceptedAt }.
  2. agentCommand handles the turn: resolves model plus thinking/verbose/trace defaults, loads the skills snapshot, invokes runEmbeddedAgent, and issues a fallback lifecycle end/error when the embedded loop has not already done so.
  3. runEmbeddedAgent: serializes runs using per-session and global queues, resolves model and auth profile, creates the OpenClaw session, subscribes to runtime events, streams assistant/tool deltas, enforces the run timeout (aborting at expiry), and returns payloads with usage metadata. For Codex app-server turns, it also aborts an accepted turn that stops producing app-server progress before a terminal event.
  4. subscribeEmbeddedAgentSession bridges runtime events to the agent stream: tool events map to stream: "tool", assistant deltas to stream: "assistant", lifecycle events to stream: "lifecycle" (phase: "start" | "end" | "error").
  5. agent.wait (waitForAgentRun) awaits lifecycle end/error on a runId and returns { status: ok|error|timeout, startedAt, endedAt, error? }.

Queueing and concurrency

Runs are serialized per session key (session lane) and optionally through a global lane, preventing tool/session races. Messaging channels select a queue mode (steer/followup/collect/interrupt) that feeds this lane system; see Command Queue.

Before streaming, an admitted run records its durable activeWriterRunId claim. Every transcript append or rewrite supplies expectedWriterRunId, and the synchronous commit transaction verifies that it still matches the active claim. A superseded run therefore cannot commit stale transcript data. The SQLite writer queue orders per-agent mutations, while the Gateway state-directory lock prevents another Gateway or openclaw agent --local process from owning the same state directory concurrently.

Session and workspace preparation

  • Workspace is resolved and created; sandboxed runs may redirect to a sandbox workspace root.
  • Skills are loaded (or reused from a snapshot) and injected into env and prompt.
  • Bootstrap/context files are resolved and injected into the system prompt.
  • The session transcript target and writer claim are prepared before streaming starts. Later rewrites, compaction, and truncation use the same in-transaction writer-claim fence.

Prompt assembly

System prompt is built from OpenClaw's base prompt, skills prompt, bootstrap context, and per-run overrides. Model-specific limits and compaction reserve tokens are enforced. See System prompt for what the model sees.

Hooks

OpenClaw has two hook systems:

  • Internal hooks (Gateway hooks): event-driven scripts for commands and lifecycle events.
  • Plugin hooks: extension points inside the agent/tool lifecycle and gateway pipeline.

Internal hooks (Gateway hooks)

  • agent:bootstrap: runs while building bootstrap files before the system prompt is finalized. Use it to add or remove bootstrap context files.
  • Command hooks: /new, /reset, /stop, and other command events (see the Hooks doc).

See Hooks for setup and examples.

Plugin hooks

These run inside the agent loop or gateway pipeline:

HookRuns
before_model_resolveBefore the session starts, with no messages available, so the provider/model can be overridden deterministically ahead of resolution.
before_prompt_buildOnce the session is loaded, with messages present, to inject prependContext, systemPrompt, prependSystemContext, or appendSystemContext, or, on runtimes that support it, to restrict the turn-scoped submitted tool surface using toolsAllow. Supplying an empty toolsAllow means no optional tools are submitted; omitting it leaves the host-resolved surface untouched. Runtimes without support reject restrictive values rather than ignoring them.
before_agent_replyAfter inline actions, prior to the LLM call. This lets a plugin take over the turn and either return a synthetic reply or suppress it completely.
agent_endAfter finishing, carrying the final message list and run metadata.
before_compaction / after_compactionUsed to observe or annotate compaction cycles.
before_tool_call / after_tool_callIntercept tool parameters and results.
before_installAfter the operator install policy runs, on staged skill/plugin install material, when plugin hooks are loaded in the current process.
tool_result_persistSynchronously alters tool results before they get written to an OpenClaw-owned session transcript.
message_received / message_sending / message_sentHooks for inbound and outbound messages.
session_start / session_endMark the boundaries of session lifecycle.
gateway_start / gateway_stopGateway lifecycle events.

Hook decision rules for outbound/tool guards:

  • before_tool_call: { block: true } is terminal, halting lower-priority handlers. { block: false } does nothing and won't clear an existing block.
  • before_install: terminal/no-op behavior matches the above. For operator-owned install allow/warn/block decisions that must cover CLI install and update paths, use security.installPolicy rather than before_install.
  • message_sending: { cancel: true } is terminal, stopping lower-priority handlers. { cancel: false } is a no-op and doesn't clear a prior cancel.

Refer to Plugin hooks for the hook API and registration details.

Harnesses are free to adapt these hooks. The Codex app-server harness keeps OpenClaw plugin hooks as the compatibility contract for documented mirrored surfaces; Codex native hooks are a separate, lower-level Codex mechanism.

Streaming

  • Assistant deltas arrive from the agent runtime as assistant events.
  • Partial replies can be emitted during block streaming via text_end or message_end.
  • Reasoning streaming may run as its own stream or as block replies.
  • For chunking and block reply behavior, see Streaming.

Tool execution

  • Tool start/update/end events are emitted on the tool stream.
  • Before logging or emitting, tool results are cleaned up for size and image payloads.
  • Messaging tool sends are tracked to prevent duplicate assistant confirmations.

Reply shaping

Final payloads are built from assistant text (plus optional reasoning), inline tool summaries (when verbose and allowed), and assistant error text when the model errors.

  • The exact silent token NO_REPLY is stripped from outgoing payloads.
  • Duplicates from messaging tools are removed from the final payload list.
  • If no renderable payloads remain and a tool errored, a fallback tool error reply is emitted unless a messaging tool already sent a user-visible reply.

Compaction and retries

Auto-compaction sends compaction stream events and may trigger a retry. On retry, in-memory buffers and tool summaries reset to avoid duplicate output. See Compaction.

Event streams

  • lifecycle: emitted by subscribeEmbeddedAgentSession (and as a fallback by agentCommand).
  • assistant: streamed deltas from the agent runtime.
  • tool: streamed tool events from the agent runtime.

The Gateway projects lifecycle and tool start/terminal events into the bounded, metadata-only audit ledger. This projection records provenance and result codes without copying prompts, messages, tool arguments, tool results, or raw errors out of the transcript/runtime path.

Chat channel handling

Assistant deltas buffer into chat delta messages. A chat final is emitted on lifecycle end/error.

Timeouts

TimeoutDefaultNotes
agent.wait30sApplies to waiting only; timeoutMs overrides this parameter. The underlying run is not halted.
Agent runtime (agents.defaults.timeoutSeconds)172800s (48h)Governed by runEmbeddedAgent's abort timer. Set 0 for a run without a time limit; model stream liveness checks remain active.
CLI backend no-output watchdogcomputed per fresh/resumed CLI runDistinct from the agent runtime and managed by the registered backend plugin. A background task inside the CLI shares the parent subprocess and terminates before any overall agent timeout.
Cron isolated agent turnowned by cronWhen execution starts, the scheduler launches its own timer, cancels the run at the configured limit, then performs bounded cleanup before recording the timeout, preventing a stale child session from blocking the lane.
Model idle timeoutCloud 120s; self-hosted 300sOpenClaw stops a model request if no response chunks arrive within the idle window. models.providers.<id>.timeoutSeconds lengthens this idle watchdog for slow local/self-hosted providers, but remains capped by any lower finite agents.defaults.timeoutSeconds or run-specific timeout, since those control the entire agent run. Unlimited run budgets still apply the provider-class idle watchdog. Cron-triggered cloud model runs without an explicit model/agent timeout use the same default; with an explicit cron run timeout, cloud model stream stalls max out at 60s so configured model fallbacks can execute before the outer cron deadline. Cron-triggered runs on genuinely local endpoints (loopback/private baseUrl) keep the local idle opt-out; self-hosted providers on network baseUrls get the 300s implicit watchdog. With an explicit cron run timeout, local/self-hosted stalls cap at that timeout. Set models.providers.<id>.timeoutSeconds for slow local providers.
Provider HTTP request timeoutmodels.providers.<id>.timeoutSecondsIncludes connect, headers, body, SDK request timeout, guarded-fetch abort handling, and the model stream idle watchdog for that provider. Use for slow local/self-hosted providers (for example Ollama) before raising the whole agent runtime timeout; keep the agent/runtime timeout at least as high when the model request needs to run longer.

Stuck session diagnostics

When diagnostics are on, a built-in two-minute threshold categorizes long processing sessions that show no reply, tool, status, block, or ACP progress:

  • Active embedded runs, model calls, and tool calls are reported as session.long_running. Owned silent model calls stay session.long_running until the abort threshold so slow or non-streaming providers are not marked stalled prematurely.
  • Active work without recent progress is reported as session.stalled. Owned model calls switch to session.stalled at or after the abort threshold; ownerless stale model/tool activity is not hidden as long-running.
  • session.stuck is set aside for recoverable stale session bookkeeping, including idle queued sessions with stale ownerless model/tool activity.

The abort threshold is at least 5 minutes and 3x the warning threshold. Stale session bookkeeping releases the affected session lane immediately after recovery gates pass; stalled embedded runs are abort-drained only after the abort threshold, so queued work resumes without cutting off merely slow runs. Recovery emits structured requested/completed outcomes; diagnostic state is marked idle only if the same processing generation is still current, and repeated session.stuck diagnostics back off while the session stays unchanged.

Where things can end early

  • Agent timeout (abort)
  • AbortSignal (cancel)
  • Gateway disconnect or RPC timeout
  • agent.wait timeout (wait-only, does not stop the agent)
  • Tools - available agent tools
  • Hooks - event-driven scripts triggered by agent lifecycle events
  • Compaction - how long conversations are summarized
  • Exec Approvals - approval gates for shell commands
  • Thinking - thinking/reasoning level configuration
1,736 words · updated Aug 16, 2026