Personal blog powered by a passion for technology.

How OpenClaw Actually Works: Gateway Routing, Session Keys, and the Pi Runtime

I run OpenClaw daily across Discord, Telegram, and scheduled background routines.

If you treat an AI assistant as just a prompt wrapper around an LLM API, it breaks as soon as real users and background tasks touch it:

  • A second message arrives while a slow shell script is running, causing parallel turns to collide.
  • You ping the bot in a group chat, and background cron reminders start firing into that group instead of your private DM.
  • You switch between debugging infrastructure and drafting an article in one window, quickly filling your context window with noise.

OpenClaw avoids this by separating communication from execution:

  • The Gateway manages network connections, routing rules, session keys, turn queueing, and transcript files on disk.
  • The Agent Runtime runs isolated execution loops powered by Mario Zechner’s Pi engine.

Here is how a message flows through the system:

flowchart TD subgraph Inbound[1. Inbound Ingress] Discord[Discord] Telegram[Telegram] WhatsApp[WhatsApp] Slack[Slack] Cron[Cron / Heartbeat] Adapter[Channel Adapter] end subgraph Core[2. Gateway Routing] Gateway[Gateway Router] Match[Match Agent Binding] SessionKey[Resolve Session Key] Queue[Session Queue: Serialize Turns] Assemble[Assemble System Prompt] end subgraph Runtime[3. Pi Agent Runtime] Pi[Pi Execution Loop] Tools[Tool Calls: read, edit, exec] end subgraph Egress[4. Persistence and Delivery] Transcripts[Write Transcript Log] SessionStore[Update SessionStore] OutAdapter[Outbound Adapter] Client[Deliver Response] end Discord --> Adapter Telegram --> Adapter WhatsApp --> Adapter Slack --> Adapter Cron --> Gateway Adapter --> Gateway Gateway --> Match Match --> SessionKey SessionKey --> Queue Queue --> Assemble Assemble --> Pi Pi --> Tools Tools --> Pi Pi --> Transcripts Pi --> SessionStore SessionStore --> OutAdapter OutAdapter --> Client

Normalizing Inbound Transports

OpenClaw keeps transport details away from the core routing logic. Each chat platform (Discord, Telegram, WhatsApp, Slack, Matrix, iMessage) runs as an isolated channel plugin.

When an event arrives, its adapter converts vendor-specific payloads into a standard internal structure:

  • Message text along with media attachments
  • Sender identification
  • Account ID
  • Scope type (direct message, group, channel, thread, topic)

Because the gateway only works with this normalized structure, routing logic stays identical regardless of where the message originated.


Routing to the Right Agent

In OpenClaw, an agent is not just a custom system prompt. Every agent gets an isolated directory tree on disk containing dedicated memory files, daily scratchpads, tool policies, and transcripts.

The Gateway determines which agent handles an incoming message by checking bindings in top-down priority:

  1. Exact peer match: explicit peer ID bindings.
  2. Parent peer inheritance: threads and forum topics inherit bindings from their parent channel.
  3. Peer wildcard: wildcard peer rules.
  4. Guild or Team match: Discord server roles or Slack workspace teams.
  5. Account match: specific channel account IDs.
  6. Channel fallback: fallback rules across an entire channel.
  7. Default agent: configured default fallback.

Session Keys and Context Isolation

Once an agent is selected, the Gateway derives a Session Key.

The session key defines execution boundaries and context isolation. OpenClaw structures keys hierarchically:

agent:<agentId>:<scope>

A few examples from my setup:

  • Personal direct messages: agent:main:main
  • Shared room: agent:main:discord:channel:1479375441797320
  • Task thread: agent:main:discord:channel:1479375441797320:thread:15435047259395
  • Topic lane: agent:main:telegram:group:-1001928374:topic:42
flowchart LR subgraph Messages[Inbound Messages] DM[Direct Message] Thread[Discord Thread] Topic[Telegram Topic] end subgraph Keys[Session Key Resolution] K1[agent:main:main] K2[agent:main:discord:channel:thread] K3[agent:main:telegram:group:topic] end subgraph State[Disk Logs] T1[Transcript: Personal DM] T2[Transcript: Isolated Task] T3[Transcript: Topic Lane] end DM --> K1 Thread --> K2 Topic --> K3 K1 --> T1 K2 --> T2 K3 --> T3

This structure solves context pollution cleanly. When I investigate a bug inside a dedicated thread, that transcript remains completely isolated from my daily personal session.

Separating Metadata from Transcripts

OpenClaw splits session state into two pieces on disk:

  1. Session Store: A fast key-value store mapping each key to its active session ID, last update timestamp, and last known route.
  2. Transcripts: Append-only JSONL files containing turn logs, tool invocations, and responses for a given session ID.

When a message arrives, the Gateway checks the last update timestamp. If the session has expired past an idle threshold or hit a daily reset boundary, the Gateway mints a fresh session ID. The old transcript remains on disk for auditing, while the session key immediately points to a clean slate.

Protecting the Default Outbound Route

By default, personal direct messages collapse into agent:main:main, and the last active channel becomes the target for outbound alerts.

If another user messages the bot in a shared group, allowing their message to update the outbound route would hijack scheduled notifications and push them to the wrong channel.

OpenClaw prevents this by deriving a pinned owner from configuration rules. Other users can receive scoped replies when allowed, but only the pinned owner can update the outbound notification target.

Serializing Turns

If you send multiple messages while an agent is executing a command, OpenClaw queues them per session key. The active turn runs to completion, writes its changes to disk, and the next queued turn starts with the updated history.


Assembling the Prompt on Every Turn

OpenClaw builds a fresh prompt for every turn from markdown files on disk and live runtime state:

flowchart TD subgraph Files[Workspace Markdown Files] Soul[SOUL.md: Tone and Boundaries] Identity[IDENTITY.md: Name and Handle] User[USER.md: Personal Background] Agents[AGENTS.md: Operating Rules] Memory[MEMORY.md: Curated Facts] Daily[Daily Notes: Today and Yesterday] end subgraph Dynamic[Runtime State] Tools[Tool Definitions] Skills[Skill Routing Metadata] Clock[UTC Clock and Timezone] History[Active Session Transcript] end subgraph Prompt[Prompt Construction] Assembly[Prompt Builder] FinalPrompt[System Prompt and Context] Pi[Pi Agent Core] end Files --> Assembly Dynamic --> Assembly Assembly --> FinalPrompt FinalPrompt --> Pi

Skills load lazily: only their names and matching descriptions sit in the prompt. The agent reads the full skill markdown from disk only when it decides to run that workflow.


The Pi Execution Loop

Once the prompt is ready, the Gateway hands control to the Pi runtime.

Inside Pi, the model decides whether to respond with text or invoke tools such as reading files, patching code, running terminal commands, or searching. Each tool result appends back into context, and the loop repeats until the model finishes its task.

Throughout this process, Pi streams structured events back so terminal interfaces and chat channels can display live progress.

When the turn ends, the Gateway saves the new transcript entries, updates the session timestamp, and hands the output to the channel adapter to deliver back to your chat.


Trigger Types

Beyond direct chat messages, OpenClaw supports five different event sources:

  • Interactive user messages from chat apps
  • Periodic heartbeat ticks for background hygiene and checks
  • Scheduled cron jobs for morning digests and sync tasks
  • Inbound HTTP webhooks from external services
  • Internal events from child sub-agents finishing background work

Why This Architecture Works

OpenClaw stays reliable in daily operation because of straightforward design choices:

  • Channel adapters keep chat protocol quirks isolated from core logic.
  • Hierarchical session namespaces set concurrency boundaries without external lock managers.
  • Transcripts stay append-only, making audits and rollbacks simple.
  • Rules, identity, and memory live in plain Markdown files under Git version control.