Agent Loop Lifecycle: Streams, Wait Semantics, and Run Sequence
This page explains the agent loop lifecycle, including entry points, run sequence, and wait semantics. It is intended for developers integrating with the agent RPC and CLI.
Read this when
- You need an exact walkthrough of the agent loop or lifecycle events
- You are changing session queueing, transcript writes, or session write lock behavior
The agent loop is a serialized, per session execution that transforms a message into actions and a response. It handles intake, context assembly, model inference, tool execution, streaming, and persistence.
Entry points
- Gateway RPC:
agentandagent.wait. - CLI:
openclaw agent.
Run sequence
- The
agentRPC validates parameters, resolves the session (sessionKey/sessionId), persists session metadata, and returns{ runId, acceptedAt }right away. agentCommandexecutes the turn: it resolves the model and default settings for thinking, verbose, and trace modes, loads the skills snapshot, invokesrunEmbeddedAgent, and sends a fallback lifecycle end/error if the embedded loop did not already emit one.runEmbeddedAgentserializes runs through per session and global queues, resolves the model and auth profile, builds the OpenClaw session, subscribes to runtime events, streams assistant and tool deltas, enforces the run timeout (aborting when it expires), and returns payloads along with usage metadata. For Codex app server turns, it also aborts an accepted turn that stops producing app server progress before a terminal event occurs.subscribeEmbeddedAgentSessionconnects runtime events to theagentstream: tool events go tostream: "tool", assistant deltas go tostream: "assistant", and lifecycle events go tostream: "lifecycle"(phase: "start" | "end" | "error").agent.wait(waitForAgentRun) waits for lifecycle end/error on arunIdand 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, which prevents tool and session races. Messaging channels select a queue mode (steer, followup, collect, or interrupt) that feeds into this lane system. See Command Queue.
Transcript writes are also protected by a session write lock on the session file. This lock is process aware and file based, so it catches writers that skip the in process queue or originate from another process. Writers wait up to 60 seconds by default (env override OPENCLAW_SESSION_WRITE_LOCK_ACQUIRE_TIMEOUT_MS) before reporting the session as busy.
Session write locks are non reentrant by default. A helper that intentionally nests acquisition of the same lock while keeping one logical writer must opt in using allowReentrant: true.
Session and workspace preparation
- The 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 the environment and prompt.
- Bootstrap and context files are resolved and injected into the system prompt.
- A session write lock is acquired and the session transcript target is prepared before streaming starts. Any later transcript rewrite, compaction, or truncation path must take the same lock before modifying the SQLite transcript rows.
Prompt assembly
The system prompt is built from OpenClaw's base prompt, the 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 provides two hook systems:
- Internal hooks (Gateway hooks): event driven scripts for commands and lifecycle events.
- Plugin hooks: extension points inside the agent and 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:
| Hook | Runs |
|---|---|
before_model_resolve | Before a session starts (no messages exists), used to deterministically override the provider or model prior to resolution. |
before_prompt_build | After the session loads (with messages present), to insert prependContext, systemPrompt, prependSystemContext, or appendSystemContext before submission. For dynamic per-turn text, use prependContext; the system-context fields are for stable guidance that belongs in system prompt space. |
before_agent_reply | Fires after inline actions but before the LLM call. Allows a plugin to take over the turn, returning a synthetic reply or suppressing it entirely. |
agent_end | Runs after completion, with the final message list and run metadata available. |
before_compaction / after_compaction | Observe or annotate compaction cycles. |
before_tool_call / after_tool_call | Intercept tool parameters or results. |
before_install | Fires after operator install policy runs, on staged skill/plugin install material, when plugin hooks are loaded in the current process. |
tool_result_persist | Synchronously transforms tool results before they are written to an OpenClaw-owned session transcript. |
message_received / message_sending / message_sent | Hooks for inbound and outbound messages. |
session_start / session_end | Session lifecycle boundaries. |
gateway_start / gateway_stop | Gateway lifecycle events. |
Hook decision rules for outbound/tool guards:
before_tool_call:{ block: true }is terminal and halts lower-priority handlers.{ block: false }is a no-op and does not clear a prior block.before_install: the same terminal/no-op semantics apply. For operator-owned install allow/block decisions that must cover CLI install and update paths, usesecurity.installPolicy, notbefore_install.message_sending:{ cancel: true }is terminal and stops lower-priority handlers.{ cancel: false }is a no-op and does not clear a prior cancel.
Refer to Plugin hooks for the hook API and registration details.
Harnesses may adapt these hooks. The Codex app-server harness uses OpenClaw plugin hooks as the compatibility contract for documented mirrored surfaces; Codex native hooks are a separate, lower-level Codex mechanism.
Streaming
- Assistant deltas stream from the agent runtime as
assistantevents. - Block streaming can emit partial replies on
text_endormessage_end. - Reasoning streaming may be a separate stream or block replies.
- See Streaming for chunking and block reply behavior.
Tool execution
- Tool start/update/end events emit on the
toolstream. - Tool results are sanitized for size and image payloads before logging or emitting.
- Messaging tool sends are tracked to suppress 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 silent token
NO_REPLYis filtered from outgoing payloads. - Messaging tool duplicates 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 emits compaction stream events and can trigger a retry. On retry, in-memory buffers and tool summaries reset to avoid duplicate output. See Compaction.
Event streams
lifecycle: emitted bysubscribeEmbeddedAgentSession(and as a fallback byagentCommand).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
| Timeout | Default | Notes |
|---|---|---|
agent.wait | 30s | Applies to waiting only; the timeoutMs parameter can override it. The underlying run is not halted. |
Agent runtime (agents.defaults.timeoutSeconds) | 172800s (48h) | Enforced through runEmbeddedAgent's abort mechanism. Set 0 to allow an unlimited run duration; provider-level stream liveness checks still remain active. |
| CLI backend no-output watchdog | computed per fresh/resumed CLI run | Independent of the agent runtime and managed by the registered backend plugin. A background process within the CLI shares the parent subprocess and terminates before any overall agent timeout. |
| Cron isolated agent turn | owned by cron | When execution begins, the scheduler starts its own timer, cancels the run at the configured deadline, performs bounded cleanup, and then records the timeout so a lingering child session cannot block the lane. |
| Model idle timeout | Cloud 120s; self-hosted 300s | OpenClaw cancels a model request if no response chunks arrive within the idle window. models.providers.<id>.timeoutSeconds lengthens this idle watchdog for slow local or self-hosted providers, but remains constrained 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 or agent timeout use the same default; with an explicit cron run timeout, cloud model stream pauses are capped at 60s so configured model fallbacks can still execute before the outer cron deadline. Cron-triggered runs on truly local endpoints (loopback or private baseUrl) retain the local idle opt-out; self-hosted providers on network baseUrls get the 300s implicit watchdog. With an explicit cron run timeout, local or self-hosted pauses are capped at that timeout. Set models.providers.<id>.timeoutSeconds for slow local providers. |
| Provider HTTP request timeout | models.providers.<id>.timeoutSeconds | Encompasses connect, headers, body, SDK request timeout, guarded-fetch abort handling, and the provider's model stream idle watchdog. Use for slow local or self-hosted providers (for example Ollama) before raising the whole agent runtime timeout; keep the agent or runtime timeout at least as high when the model request needs a longer duration. |
Stuck session diagnostics
When diagnostics are enabled, a built-in two-minute threshold classifies long processing sessions that show no reply, tool, status, block, or ACP progress:
- Active embedded runs, model calls, and tool calls report as
session.long_running. Owned silent model calls remainsession.long_runninguntil the abort threshold so slow or non-streaming providers are not flagged as stalled prematurely. - Active work without recent progress reports as
session.stalled. Owned model calls switch tosession.stalledat or after the abort threshold; ownerless stale model or tool activity is not hidden as long-running. session.stuckis reserved for recoverable stale session bookkeeping, including idle queued sessions with stale ownerless model or tool activity.
The abort threshold is at least 5 minutes and 3 times 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 interrupting merely slow runs. Recovery emits structured requested or 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.waittimeout (wait-only, does not stop the agent)
Related
- 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