* refactor: extract agent core package Introduce packages/agent-core as the OpenClaw-owned home for reusable agent loop, harness, session, prompt, and runtime dependency contracts. * refactor: extract shared llm runtime Move provider model registries, stream wrappers, OAuth helpers, and LLM utilities into src/llm with plugin-sdk barrels instead of depending on the old embedded runtime layout. * refactor: remove pi runtime internals Rename remaining Pi-shaped agent surfaces to OpenClaw agent runtime names, delete obsolete Pi docs and package graph checks, and add the third-party notice for incorporated code. * refactor: tighten agent session runtime Make agent-core/runtime dependencies explicit, consolidate compaction and session transcript helpers, and move model/session helpers behind OpenClaw-owned contracts. * refactor: remove static model and pi auth paths Drop static model catalogs and Pi auth bridges, move model/provider facts to manifest-owned runtime contracts, and harden internal embedded-agent utilities. * refactor: remove legacy provider compat paths * docs: remove agent parity notes * fix: skip provider wildcard metadata parsing * refactor: share session extension sdk loading * refactor: inline acpx proxy error formatter * refactor: fold edit recovery into edit tool * fix: accept extension batch separator * test: align startup provider plugin expectations * fix: restore provider-scoped release discovery * test: align static asset packaging expectations * fix: run static provider catalogs during scoped discovery * fix: add provider entry catalogs for scoped live discovery * fix: load lightweight provider catalog entries * fix: refresh provider-scoped plugin metadata * fix: keep provider catalog entries on release live path * fix: keep static manifest models in release live checks * fix: harden release model discovery * fix: reduce OpenAI live cache probe reasoning * fix: disable OpenAI cache probe reasoning * ci: extend OpenAI gateway live timeout * fix: extend live gateway model budget * fix: stabilize release validation regressions * fix: honor provider aliases in model rows * fix: stabilize release validation lanes * fix: stabilize release memory qa * ci: stabilize release validation lanes * ci: prefer ipv4 for live docker node calls * fix: restore shared tool-call stream wrapper * ci: remove legacy pi test shard alias * fix: clean up embedded agent test drift * fix: stabilize runtime alias status * fix: clean up embedded agent ci drift * fix: restore release ci invariants * fix: clean up post-rebase runtime drift * fix: restore release ci checks * fix: restore release ci after rebase * fix: remove stale pi runtime path * test: align compaction runtime expectations * test: update plugin prerelease expectations * fix: handle claude live tool approvals * fix: stabilize release validation gates * fix: finish agent runtime import * test: finish post-rebase agent runtime mocks * fix: keep codex compaction native * fix: stabilize codex app-server hook tests * test: isolate codex diagnostic active run * test: remove codex diagnostic completion race # Conflicts: # extensions/codex/src/app-server/run-attempt.test.ts * ci: fix full release manifest performance run id * refactor: narrow llm plugin sdk boundary * chore: drop generated google boundary stamps * fix: repair rebase fallout * fix: clean up rebased runtime references * fix: decode codex jwt payloads as base64url * fix: preserve shipped pi runtime alias * fix: add scoped sdk virtual modules * fix: decode llm codex oauth jwt as base64url * fix: avoid stale vertex adc negative cache * fix: harden tool arg decoding and codeql path * fix: keep vertex adc negative checks live * refactor: consolidate codex jwt and edit helpers * fix: await codex oauth node runtime imports * fix: preserve sdk tool and notice contracts * fix: preserve shipped compat config boundaries * fix: align codex oauth callback host * fix: terminate agent-core loop streams on failure * fix: keep codex oauth callback alive during fallback * ci: include session tools in critical codeql scans * fix: keep Cloudflare Anthropic provider auth header * docs: redirect legacy pi runtime pages * fix: honor bundled web provider compat discovery * fix: protect session output spill files * fix: keep legacy agent dir env blocked * fix: contain auto-discovered skill symlinks * fix: harden agent core sdk proxy surfaces * fix: restore approval reaction sdk compat * fix: keep live docker runs bounded * fix: keep codex oauth redirect host aligned * fix: resolve post-rebase agent runtime drift * fix: redact anthropic oauth parse failures * fix: preserve responses strict tool shaping * fix: repair agent runtime rebase cleanup * docs: redirect retired parity pages * fix: bound auto-discovered resources to roots * fix: repair post-rebase agent test drift * fix: preserve bundled provider allowlist migration * fix: preserve manifest-owned provider aliases * fix: declare photon image dependency * fix: keep provider headers out of proxy body * fix: preserve shipped env aliases * fix: refresh control ui i18n generated state * fix: quote read fallback paths * fix: preview edits through configured backend * test: satisfy core test typecheck * fix: preserve ZAI usage auth fallback * test: repair codex diagnostic test * fix: repair agent runtime rebase drift * test: finish embedded runner import rename * fix: repair agent runtime rebase integrations * test: align compaction oauth fallback expectations * fix: allow sdk-auth session models * fix: update doctor tool schema import * fix: preserve bedrock plugin region * fix: stream harmony-like prose immediately * ci: include session runtime in codeql shards * fix: repair latest rebase integrations * fix: honor explicit codex websocket transport * fix: keep openai-compatible credentials provider-scoped * fix: refresh sdk api baseline after rebase * fix: route cli runtime aliases through openclaw harness * test: rename stale harness mock expectation * test: rename embedded agent overflow calls * test: clean embedded auth test wording * test: use openclaw stream types in deepinfra cache test * fix: refresh sdk api baseline on latest main * fix: honor bundled discovery compat allowlists * fix: refresh sdk api baseline after latest rebase * fix: remove stale rebase imports * test: rename stale model catalog mock * test: mock renamed doctor runtime modules * fix: map canonical kimi env auth * fix: use internal model registry in bench script * fix: migrate deepinfra provider catalog entry * fix: enforce builtin tool suppression * fix: route compaction auth and proxy payloads safely * refactor: prune unused llm registry leftovers * test: update codex hooks session import * test: fix model picker ci coverage * test: align model picker auth mock types
12 KiB
summary, read_when, title
| summary | read_when | title | ||
|---|---|---|---|---|
| Agent loop lifecycle, streams, and wait semantics |
|
Agent loop |
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.
Entry points
- Gateway RPC:
agentandagent.wait. - CLI:
agentcommand.
How it works (high-level)
agentRPC validates params, resolves session (sessionKey/sessionId), persists session metadata, returns{ runId, acceptedAt }immediately.agentCommandruns the agent:- resolves model + thinking/verbose/trace defaults
- loads skills snapshot
- calls
runEmbeddedAgent(OpenClaw agent runtime) - emits lifecycle end/error if the embedded loop does not emit one
runEmbeddedAgent:- serializes runs via per-session + global queues
- resolves model + auth profile and builds the OpenClaw session
- subscribes to runtime events and streams assistant/tool deltas
- enforces timeout -> aborts run if exceeded
- for Codex app-server turns, aborts an accepted turn that stops producing app-server progress before a terminal event
- returns payloads + usage metadata
subscribeEmbeddedAgentSessionbridges agent runtime events to OpenClawagentstream:- tool events =>
stream: "tool" - assistant deltas =>
stream: "assistant" - lifecycle events =>
stream: "lifecycle"(phase: "start" | "end" | "error")
- tool events =>
agent.waituseswaitForAgentRun:- waits for lifecycle end/error for
runId - returns
{ status: ok|error|timeout, startedAt, endedAt, error? }
- waits for lifecycle end/error for
Queueing + concurrency
- Runs are serialized per session key (session lane) and optionally through a global lane.
- This prevents tool/session races and keeps session history consistent.
- Messaging channels can choose queue modes (steer/followup/collect/interrupt) that feed this lane system. See Command Queue.
- Transcript writes are also protected by a session write lock on the session file. The lock is
process-aware and file-based, so it catches writers that bypass the in-process queue or come from
another process. Session transcript writers wait up to
session.writeLock.acquireTimeoutMsbefore reporting the session as busy; the default is60000ms. - Session write locks are non-reentrant by default. If a helper intentionally nests acquisition of
the same lock while preserving one logical writer, it must opt in explicitly with
allowReentrant: true.
Session + 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 report.
- A session write lock is acquired;
SessionManageris 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.
Prompt assembly + system prompt
- 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.
Hook points (where you can intercept)
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 this to add/remove bootstrap context files.- Command hooks:
/new,/reset,/stop, and other command events (see Hooks doc).
See Hooks for setup and examples.
Plugin hooks (agent + gateway lifecycle)
These run inside the agent loop or gateway pipeline:
before_model_resolve: runs pre-session (nomessages) to deterministically override provider/model before model resolution.before_prompt_build: runs after session load (withmessages) to injectprependContext,systemPrompt,prependSystemContext, orappendSystemContextbefore prompt submission. UseprependContextfor 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.
Streaming + partial replies
- Assistant deltas are streamed from the agent runtime and emitted as
assistantevents. - Block streaming can emit partial replies either on
text_endormessage_end. - Reasoning streaming can be emitted as a separate stream or as block replies.
- See Streaming for chunking and block reply behavior.
Tool execution + messaging tools
- Tool start/update/end events are emitted on the
toolstream. - Tool results are sanitized for size and image payloads before logging/emitting.
- Messaging tool sends are tracked to suppress duplicate assistant confirmations.
Reply shaping + suppression
- Final payloads are assembled from:
- assistant text (and optional reasoning)
- inline tool summaries (when verbose + allowed)
- assistant error text when the model errors
- The exact silent token
NO_REPLY/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 + retries
- Auto-compaction emits
compactionstream events and can trigger a retry. - On retry, in-memory buffers and tool summaries are reset to avoid duplicate output.
- See Compaction for the compaction pipeline.
Event streams (today)
lifecycle: emitted bysubscribeEmbeddedAgentSession(and as a fallback byagentCommand)assistant: streamed deltas from the agent runtimetool: streamed tool events from the agent runtime
Chat channel handling
- Assistant deltas are buffered into chat
deltamessages. - A chat
finalis emitted on lifecycle end/error.
Timeouts
agent.waitdefault: 30s (just the wait).timeoutMsparam overrides.- Agent runtime:
agents.defaults.timeoutSecondsdefault 172800s (48 hours); enforced inrunEmbeddedAgentabort timer. - Cron runtime: isolated agent-turn
timeoutSecondsis 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. - Session liveness diagnostics: with diagnostics enabled,
diagnostics.stuckSessionWarnMsclassifies longprocessingsessions that have no observed reply, tool, status, block, or ACP progress. Active embedded runs, model calls, and tool calls report assession.long_running; active work with no recent progress reports assession.stalled;session.stuckis reserved for recoverable stale session bookkeeping, including idle queued sessions with stale ownerless model/tool activity. Stale session bookkeeping releases the affected session lane immediately after recovery gates pass; stalled embedded runs are abort-drained only afterdiagnostics.stuckSessionAbortMs(default: at least 5 minutes and 3x the warning threshold) so queued work can resume without cutting off merely slow runs. Recovery emits structured requested/completed outcomes, and diagnostic state is marked idle only if the same processing generation is still current. Repeatedsession.stuckdiagnostics back off while the session remains unchanged. - Model idle timeout: OpenClaw aborts a model request when no response chunks arrive before the idle window.
models.providers.<id>.timeoutSecondsextends this idle watchdog for slow local/self-hosted providers, but it is still bounded by any loweragents.defaults.timeoutSecondsor run-specific timeout because those control the whole agent run. Otherwise OpenClaw usesagents.defaults.timeoutSecondswhen 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. - Provider HTTP request timeout:
models.providers.<id>.timeoutSecondsapplies 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, and keep the agent/runtime timeout at least as high when the model request needs to run longer.
Where things can end early
- Agent timeout (abort)
- AbortSignal (cancel)
- Gateway disconnect or RPC timeout
agent.waittimeout (wait-only, does not stop 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