Agent Loop
The core execution cycle that turns a user message into an agent reply. Every message โ from CLI, Telegram, Discord, web โ goes through the same loop.
Lifecycle
intake โ command-check โ lane-acquire โ context โ model โ tools โ persist โ drain-queue- Intake โ receive message from a channel, record metadata (sessionKey, channel, agent)
- Command-check โ slash commands (
/reset,/status, etc.) handled inline, no LLM call - Lane-acquire โ see Command Queue. If session is busy, queue or steer.
- Context โ Context Engine builds the system prompt from 10 layers
- Model โ call the LLM with assembled context + recent messages
- Tools โ if LLM wants to call tools, execute them (with permission checks)
- Persist โ append user message + assistant reply to session store
- Drain-queue โ process any messages queued while busy
Hooks fired during the loop
agent:bootstrapโ before model call, after context builtagent:tool-callโ when LLM decides to call a toolagent:tool-resultโ after tool execution returnsagent:completeโ final reply readyagent:errorโ anything failsagent:queuedโ run rejected, message queued
Plugins can register handlers for these hooks to extend behavior without modifying core. See Plugin SDK.
Sync vs async
The gateway exposes two ways to run an agent loop:
- Sync โ
POST /agentblocks the HTTP connection until the loop finishes - Async โ
POST /agent/asyncreturns{ runId, acceptedAt }immediately. UsePOST /agent/run/:runId/waitto block, orPOST /agent/run/:runId/abortto cancel.
Source
Implementation: apps/gateway/src/agent-loop.ts.
What's next?
- Command Queue โ what happens when a new message arrives mid-run
- Context Engine โ how the system prompt is assembled
- Gateway Overview