Openclaw · EN · Part 1

Ch 02 System Architecture Overview

02-ch-02-System-Architecture-Overview.md

第2章 System Architecture Overview

The Gateway Control Plane

At OpenClaw's heart sits the Gateway server—not a message broker, not a service mesh, but a typed control plane that exposes ~40 RPC methods over WebSocket. Every component in the system connects to this Gateway as a "node," authenticated through a device challenge-response handshake, then communicates through three frame types: RequestFrame, ResponseFrame, and EventFrame.

Rendering diagram...

The Gateway protocol, defined in src/gateway/protocol/schema/types.ts, is not ad-hoc JSON over WebSocket. Every frame is validated against TypeBox schemas before processing, ensuring type safety from the wire up. The protocol version is negotiated during connection handshake, enabling graceful evolution without breaking existing clients.

When a client connects, it sends a ConnectParams frame with authentication credentials:

// src/gateway/client.ts:79-100
export type GatewayClientOptions = {
  url?: string;                      // ws://127.0.0.1:18789
  connectChallengeTimeoutMs?: number; // [1]
  token?: string;                    // Bootstrap token
  deviceToken?: string;              // Device-specific token
  password?: string;                 // Optional password auth
  instanceId?: string;               // Instance identifier
  clientName?: GatewayClientName;    // cli, mobile, web, etc.
  clientDisplayName?: string;         // Human-readable name
  clientVersion?: string;            // Protocol version
  platform?: string;                 // os, platform info
  deviceFamily?: string;             // Device type
  mode?: GatewayClientMode;          // Operation mode
  role?: string;                     // Access role
  scopes?: string[];                 // Permission scopes
  caps?: string[];                   // Capabilities
  commands?: string[];               // Available commands
  // ...
};

The Gateway responds with HelloOk, containing the negotiated protocol version and server capabilities. From there, all communication follows the request/response pattern with optional server-initiated events.

🤿 深入底层:The Gateway uses TypeBox schemas rather than plain TypeScript types for runtime validation. This means every incoming frame is checked against its schema before processing—catching malformed payloads before they reach business logic. The protocol schemas live in protocol-schemas.ts, generating both TypeScript types and runtime validators from a single source of truth.

The Gateway protocol handles six major domains:

DomainExample MethodsPurpose
SessionssessionsCreate, sessionsSend, sessionsListManage conversation state and message history
ConfigconfigGet, configSet, configSchemaRead and write configuration
ChannelschannelsStatus, channelsLogoutQuery channel connection state
AgentsagentsCreate, agentsList, agentIdentityManage agent instances and identities
NodesnodePairRequest, nodeList, nodeInvokeHandle node pairing and RPC
WizardwizardStart, wizardNext, wizardCancelGuide multi-step configuration flows

This RPC-style design means the Gateway is not a message broker—it doesn't route arbitrary messages between components. Instead, it exposes a structured API surface, making versioning, documentation, and client code generation straightforward.

Component Boundaries and Responsibilities

With the Gateway as the central hub, OpenClaw's architecture breaks into clear component boundaries. Each component has a specific responsibility and crosses into others only through defined contracts.

Gateway Server

The Gateway server (src/gateway/server.ts) is the WebSocket control plane implementation. Its responsibilities:

  • Connection management: Accept WebSocket connections, handle authentication, manage node lifecycle
  • Protocol enforcement: Validate all frames against schemas, return structured errors
  • Request routing: Route RPC calls to appropriate handlers (sessions, config, agents, etc.)
  • Event broadcasting: Push server-initiated events (agent responses, channel status changes, etc.) to relevant clients
  • State coordination: Maintain session state, channel status, and configuration in memory

The Gateway does not execute agent logic directly. That responsibility belongs to the Agent Runtime. The Gateway also doesn't implement platform-specific messaging logic—that's delegated to channel plugins.

Agent Runtime

The Agent Runtime (src/agents/) manages the Pi (Personal Intelligence) execution environment. Its responsibilities:

  • Agent lifecycle: Start, stop, and monitor agent instances
  • Model streaming: Stream responses from language models in real-time
  • Tool execution: Invoke tools through the Gateway when requested by agents
  • Prompt caching: Cache prompt prefixes to reduce API costs and latency
  • RPC integration: Expose agent capabilities through Gateway protocol endpoints

The Agent Runtime receives user messages through the Gateway, streams responses back through Gateway events, and invokes tools via Gateway RPC calls. This design means the agent itself doesn't need to understand channels, authentication, or session management—it focuses on AI execution.

Channels

Channels (src/channels/) implement platform-specific messaging logic. Each channel (Telegram, Discord, Slack, etc.) is responsible for:

  • Platform integration: Connect to the platform's API or webhook system
  • Message inbound: Receive messages from the platform, wrap them in InboundEnvelope, submit to Gateway
  • Message outbound: Receive agent replies from Gateway, format and send to platform
  • Pairing & policy: Handle DM pairing allowlists, group access control, context visibility filters

Channels don't execute agent logic or manage configuration—they're pure adapters between platform protocols and OpenClaw's internal envelope format. The InboundEnvelope contract, defined in src/channels/plugins/types.core.ts, carries metadata (channel ID, account ID, peer info, message type) alongside the message content.

Plugins

The Plugin system (src/plugins/) manages discovery, loading, and lifecycle of extensions. Its responsibilities:

  • Manifest validation: Read and validate openclaw.plugin.json files
  • Plugin activation: Load plugins on demand, track enabled state
  • Contract enforcement: Ensure plugins respect SDK boundaries
  • Provider/model routing: Route model requests to appropriate provider plugins

The Plugin system doesn't implement provider logic itself—that's delegated to individual provider plugins (OpenAI, Anthropic, Google, etc.). It also doesn't implement channel logic—that's handled by channel plugins.

Config

The Config system (src/config/) manages configuration schema, validation, and persistence. Its responsibilities:

  • Schema generation: Generate Zod schemas from plugin manifests and core config
  • Validation: Validate config reads and writes against schemas
  • Migration: Handle legacy config key migrations
  • Persistence: Read and write config to JSON files

The Config system doesn't enforce business logic—that's spread across components that use config values. It focuses on schema correctness and backward compatibility.

CLI

The CLI (src/cli/) provides the command-line interface for human interaction. Its responsibilities:

  • Command routing: Parse argv, route to appropriate command handlers
  • Help & documentation: Generate help text from command metadata
  • Gateway client: Connect to Gateway as a node, invoke RPC methods
  • User interaction: Handle prompts, progress indicators, and terminal output

The CLI doesn't implement core business logic—it's a thin client that translates human commands into Gateway protocol calls. This design means alternative clients (mobile apps, Web UI) can use the same Gateway protocol without duplicating logic.

⚠️ 避坑指南:A common misunderstanding is thinking the CLI is the "main" OpenClaw process. In reality, the Gateway server is the core process—the CLI, mobile apps, and Web UI are all clients connecting to it. Running openclaw gateway run starts the server; openclaw agent ... connects as a client.

The Plugin Architecture

OpenClaw's extensibility comes from its plugin system, but what makes this architecture work is not just "supporting plugins"—it's enforcing boundaries so plugins can extend the system without breaking it.

The Plugin SDK, located at src/plugin-sdk/, defines the public contract that all plugins must use. Extensions are forbidden from importing core internals directly (src/**)—they must cross the boundary through SDK exports. This rule, enforced in CLAUDE.md, prevents tight coupling and allows core evolution without breaking third-party plugins.

Plugins are discovered through their manifest file (openclaw.plugin.json), defined in src/plugins/manifest.ts:

// src/plugins/manifest.ts:35-71
export type PluginManifest = {
  id: string;                                  // Unique plugin identifier
  configSchema: Record<string, unknown>;       // Plugin's config schema
  enabledByDefault?: boolean;                   // Auto-enable on discovery
  legacyPluginIds?: string[];                   // Compat for old ids
  autoEnableWhenConfiguredProviders?: string[]; // Wiring for provider refs
  kind?: PluginKind | PluginKind[];             // channel | provider | skill
  channels?: string[];                          // Channel IDs provided
  providers?: string[];                         // Provider IDs provided
  modelSupport?: PluginManifestModelSupport;    // Model family ownership
  cliBackends?: string[];                       // CLI inference backends
  providerAuthEnvVars?: Record<string, string[]>; // Auth env var lookup
  providerAuthChoices?: PluginManifestProviderAuthChoice[]; // Auth metadata
  skills?: string[];                            // Skill IDs provided
  name?: string;                                // Human-readable name
  description?: string;                         // Plugin description
  version?: string;                             // Plugin version
  uiHints?: Record<string, PluginConfigUiHint>; // UI config hints
  contracts?: PluginManifestContracts;          // Capability ownership
  channelConfigs?: Record<string, PluginManifestChannelConfig>; // Channel configs
};

This manifest structure enables manifest-first behavior—the system can understand plugin capabilities, validate config, and make wiring decisions without loading the plugin runtime. This is why openclaw doctor can report on plugin status before any plugins are activated.

Three Plugin Types

OpenClaw plugins fall into three categories, each with distinct contracts:

  1. Channel plugins: Implement messaging platform integrations

    • Contract: src/channels/plugins/types.plugin.ts defines inbound/outbound handlers, pairing logic, and config schema
    • Examples: Telegram, Discord, Slack, Signal, iMessage, WhatsApp, Matrix, Zalo
    • Boundary: Channels use the Plugin SDK's channel contract; they don't import other channels or core agent logic
  2. Provider plugins: Implement model provider integrations

    • Contract: src/plugin-sdk/provider-entry.ts defines auth, model catalog, and inference hooks
    • Examples: OpenAI, Anthropic, Google, xAI, Moonshot
    • Boundary: Providers register models through SDK hooks; core doesn't read plugins.entries.<id>.config directly
  3. Skill plugins: Implement agent capabilities (often packaged as "apps" in the plugin ecosystem)

    • Contract: openclaw/plugin-sdk/core.ts defines tool registration and skill metadata
    • Examples: Memory plugins, media understanding, web search, custom tools
    • Boundary: Skills register tools through the Gateway; they don't reach into agent internals

☕ 茶水间时间:Why 95+ bundled plugins? OpenClaw includes popular channels and providers as "bundled workspace plugins" in the extensions/ directory. This serves two purposes: (1) users get common integrations out-of-the-box without hunting for community plugins, and (2) these plugins serve as reference implementations for third-party developers. The bundled plugins follow the same Plugin SDK contract as any external plugin—they're not "special" internally, just conveniently packaged.

Boundary Enforcement

The architecture enforces boundaries through multiple mechanisms:

  • Import restrictions: Extensions cannot import src/** internals (enforced in CLAUDE.md)
  • SDK facades: Public contracts are exported through openclaw/plugin-sdk/* barrels
  • Manifest validation: Plugin capabilities must be declared in manifests before runtime
  • Contract tests: Automated tests verify plugins respect their contracts

When core needs a new plugin seam, it's added to the Plugin SDK first—not as an ad hoc import. This backward-compatible contract evolution ensures existing plugins continue working as the system evolves.

Data Flow Patterns

Understanding component boundaries is one thing; seeing how data flows through them is another. Let's trace two critical flows with concrete file paths and function names.

CLI Command Execution Flow

When you run openclaw agent "explain quantum computing", the execution path is:

  1. Entry wrapper (openclaw.mjs:1-181): Checks Node version, enables compile cache, loads dist/entry.js

  2. Main entry (src/entry.ts:102-156): runMainOrRootHelp() normalizes argv, handles profile/container arguments

  3. CLI routing (src/cli/run-main.ts): tryRouteCli() parses the command path, routes to agent command handler

  4. Gateway client (src/gateway/client.ts): Creates GatewayClient, connects to WebSocket at ws://127.0.0.1:18789

  5. Protocol handshake (src/gateway/protocol/schema/types.ts:7-11): Sends ConnectParams with auth tokens, receives HelloOk with negotiated protocol version

  6. Session creation: Sends RequestFrame with method sessionsCreate, Gateway creates or resolves session

  7. Message send: Sends RequestFrame with method sessionsSend containing the user's message

  8. Agent invocation (src/agents/): Agent runtime receives message, invokes Pi agent with context

  9. Streaming response: Agent streams response through Gateway EventFrame messages with type agentChunk

  10. CLI output: CLI client receives events, prints streaming response to terminal

This flow demonstrates that the CLI is a thin client—it doesn't execute agent logic, manage state, or implement protocols. It translates human commands into Gateway protocol calls and renders Gateway events as terminal output.

Inbound Message Handling Flow

When a Telegram message arrives, the path to agent response is more complex:

  1. Platform webhook (extensions/telegram/): Telegram sends webhook to OpenClaw's HTTP endpoint

  2. Channel inbound (src/channels/telegram/inbound.ts): Channel receives webhook payload, validates signature

  3. Envelope creation (src/channels/plugins/types.core.ts): Wraps message in InboundEnvelope with metadata (channelId, accountId, peer, message type, content)

  4. Routing (src/routing/): Routes envelope to appropriate session based on channel/account/peer

  5. Session resolution (src/sessions/): Resolves or creates session, updates session state

  6. Agent submission (src/agents/): Submits message to Pi agent with session context, available tools

  7. Tool invocation (src/gateway/tools-invoke-http.ts): Agent requests tool call, Gateway routes to appropriate plugin

  8. Tool execution: Provider/skill plugin executes tool, returns result through Gateway

  9. Agent streaming: Agent streams response chunks through Gateway events

  10. Channel outbound (src/channels/telegram/send.ts): Channel receives agent reply, formats and sends via Telegram API

This flow shows how channels are pure adapters—they translate platform protocols to OpenClaw's envelope format and back. The agent doesn't know about Telegram; it just receives a message with context and responds. The Gateway coordinates the handoff between channel, session, agent, and tool execution.

The InboundEnvelope carries everything needed for routing and policy:

// Conceptual structure (simplified)
type InboundEnvelope = {
  channelId: string;      // "telegram"
  accountId: string;      // Telegram bot ID
  peer: {                 // Sender info
    id: string;           // User ID
    username?: string;    // Optional handle
    displayName: string;  // Display name
  };
  messageType: "dm" | "group";  // Direct or group message
  content: string;       // Message text
  metadata: {            // Platform-specific metadata
    messageId: string;
    replyTo?: string;    // If this is a reply
    attachments?: Array<Attachment>;
  };
};

🤿 深入底层:The routing logic in src/routing/ determines whether a message creates a new session or resumes an existing one. For DMs, this is straightforward (one session per peer). For groups, it's more complex—groups can share sessions or have isolated sessions depending on configuration. The routing decision respects channel pairing policies and group access controls before resolving a session.

Boundary Enforcement in Action

Notice what doesn't happen in these flows:

  • The CLI doesn't import src/agents/ directly—it goes through the Gateway protocol
  • Channels don't import src/agents/—they submit envelopes and receive replies
  • Provider plugins don't import core agent logic—they register tools and model catalogs
  • The Agent runtime doesn't import channel code—it receives normalized messages through sessions

Each component stays in its lane, communicating through defined contracts. This is why OpenClaw can add new channels, new providers, and new agent capabilities without rewriting core systems.


With the architecture mapped out—the Gateway control plane at the center, components with clear responsibilities, plugins extending through contracts, and data flowing through boundaries—we're ready to get OpenClaw running on your local machine. The next chapter moves from architecture to action: installing OpenClaw, configuring your first model, and exploring its capabilities hands-on.

Chapter Exercises

  1. Trace the command flow: Pick a CLI command (e.g., openclaw config get) and trace it through src/cli/run-main.ts → src/gateway/client.ts → Gateway protocol → handler. Identify which files handle each step.

  2. Map a message flow: Starting from a Telegram webhook, follow the path through channel inbound → envelope → routing → session → agent → response → channel outbound. Draw a diagram showing which component owns each step.

  3. Find boundary enforcement: Locate three places in the codebase where the architecture enforces a boundary (hint: check CLAUDE.md for import restrictions, look for SDK exports in src/plugin-sdk/, find manifest validation in src/plugins/). Explain what each boundary protects and how it's enforced.