openclaw/openclaw
Local Gateway that connects ~28 chat channels and companion apps to an embedded tool-calling agent with SQLite state and plugins.
Overview
OpenClaw is a self-hosted assistant built around one long-running process, the Gateway. The Gateway owns sessions, tools, events and channel connections. Clients connect to it: chat platforms (WhatsApp, Telegram, Slack, Discord, Signal, iMessage, Teams, Matrix and many more), the browser Control UI, the CLI and TUI, and companion apps for macOS, iOS, Android and Linux that add voice, camera, screen and “Canvas” actions. A message from any of them becomes a turn in a per-conversation session, which runs an embedded agent loop with tools, memory and skills.
Measured by code, it is the largest project in this category. src/agents/ alone has more than 900 non-test files, src/gateway/ about as many, and extensions/ holds 175 bundled plugins: model providers, channels, memory backends, speech, search and more. The engineering rules in AGENTS.md explain the shape: “one owner per responsibility”, “small core, capable plugins”, SQLite for all state, and database access in worker threads instead of on the Gateway’s main thread. The result is very modular and heavily guarded, but hard to read end to end.
OpenClaw is for people who want an assistant that lives in their existing chat apps and on their own machines, with no hosted tier. It is equally usable as one person’s assistant on a laptop or as a team deployment. It ships with permissive defaults: host shell execution is on without prompts unless you configure exec policy or a sandbox.
Architecture
flowchart LR
CH["Channel plugins (Telegram, Slack...)"] --> IN["Inbound access + pairing"]
UI["Control UI / CLI / TUI / apps"] --> GW["Gateway (HTTP + WebSocket)"]
IN --> DSP["auto-reply dispatch"]
GW --> DSP
DSP --> RUN["Embedded agent runner"]
RUN --> ATT["Attempt: prompt, tools, compaction, failover"]
ATT --> LOOP["agent-core runLoop"]
LOOP --> PRV["Provider plugins via packages/ai"]
LOOP --> TOOLS["Tools: exec, files, browser, sessions, cron..."]
TOOLS --> POL["Exec policy + approvals"]
TOOLS --> SBX["Optional sandbox (Docker/Podman/SSH)"]
ATT --> MEM["Memory plugin (memory-core)"]
RUN --> DB["SQLite state + transcripts"]
HB["Heartbeat + cron"] --> DSP
| Component | Path | Role |
|---|---|---|
| CLI entry | openclaw.mjs, src/entry.ts, src/cli/ |
openclaw command: onboarding, gateway, pairing, doctor, update |
| Gateway | src/gateway/ |
HTTP/WebSocket server, server methods, approvals, Control UI, OpenAI-compatible API |
| Channels | src/channels/, extensions/<channel>/ |
ChannelPlugin contract and about 28 bundled channel plugins |
| Inbound dispatch | src/auto-reply/ |
Context finalization, command detection, debouncing, reply dispatch, heartbeat |
| Embedded runner | src/agents/embedded-agent-runner/ |
Run orchestration, attempts, model resolution, compaction, failover |
| Agent core | packages/agent-core/ |
Agent class and the provider/tool loop with steering and tool batches |
| Provider layer | packages/ai/, src/llm/, extensions/<provider>/ |
Transports, stream wrappers, provider plugins |
| Tools | src/agents/tools/, src/agents/bash-tools.* |
Core tools (sessions, cron, nodes, image, browser, terminal) and exec |
| Security | src/infra/exec-approvals*.ts, src/security/, src/agents/sandbox/ |
Exec policy, DM/group access, audit, sandbox backends |
| Memory | packages/memory-host-sdk/, extensions/memory-core/ |
Memory host SDK, default memory plugin with search and “dreaming” |
| Apps | apps/{macos,ios,android,linux} |
Companion apps and device nodes |
How a request flows
Take a Telegram DM, “remind me tomorrow to renew the passport”:
- Admit the sender. The Telegram plugin implements
ChannelPlugin, a bundle of adapters for config, pairing, security, groups, mentions, outbound, status, auth, approvals, commands and secrets (types.plugin.ts). The default DM policy ispairing: an unknown sender gets a pairing code, and the owner approves it withopenclaw pairing approve. Groups default to an allowlist (dm-policy-shared.ts). - Dispatch.
dispatchInboundMessagefinalizes the message context, installs outbound hooks, and callsdispatchReplyFromConfig, or the group-thread dispatcher for threaded groups (dispatch.ts). Slash commands and directives are handled here before any model call. - Run. The reply runner picks a candidate (embedded or an external CLI harness) and calls
runEmbeddedAgent. The orchestrator wraps the run in plugin-generation scopes, can refresh plugins and continue the same task, and settles usage and terminal receipts (run-orchestrator.ts). - Attempt. Inside an attempt the runner builds the prompt from workspace bootstrap files (
AGENTS.md,SOUL.md,IDENTITY.mdand others), skills, memory and history. It then callsactiveSession.promptunder a session transcript lock (attempt-execution-phase.ts). That is the agent-coreAgent, which callsrunAgentLoop(agent.ts). - Loop.
runLoopstreams the assistant response and starts tool calls while the stream is still arriving, then runs any remaining calls as a terminal batch. It continues while there are tool results or queued “steering” messages, which are user messages that arrived mid-run and get injected at the next checkpoint. It marks the turn tainted when tool output is untrusted, and records a loop-intervention flag when the tool-loop detector fires (agent-loop.ts). - Act. Here the model would call the
crontool to schedule a job in the Gateway’s scheduler. A shell command would instead go throughexec, whose host and approval policy are described below. - Reply. Streaming text and the final reply go back through the channel’s outbound adapter. The transcript and session state are written to SQLite.
Key components
Gateway
startGatewayServerCore defaults to port 18789 and boots a kernel that loads plugins, channels, cron, hooks and the WebSocket control protocol (server-start.ts). The same process serves the Control UI, an OpenAI-compatible endpoint, plugin HTTP routes and the methods the CLI, TUI and apps use. Approvals for exec and other gated actions are delivered to whichever surface is watching, including Web Push.
Exec policy
Exec has a host (sandbox, gateway or node), a security level (deny, allowlist, full) and an ask mode (off, on-miss, always). Five named modes map onto those: deny, allowlist, ask, auto (allowlist plus automatic review) and full (exec-approvals-core.ts). With nothing configured, the requested policy is security: "full", ask: "off" (exec-approvals-effective.ts), and the approvals file defaults match (exec-approvals-config.ts). Set tools.exec.mode: ask or allowlist if you want prompts or a command allowlist.
Sandbox
Sandboxing is per agent, with mode set to off, non-main or all. The default is off, with a docker backend and workspaceAccess: "none" when it is enabled (sandbox/config.ts). non-main sandboxes every session except your main DM, which is the sensible setting for group chats and shared bots. Podman and SSH backends, sandboxed browsers and per-sandbox tool policy are also supported.
Memory
The workspace’s MEMORY.md is the canonical root memory file (root-memory-files.ts). Indexing and search are a plugin “slot”. The bundled memory-core plugin provides memory_search and memory_get over SQLite full-text search plus embeddings, with OpenAI as the default embedding provider. It also schedules a “dreaming” consolidation pass over the workspace’s memory. memory-lancedb and memory-wiki are alternative plugins. Memory entries carry provenance, and only owner- or agent-originated entries are eligible for automatic prompt injection (types.ts).
Heartbeat, cron and sessions
The heartbeat wakes an agent on a schedule (default every 30 minutes, heartbeat.ts) so it can act without being messaged. Cron jobs run isolated agent turns. Session tools (sessions_spawn, sessions_send, sessions_history and others) let one agent start, message or inspect other sessions and subagents.
Extending it
- Plugins. Each extension has an
openclaw.plugin.jsonmanifest and uses onlyopenclaw/plugin-sdk/*contracts. Channels, providers, memory backends, speech, search and tools are all plugins, and the Docker build can select a subset withOPENCLAW_EXTENSIONS. - Skills. About 50 bundled skills live in
skills/, from Apple Notes to 1Password, and workspaces can add their own. A skill workshop tool lets the agent draft skills. - Channels. Implement the
ChannelPluginadapters you need. Core owns message tools and dispatch, and the channel owns accounts, security and transport. - MCP and harnesses. MCP servers are bundled into agent tool sets. External agent harnesses such as Codex or ACP agents can be selected as run candidates in place of the embedded runner.
Running it
- Install. Use
curl -fsSL https://openclaw.ai/install.sh | bashornpm install -g openclaw. Node 24.16+ or 26.1+ is required, because state usesnode:sqlite. Then runopenclaw onboard --install-daemon, which checks model access, creates the workspace and installs the Gateway service, andopenclaw dashboardto open the Control UI. - Docker.
docker-compose.ymlrunsopenclaw-gatewayon 18789 (plus a bridge port and a Teams port) withNET_RAW/NET_ADMINdropped,no-new-privileges, a health check, andhost.docker.internalmapped for local model servers (docker-compose.yml). A secondopenclaw-cliservice runs commands against it. - Needs. At least one model provider (hosted, or a local server such as Ollama, LM Studio, vLLM or llama.cpp), plus credentials for each channel you enable.
Strengths and caveats
- Strength: channel coverage. No other project here reaches as many chat surfaces with as uniform a contract, including pairing, group policy and per-channel approvals.
- Strength: mid-run steering. Messages sent while the agent works are injected at checkpoints instead of queuing behind the run, which suits chat use.
- Strength: local and plugin-first. All state is in SQLite on your machine, and models, memory, channels and harnesses are swappable plugins.
- Caveat: permissive exec defaults. Out of the box, exec runs on the Gateway host with
fullsecurity and no prompts, and the sandbox is off. Pairing protects DMs, but anyone you approve, and any prompt injection that reaches the model, inherits shell access until you tighten policy. - Caveat: scale. Thousands of source files, many layers between a message and the loop, and fast churn make it hard to audit or patch without the project’s own tooling and docs.
- Caveat: heavy runtime. It needs a recent Node with
node:sqlite, worker threads for database access, and many plugins. It is a service to operate, not a script.
Sources: code at 10334ec, verified Q&A.
How it answers the Open-source personal assistants questions
Each answer was drafted by a code-reading agent at commit 10334ec. Its citations were checked mechanically. Compare with the other open-source personal assistants →
How is the assistant architected?
answeredAgent loop and runtime. The Gateway (src/gateway/) is the central control plane — an HTTP/WebSocket server that manages all sessions, tool execution, events, and channel connections. Session lifecycle is governed by src/sessions/session-lifecycle-admission.ts which serializes mutations and work admission via SQLite-backed store writers. When a user message arrives, the agentRunHandler in src/gateway/server-methods/agent-run-handler.ts performs preflight validation, creates an agent turn service via createAgentTurnService, and dispatches the run. The agent turn service coordinates with src/agents/ — the agent run loop — which manages MCP tool bundles, memory search, and LLM invocation through the provider runtime.
Frontend/backend split. The backend is the Gateway (HTTP server in src/gateway/server-http.ts). Frontends include a Control UI (Web), a CLI (src/cli/), a TUI (src/tui/), and native companion apps. The Gateway serves the Control UI, an OpenAI-compatible API, plugin HTTP surfaces, and WebSocket upgrades. It also exposes server-method modules (src/gateway/server-methods/) for agent runs, session history, model listing, tool invocation, user profiles, and more.
Main packages. Organized as a pnpm workspace monorepo. Key source directories: src/gateway/ (routing, approvals, chat projection), src/agents/ (agent harness, MCP tool manager, memory prompt prepare), src/sessions/ (lifecycle, state events, transcripts), src/channels/ (channel plugin system), src/llm/ and packages/ai/ (provider transports), packages/memory-host-sdk/ (memory engine), src/state/ (SQLite DB schemas for agent state, config, user profiles), src/secrets/ (credential management), src/security/ (audit, policy).
Request flow. A message arrives via a channel plugin (e.g., Discord webhook) — each channel implements ChannelPlugin (src/channels/plugins/types.plugin.ts:48). The plugin routes it into the Gateway chat runtime (src/gateway/server-chat.ts), which creates a session via lifecycle admission. The session runs by calling the agent turn service, which invokes an LLM through the provider transport layer (packages/ai/src/host.ts), executes any tool calls (MCP, filesystem, exec), and streams responses back through the channel.
How are integrations (email, calendar, chat, docs) implemented?
answeredSupported services. OpenClaw connects to 20+ messaging platforms through a channel plugin system. Each platform implements the ChannelPlugin type (src/channels/plugins/types.plugin.ts:48) which exposes adapters for config, auth, outbound messaging, threading, approvals, status, security, pairing, directory resolution, and more. Bundled channel IDs are generated in src/channels/bundled-channel-ids.generated.ts. The plugin registry (src/channels/plugins/registry.ts) manages discovery and loading.
API clients vs MCP. Channels are native plugins with full lifecycle — config setup wizards (src/channels/plugins/setup-wizard.ts), OAuth flows, webhook receivers, outbound message formatting, and streaming. This is separate from MCP, which is managed through the MCP tool bundle system in src/agents/agent-bundle-mcp-*.ts
OAuth flow. Provider OAuth callbacks are handled by src/gateway/provider-browser-auth.ts via the PROVIDER_OAUTH_CALLBACK_PATH route. Channel plugins have their own auth adapters (ChannelAuthAdapter). The Gateway supports browser-based OAuth for model providers and channel-specific auth flows.
Token storage. Secrets and credentials are managed by src/secrets/ — including resolve-store.ts, configure-plan.ts, trusted-plan-path.ts, and per-channel persisted auth state in src/channels/plugins/persisted-auth-state.ts. The Gateway's credential-planner.ts plans credential provisioning. Token material is stored in SQLite via the secrets subsystem with encryption support (src/storage/encryption.ts).
Sync vs on-demand. Channels are event-driven: incoming messages (webhooks, socket events) trigger session creation and agent runs. The auto-reply/ module handles responses and memory flushes. There is also a cron subsystem (src/cron/) for scheduled/sync operations.
How is memory and user context stored and retrieved?
answeredStorage. Memory lives in two layers. First, markdown files in the workspace (MEMORY.md as the canonical root memory file — see src/memory/root-memory-files.ts:6). Second, a SQLite database with full-text search (FTS) and optional vector embeddings via sqlite-vec or remote embedding APIs. The core engine is in packages/memory-host-sdk/ with separate module files for storage (engine-storage.ts), embeddings (engine-embeddings.ts), session recall (engine-sessions.ts), and foundation (engine-foundation.ts). Embedding vectors are stored in SQLite with sqlite-vec. The default embedding provider is OpenAI (DEFAULT_MEMORY_EMBEDDING_PROVIDER = "openai" in src/agents/memory-search.ts:70).
What is remembered. Session transcripts, user-authored memory files, and agent-written memory entries. Each entry carries provenance metadata (MemoryEntryProvenance with originClass, sessionKind, observedAt in packages/memory-host-sdk/src/host/types.ts:13). Provenance classes (owner, agent, untrusted, system) determine eligibility for automatic prompt injection — only owner and agent origins qualify (types.ts:41).
Injected into prompts. src/agents/memory-prompt-prepare.ts:8 calls prepareMemoryPromptSection from src/plugins/memory-state.ts which builds a PreparedMemoryPromptSection containing ranked search results with scores, snippets, and scores for relevance. This prompt section is injected into the LLM context.
Summarization. The memory host SDK includes consolidation/dreaming — packages/memory-host-sdk/src/host/curated-annotations.ts handles curated annotations. The memory-host-sdk/dreaming.ts module hints at consolidation processes that summarize or compact memory over time. Memory sync (MemorySyncParams in types.ts:79) supports forced refresh of session transcripts and archive files with progress tracking.
How are actions on the user's behalf gated?
answeredApproval / human-in-the-loop. The Gateway manages exec approvals through src/gateway/exec-approval-manager.ts with Web Push notifications delivered to browsers via src/gateway/approval-web-push.ts:1. The agent-runtime-approval-authority.ts:12 module validates delegated authority for each agent action, checking worker turn claims and message action turn capabilities. Operator approval flows live in src/gateway/operator-approval-*.ts with structured scopes src/gateway/method-scopes.ts and src/gateway/operator-scopes.ts.
Permission scopes. The security audit subsystem (src/security/audit.ts:1) runs comprehensive checks on startup: it inspects dangerous config flags (dangerous-config-flags.ts), filesystem exec policy drift (exec-filesystem-policy.ts), code safety (audit-deep-code-safety.ts), agent roster config (audit-agent-roster.ts), gateway config (audit-gateway-config.ts), and installed plugin trust. The dangerous-tools.ts module flags risky tool configurations. Channel-specific security is handled by ChannelSecurityAdapter in src/channels/plugins/types.plugin.ts:75.
Dry-run / draft modes. The src/channels/draft-stream-controls.ts and src/channels/draft-stream-loop.ts modules handle draft streaming for channels — responses can be streamed as drafts before final delivery. The approval system in src/gateway/approval-session-audience.ts manages which sessions see which approvals.
Audit trail. The src/audit/ directory (separate from src/security/) contains the structured audit logging system. Gateway security events are emitted through src/infra/diagnostic-events.ts. The session-lifecycle-events.ts and session-state-events.ts in sessions provide an event-sourced audit trail. The SQLite state DB includes operator approval tables managed in src/state/openclaw-state-db-operator-approval-migration.ts.
Filesystem sandbox. The sandbox system (src/agents/sandbox/) provides optional Docker-based isolation for agent exec operations, with configurable workspace access modes (none, ro, rw).
How are LLM providers selected and configured?
answeredSupported providers. The provider abstraction lives in packages/ai/src/ — the providers.ts:2 registers built-in API providers. Stream-wrappers for individual providers are in src/llm/providers/stream-wrappers/ with adapters for Anthropic, OpenAI, Google, MiniMax, Moonshot, and ZAI. Each wrapper handles provider-specific streaming nuances: Anthropic prompt caching semantics (anthropic-family-cache-semantics.ts), Google thinking payloads (google-thinking-payload.ts), OpenAI service tier observation (openai-service-tier-observation.ts), reasoning effort utils (reasoning-effort-utils.ts). Model contracts (including Anthropic-specific ones) are defined in packages/llm-core/src/model-contracts/.
Config surface. The ModelRegistry interface in src/llm/model-registry.ts:4 provides getAll(), getAvailable(), find(), and hasConfiguredAuth(). Model pickers resolve from the model catalog (src/model-catalog/, packages/model-catalog-core/). Per-agent model overrides are handled by src/sessions/model-overrides.ts. Provider-level config (endpoint class, provider family, API type) is described by AiProviderRequestCapabilities in packages/ai/src/host.ts:11.
Tool-calling / structured-output usage. Tool-calling support is inferred from the provider transport system — packages/ai/src/host.ts provides AiTransportPluginHost with resolveProviderStream() and stream wrapping. The agent harness in src/agents/ manages MCP tool bundles (agent-bundle-mcp-*.ts), tool policies (src/agents/tool-policy-match.ts, src/agents/agent-tools.policy.ts), and tool error handling (tool-error-summary.ts). The packages/ai/src/transports.ts module provides low-level HTTP/stream transport.
Local-model support. The Docker setup includes extra_hosts: host.docker.internal to reach host-side LM Studio/Ollama (docker-compose.yml:66). The OPENCLAW_EXTENSIONS build arg in the Dockerfile:11 allows including bundled local-model provider plugins. The env-api-keys.ts in packages/ai/ supports API key configuration from environment variables. The provider runtime (src/provider-runtime/operation-retry.ts) handles retry logic.
Provider auth. OAuth flows for Anthropic and OpenAI are in src/llm/utils/oauth/ with anthropic.ts and openai-chatgpt.ts. Secret credential management is handled through the secrets subsystem (src/secrets/).
How is it deployed and self-hosted?
answeredRuntime dependencies. OpenClaw requires Node.js ≥24.16.0 or ≥26.1.0 with node:sqlite support (src/openclaw.mjs:35). Bun is supported for installs and scripts but not as the primary runtime. SQLite is the core database engine — multiple databases per agent (agent-specific DB, state DB, preferences, secrets, memory with sqlite-vec for vector storage). No external database server (PostgreSQL, etc.) is required — everything runs on local SQLite files. Message queues are built into the gateway process (no Redis dependency). Docker CLI is optional for sandbox isolation.
Docker / one-click paths. The Dockerfile:16 is a multi-stage build from node:24-bookworm-slim with pinned SHA256 digests for reproducibility. The build supports extension selection via OPENCLAW_EXTENSIONS build arg (Dockerfile:11). docker-compose.yml provides two services: openclaw-gateway (main service, port 18789) and openclaw-cli (command runner). Host directories for config, workspace, and auth profiles are bind-mounted. Docker supports healthcheck, restart policies, optional Docker socket mounting for sandbox, and host.docker.internal for local model providers. One-line install is available via curl-sh or npm install -g (README.md:28-41).
Required external accounts. LLM provider API keys (Claude/Anthropic, OpenAI/ChatGPT) are required for model access. Channel-specific accounts (Discord bot token, Slack app, Telegram bot token, WhatsApp Business API, etc.) are needed per integrated channel. Environment variables accepted include CLAUDE_AI_SESSION_KEY, CLAUDE_WEB_SESSION_KEY, CLAUDE_WEB_COOKIE for Claude web access, and OPENCLAW_GATEWAY_TOKEN for gateway auth (docker-compose.yml:42-44).
Telemetry and updates. By default the only phone-home is a daily version check. Anonymous feature statistics are opt-in. update.checkOnStart: false disables both (README.md:20). The node-runtime-update.mjs and src/state/local-onboarding-state.ts manage runtime updates. OpenTelemetry export is supported via environment variables but is entirely optional.
Platform support. Native installers for macOS, iOS, Android, Linux, Windows. Companion apps add voice, Canvas, camera, screen, and device-local actions.