docs/concepts/agent-loop.md
An agentic loop is the full “real” run of an agent: intake → context assembly → model inference → tool execution → streaming replies → persistence. It’s the authoritative path that turns a message into actions and a final reply, while keeping session state consistent.
In OpenClaw, a loop is a single, serialized run per session that emits lifecycle and stream events as the model thinks, calls tools, and streams output. This doc explains how that authentic loop is wired end-to-end.
agent and agent.wait.agent command.agent RPC validates params, resolves session (sessionKey/sessionId), persists session metadata, returns { runId, acceptedAt } immediately.agentCommand runs the agent:
runEmbeddedPiAgent (pi-agent-core runtime)runEmbeddedPiAgent:
subscribeEmbeddedPiSession bridges pi-agent-core events to OpenClaw agent stream:
stream: "tool"stream: "assistant"stream: "lifecycle" (phase: "start" | "end" | "error")agent.wait uses waitForAgentRun:
runId{ status: ok|error|timeout, startedAt, endedAt, error? }session.writeLock.acquireTimeoutMs
before reporting the session as busy; the default is 60000 ms.allowReentrant: true.SessionManager is opened and prepared before streaming. Any
later transcript rewrite, compaction, or truncation path must take the same lock before opening or
mutating the transcript file.OpenClaw has two hook systems:
agent:bootstrap: runs while building bootstrap files before the system prompt is finalized.
Use this to add/remove bootstrap context files./new, /reset, /stop, and other command events (see Hooks doc).See Hooks for setup and examples.
These run inside the agent loop or gateway pipeline:
before_model_resolve: runs pre-session (no messages) to deterministically override provider/model before model resolution.before_prompt_build: runs after session load (with messages) to inject prependContext, systemPrompt, prependSystemContext, or appendSystemContext before prompt submission. Use prependContext for per-turn dynamic text and system-context fields for stable guidance that should sit in system prompt space.before_agent_start: legacy compatibility hook that may run in either phase; prefer the explicit hooks above.before_agent_reply: runs after inline actions and before the LLM call, letting a plugin claim the turn and return a synthetic reply or silence the turn entirely.agent_end: inspect the final message list and run metadata after completion.before_compaction / after_compaction: observe or annotate compaction cycles.before_tool_call / after_tool_call: intercept tool params/results.before_install: inspect built-in scan findings and optionally block skill or plugin installs.tool_result_persist: synchronously transform tool results before they are written to an OpenClaw-owned session transcript.message_received / message_sending / message_sent: inbound + outbound message hooks.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 stops lower-priority handlers.before_tool_call: { block: false } is a no-op and does not clear a prior block.before_install: { block: true } is terminal and stops lower-priority handlers.before_install: { block: false } is a no-op and does not clear a prior block.message_sending: { cancel: true } is terminal and stops lower-priority handlers.message_sending: { cancel: false } is a no-op and does not clear a prior cancel.See Plugin hooks for the hook API and registration details.
Harnesses may adapt these hooks differently. The Codex app-server harness keeps OpenClaw plugin hooks as the compatibility contract for documented mirrored surfaces, while Codex native hooks remain a separate lower-level Codex mechanism.
assistant events.text_end or message_end.tool stream.NO_REPLY / no_reply is filtered from outgoing
payloads.compaction stream events and can trigger a retry.lifecycle: emitted by subscribeEmbeddedPiSession (and as a fallback by agentCommand)assistant: streamed deltas from pi-agent-coretool: streamed tool events from pi-agent-coredelta messages.final is emitted on lifecycle end/error.agent.wait default: 30s (just the wait). timeoutMs param overrides.agents.defaults.timeoutSeconds default 172800s (48 hours); enforced in runEmbeddedPiAgent abort timer.timeoutSeconds is owned by cron. The scheduler starts that timer when execution begins, aborts the underlying run at the configured deadline, then runs bounded cleanup before recording the timeout so a stale child session cannot keep the lane stuck.diagnostics.stuckSessionWarnMs classifies long processing sessions that have no observed reply, tool, status, block, or ACP progress. Active embedded runs, model calls, and tool calls report as session.long_running; active work with no recent progress reports as session.stalled; session.stuck is reserved for stale session bookkeeping with no active work. Stale session bookkeeping releases the affected session lane immediately; stalled embedded runs are abort-drained only after an extended no-progress window (at least 10 minutes and 5x the warning threshold) so queued work can resume without cutting off merely slow runs. Repeated session.stuck diagnostics back off while the session remains unchanged.models.providers.<id>.timeoutSeconds extends this idle watchdog for slow local/self-hosted providers; otherwise OpenClaw uses agents.defaults.timeoutSeconds when configured, capped at 120s by default. Cron-triggered runs with no explicit model or agent timeout disable the idle watchdog and rely on the cron outer timeout.models.providers.<id>.timeoutSeconds applies to that provider's model HTTP fetches, including connect, headers, body, SDK request timeout, total guarded-fetch abort handling, and model stream idle watchdog. Use this for slow local/self-hosted providers such as Ollama before raising the whole agent runtime timeout.agent.wait timeout (wait-only, does not stop agent)