--- summary: "Agent loop lifecycle, streams, and wait semantics" read_when: - You need an exact walkthrough of the agent loop or lifecycle events --- # Agent Loop (Clawdbot) 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 Clawdbot, 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: `agent` and `agent.wait`. - CLI: `agent` command. ## How it works (high-level) 1) `agent` RPC validates params, resolves session (sessionKey/sessionId), persists session metadata, returns `{ runId, acceptedAt }` immediately. 2) `agentCommand` runs the agent: - resolves model + thinking/verbose defaults - loads skills snapshot - calls `runEmbeddedPiAgent` (pi-agent-core runtime) - emits **lifecycle end/error** if the embedded loop does not emit one 3) `runEmbeddedPiAgent`: - serializes runs via per-session + global queues - resolves model + auth profile and builds the pi session - subscribes to pi events and streams assistant/tool deltas - enforces timeout -> aborts run if exceeded - returns payloads + usage metadata 4) `subscribeEmbeddedPiSession` bridges pi-agent-core events to Clawdbot `agent` stream: - tool events => `stream: "tool"` - assistant deltas => `stream: "assistant"` - lifecycle events => `stream: "lifecycle"` (`phase: "start" | "end" | "error"`) 5) `agent.wait` uses `waitForAgentJob`: - waits for **lifecycle end/error** for `runId` - returns `{ status: ok|error|timeout, startedAt, endedAt, error? }` ## 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 (collect/steer/followup) that feed this lane system. See [Command Queue](/concepts/queue). ## 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; `SessionManager` is opened and prepared before streaming. ## Prompt assembly + system prompt - System prompt is built from Clawdbot’s base prompt, skills prompt, bootstrap context, and per-run overrides. - Model-specific limits and compaction reserve tokens are enforced. - See [System prompt](/concepts/system-prompt) for what the model sees. ## Hook points (where you can intercept) Clawdbot 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](/hooks) for setup and examples. ### Plugin hooks (agent + gateway lifecycle) These run inside the agent loop or gateway pipeline: - **`before_agent_start`**: inject context or override system prompt before the run starts. - **`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. - **`message_received` / `message_sending` / `message_sent`**: inbound + outbound message hooks. - **`session_start` / `session_end`**: session lifecycle boundaries. - **`gateway_start` / `gateway_stop`**: gateway lifecycle events. See [Plugins](/plugin#plugin-hooks) for the hook API and registration details. ## Streaming + partial replies - Assistant deltas are streamed from pi-agent-core and emitted as `assistant` events. - Block streaming can emit partial replies either on `text_end` or `message_end`. - Reasoning streaming can be emitted as a separate stream or as block replies. - See [Streaming](/concepts/streaming) for chunking and block reply behavior. ## Tool execution + messaging tools - Tool start/update/end events are emitted on the `tool` stream. - 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 - `NO_REPLY` is treated as a silent token and 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 `compaction` stream events and can trigger a retry. - On retry, in-memory buffers and tool summaries are reset to avoid duplicate output. - See [Compaction](/concepts/compaction) for the compaction pipeline. ## Event streams (today) - `lifecycle`: emitted by `subscribeEmbeddedPiSession` (and as a fallback by `agentCommand`) - `assistant`: streamed deltas from pi-agent-core - `tool`: streamed tool events from pi-agent-core ## Chat channel handling - Assistant deltas are buffered into chat `delta` messages. - A chat `final` is emitted on **lifecycle end/error**. ## Timeouts - `agent.wait` default: 30s (just the wait). `timeoutMs` param overrides. - Agent runtime: `agents.defaults.timeoutSeconds` default 600s; enforced in `runEmbeddedPiAgent` abort timer. ## Where things can end early - Agent timeout (abort) - AbortSignal (cancel) - Gateway disconnect or RPC timeout - `agent.wait` timeout (wait-only, does not stop agent)