OpenClaw Architecture Overview

This is the top-level map. All other architecture documents branch from here.

Based on OpenClaw version 2026.2.23 (commit aceb17a).

High-level architecture

Message flow (inbound → outbound)

A message from Telegram to a reply, step by step:

1. Telegram webhook delivers message

2. extensions/telegram/ normalizes to common format

3. src/channels/plugins/ — Channel Plugin Manager receives message

4. src/routing/resolve-route.ts — matches bindings to find agentId + sessionKey
   (binding priority: peer → guild+roles → guild → team → account → channel → default)

5. src/auto-reply/ — command parsing (/help, /new, /model), directive handling

6. src/agents/pi-embedded-runner/run.ts — runEmbeddedPiAgent()
   a. Load session history from JSONL transcript
   b. Build system prompt (identity + skills + tools + memory context)
   c. Select model + provider from config/overrides
   d. Execute Pi Agent loop: LLM → tool calls → execute → LLM → ...
   e. Stream text chunks back via block reply pipeline
   f. Append turn to session transcript
   g. Auto-compact if context exceeds limits

7. src/channels/plugins/outbound/ — send reply to Telegram API

Module index

Each module has its own detailed document. Status indicates writing progress.

ModuleFileKey source pathsStatus
GatewayGatewaysrc/gateway/server.impl.ts, server.ts, server-http.tsDone
Pi Agent RuntimePi Agent Runtimesrc/agents/pi-embedded-runner/, pi-embedded.tsDone
Channel PluginsChannel Pluginssrc/channels/plugins/types.plugin.ts, extensions/*/Done
RoutingRoutingsrc/routing/resolve-route.ts, bindings.ts, session-key.tsDone
Auto-Reply PipelineAuto-Reply Pipelinesrc/auto-reply/reply/agent-runner-execution.tsDone
Tool PolicyTool Policysrc/agents/tool-policy-pipeline.ts, tool-policy.tsDone
Skills SystemSkills Systemsrc/agents/skills/workspace.ts, skills.tsDone
Memory (RAG)Memory (RAG)src/memory/manager.ts, hybrid.ts, embeddings.tsDone
SessionsSessionssrc/sessions/, src/gateway/session-utils.tsDone
Cron & HeartbeatCron & Heartbeatsrc/cron/service.ts, service/timer.ts, service/jobs.tsDone
Config SystemConfig Systemsrc/config/io.ts, types.openclaw.ts, zod-schema.tsDone
Plugin SDKPlugin SDKsrc/plugin-sdk/index.ts (543 lines, 18+ adapter types)Done
Control UI (Canvas)Control UI (Canvas)ui/src/ui/app.ts, gateway.tsDone
Native AppsNative Appsapps/macos/, apps/ios/, apps/android/Done

Cross-module patterns

Recurring design patterns that span multiple modules. See patterns index.

PatternFileModules involved
Markdown-as-ConfigMarkdown-as-ConfigSkills, Memory, Config
Append-Only JSONLAppend-Only JSONLSessions, Cron
Ed25519 Device AuthEd25519 Device AuthGateway, Control UI, Native Apps, Pairing
Cascading ConfigCascading ConfigConfig, Tool Policy
Context Overflow RecoveryContext Overflow RecoveryPi Agent Runtime, Compaction

Deep dives

Code-level walkthroughs of high-impact subsystems. See deep-dives index.

TopicFileCore source
Compaction InternalsCompaction Internalssrc/agents/compaction.ts (406 lines)
Heartbeat Cost AnalysisHeartbeat Cost Analysissrc/cron/service/timer.ts (877 lines)
Context Budget AllocationContext Budget Allocationsrc/agents/pi-embedded-runner/run.ts, defaults.ts

Key architectural decisions (verified from source)

  1. Single process: The Gateway (src/gateway/server.impl.ts) runs everything in one Node.js process — WebSocket server, HTTP API, channel plugins, agent runtime, cron. Default port 18789, with Canvas Host on port+1 and Browser Control on port+2.

  2. Local-first persistence: Sessions as append-only JSONL files (~/.openclaw/agents/<agentId>/sessions/<sessionId>.jsonl). Memory as SQLite with vector extensions (memory.db). Config as JSON5 with env var substitution (openclaw.json). No cloud dependency.

  3. BYOM (Bring Your Own Model): The Pi Agent SDK (@mariozechner/pi-agent-core) abstracts the LLM provider. Supports Anthropic, OpenAI, Google Gemini, xAI Grok, DeepSeek, Mistral, Ollama, and more. Model selection per agent, per session, or per turn.

  4. Markdown-as-Config: Skills are SKILL.md files with YAML frontmatter. Memory notes are Markdown. Agent workspace files are Markdown. All human-readable, Git-friendly, and natively parseable by LLMs.

  5. Channel-agnostic plugin architecture: Each messaging platform is an extension package (extensions/*/) that implements the ChannelPlugin interface from src/channels/plugins/types.plugin.ts. 38 extensions currently, each with ~15 adapter types (config, security, outbound, status, gateway, threading, etc.).

  6. Monorepo with pnpm workspaces: Root package + ui/ + extensions/* as workspace members. Build via tsdown. TypeScript 5.9, Node.js 22+, strict mode.

Source directory structure

openclaw/
├── src/                          # Core source (48 subdirectories)
│   ├── gateway/                  # Gateway server, WebSocket, HTTP, Control UI
│   ├── agents/                   # Pi Agent runtime, skills, tools, sessions, models
│   ├── channels/                 # Channel plugin infrastructure + types
│   ├── routing/                  # Message routing (bindings → agent + session)
│   ├── auto-reply/               # Inbound message → agent turn pipeline
│   ├── config/                   # openclaw.json loading, validation, schema
│   ├── memory/                   # RAG: SQLite vector + FTS5, embeddings, hybrid search
│   ├── sessions/                 # Session key utils, persistence
│   ├── cron/                     # Cron service, heartbeat, scheduled jobs
│   ├── security/                 # Audit, tool policy, dangerous tools list
│   ├── plugins/                  # Plugin SDK types, runtime, loading
│   ├── plugin-sdk/               # Public SDK for extension authors (544-line index.ts)
│   ├── hooks/                    # Lifecycle hooks system
│   ├── cli/                      # CLI wiring
│   ├── commands/                 # CLI commands (gateway, setup, doctor, etc.)
│   ├── browser/                  # Playwright browser automation
│   ├── media/                    # Media processing pipeline
│   ├── media-understanding/      # Image/video analysis
│   ├── tts/                      # Text-to-speech
│   ├── markdown/                 # Markdown utilities
│   ├── infra/                    # Infrastructure helpers
│   ├── logging/                  # Logging system
│   ├── pairing/                  # Device pairing protocol
│   ├── process/                  # Process management
│   ├── providers/                # LLM provider configs
│   ├── shared/                   # Shared utilities
│   ├── types/                    # Common type definitions
│   ├── utils/                    # General utilities
│   ├── web/                      # Web server helpers
│   ├── wizard/                   # Setup wizard
│   ├── telegram/                 # Telegram-specific src (shared with extension)
│   ├── discord/                  # Discord-specific src
│   ├── whatsapp/                 # WhatsApp-specific src
│   ├── slack/                    # Slack-specific src
│   ├── signal/                   # Signal-specific src
│   ├── imessage/                 # iMessage-specific src
│   ├── line/                     # LINE-specific src
│   └── ...

├── extensions/                   # 38 extension packages
│   ├── telegram/                 # @openclaw/telegram
│   ├── discord/                  # @openclaw/discord
│   ├── whatsapp/                 # @openclaw/whatsapp
│   ├── slack/                    # @openclaw/slack
│   ├── signal/                   # @openclaw/signal
│   ├── imessage/                 # @openclaw/imessage (via BlueBubbles)
│   ├── matrix/                   # @openclaw/matrix
│   ├── msteams/                  # @openclaw/msteams
│   ├── feishu/                   # @openclaw/feishu (Lark)
│   ├── irc/                      # @openclaw/irc
│   ├── nostr/                    # @openclaw/nostr
│   ├── twitch/                   # @openclaw/twitch
│   ├── memory-core/              # Core memory extension
│   ├── memory-lancedb/           # LanceDB memory backend
│   ├── voice-call/               # Voice call support
│   ├── phone-control/            # Phone automation
│   ├── llm-task/                 # LLM task delegation
│   ├── diagnostics-otel/         # OpenTelemetry diagnostics
│   └── ...

├── skills/                       # 52 bundled skills (SKILL.md format)
│   ├── github/                   # GitHub operations via gh CLI
│   ├── slack/                    # Slack channel tools
│   ├── canvas/                   # Canvas display actions
│   ├── coding-agent/             # Delegate to Codex/Claude Code
│   ├── obsidian/                 # Obsidian integration
│   ├── notion/                   # Notion integration
│   └── ...

├── ui/                           # Control UI (Lit + Vite)
│   ├── src/ui/app.ts             # Main <openclaw-app> component
│   ├── src/ui/gateway.ts         # WebSocket client (Ed25519 device auth)
│   └── vite.config.ts            # Builds to dist/control-ui/

├── apps/                         # Native clients
│   ├── macos/                    # Swift 6 / SwiftUI, menu bar app (macOS 15+)
│   ├── ios/                      # Swift 6 / SwiftUI, XcodeGen (iOS 18+)
│   ├── android/                  # Kotlin / Jetpack Compose (API 31+)
│   └── shared/OpenClawKit/       # Shared Swift package

├── package.json                  # name: openclaw, version: 2026.2.23
├── pnpm-workspace.yaml           # Monorepo: root + ui + extensions/*
├── tsconfig.json                 # strict, NodeNext, es2023
└── tsdown.config.ts              # Build entry points

Config structure (openclaw.json)

Verified from src/config/types.openclaw.ts and Zod schema:

{
  "agents": {
    "defaults": {
      "provider": "anthropic",
      "model": "claude-opus-4-6",
      "thinking": "auto",
      "compaction": { "enabled": true },
      "sandbox": { "tools": { "allow": ["*"], "deny": [] } },
      "skills": { "allowlist": ["*"] }
    },
    "list": [
      { "id": "default", "name": "OpenClaw", "avatar": "🦞" }
    ]
  },
  "channels": {
    "telegram": [{ "accountId": "bot1", "botToken": "${TELEGRAM_BOT_TOKEN}", "dmPolicy": "open" }],
    "discord": [{ "accountId": "bot1", "botToken": "${DISCORD_BOT_TOKEN}", "dmPolicy": "open" }]
  },
  "bindings": [
    { "match": { "channel": "telegram" }, "agentId": "default" }
  ],
  "gateway": { "enabled": true, "port": 18789, "bind": "loopback" },
  "cron": { "enabled": true, "heartbeat": { "enabled": true, "intervalSeconds": 600 } },
  "memory": { "provider": "openai", "model": "text-embedding-3-small", "enabled": true }
}

My blind spots

Honest record of what I don't yet understand. Updated as knowledge grows.

  • How does the tool policy pipeline compose its filtering layers? — Resolved: 7-step cascading allowlist pipeline, each step progressively filters tools. No explicit deny; tools not in allowlist are excluded. See Tool Policy.
  • The compaction flow — how does it decide what to summarize vs keep? — Resolved: BASE_CHUNK_RATIO=0.4 adaptive chunking, splitMessagesByTokenShare() distributes messages across N chunks targeting equal token share, oldest chunks dropped first, repairToolUseResultPairing() fixes orphaned tool_results after pruning. See Pi Agent Runtime and Compaction Internals.
  • Memory hybrid search ranking — Resolved: score = vectorWeight * vectorScore + textWeight * textScore, with optional MMR re-ranking (lambda=0.7: 70% relevance, 30% diversity penalty via Jaccard similarity). See Memory (RAG).
  • Device pairing protocol — Resolved: Ed25519 challenge-response, 8-char alphanumeric pairing code (no ambiguous 0O1I), 60-min TTL, max 3 pending, SHA-256 device ID derived from public key. See Native Apps and Ed25519 Device Auth.
  • Extension hot-reload — Resolved: Extensions do NOT hot-reload (require gateway restart). Only config-level hooks and cron support hot-reload via chokidar file watcher with 300ms debounce. See src/gateway/config-reload.ts.
  • Subagent registry — Resolved: No explicit depth limit. Tracks all levels via rootSessionKey recursive queries in listDescendantRunsForRequester(). Orphan detection via resolveSubagentRunOrphanReason(), announce retry max 3 times with 5-min expiry. See src/agents/subagent-registry.ts (1,081 lines).
  • WebSocket reconnection strategy — Resolved: Server does not initiate reconnects (stateless accept). Web UI (ui/src/ui/gateway.ts) uses exponential backoff: initial 800ms, multiplier 1.7x, max 15s. iOS app retries on foreground resume only (attemptAutoReconnectIfNeeded()). See WebSocket Disconnects.
  • Config hot-reload failure handling — Resolved: On reload failure, system logs the error and skips the update; old config stays in memory. No rollback mechanism. Missing file retries 2x with 150ms delay then skips. Invalid config logs validation errors and skips. See src/gateway/config-reload.ts:370-388.
  • JSONL transcript corruption recovery — Resolved: Two-layer recovery. File-level: repairSessionFileIfNeeded() (src/agents/session-file-repair.ts) drops unparseable JSON lines and creates .bak-{pid}-{timestamp} backups. Transcript-level: repairToolUseResultPairing() + repairToolCallInputs() + sanitizeToolCallInputs() fix orphaned/duplicate tool results. See Session Transcript Corruption.
  • Memory index cache eviction — maxEntries config exists in src/memory/manager.ts but NO pruning/eviction is implemented. INDEX_CACHE is an unbounded Map with no LRU, size check, or TTL. Appears to be a missing feature.
  • Multi-agent session key collision — Session keys are deterministic (agent:{agentId}:{mainKey} in src/routing/session-key.ts). No UUID generation, collision detection, or locking. System relies on external uniqueness (distinct agent IDs + peer IDs per channel) rather than built-in safeguards.