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
  1. Intake โ€” receive message from a channel, record metadata (sessionKey, channel, agent)
  2. Command-check โ€” slash commands (/reset, /status, etc.) handled inline, no LLM call
  3. Lane-acquire โ€” see Command Queue. If session is busy, queue or steer.
  4. Context โ€” Context Engine builds the system prompt from 10 layers
  5. Model โ€” call the LLM with assembled context + recent messages
  6. Tools โ€” if LLM wants to call tools, execute them (with permission checks)
  7. Persist โ€” append user message + assistant reply to session store
  8. Drain-queue โ€” process any messages queued while busy

Hooks fired during the loop

  • agent:bootstrap โ€” before model call, after context built
  • agent:tool-call โ€” when LLM decides to call a tool
  • agent:tool-result โ€” after tool execution returns
  • agent:complete โ€” final reply ready
  • agent:error โ€” anything fails
  • agent: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 /agent blocks the HTTP connection until the loop finishes
  • Async โ€” POST /agent/async returns { runId, acceptedAt } immediately. Use POST /agent/run/:runId/wait to block, or POST /agent/run/:runId/abort to cancel.

Source

Implementation: apps/gateway/src/agent-loop.ts.

What's next?