# OpenClaw Architecture Analysis **Prepared for:** LetsBe Biz Team **Date:** 2026-02-26 **OpenClaw Version:** 2026.2.26 **License:** MIT (Copyright 2025 Peter Steinberger) **Purpose:** Deep architectural analysis to support provisioning, Safety Wrapper integration, tool leveraging, and technical documentation for the LetsBe privacy-first AI workforce platform. --- ## Table of Contents 1. [Architecture Overview](#1-architecture-overview) 2. [Startup & Bootstrap Sequence](#2-startup--bootstrap-sequence) 3. [Plugin/Extension System](#3-pluginextension-system) 4. [AI Agent Runtime](#4-ai-agent-runtime) 5. [Tool & Integration Catalog](#5-tool--integration-catalog) 6. [Data & Storage](#6-data--storage) 7. [Deployment & Configuration](#7-deployment--configuration) 8. [API Surface](#8-api-surface) 9. [Security Model](#9-security-model) 10. [Integration Points for LetsBe Safety Wrapper](#10-integration-points-for-letsbe-safety-wrapper) 11. [Provisioning Blueprint](#11-provisioning-blueprint) 12. [Risks, Limitations & Open Questions](#12-risks-limitations--open-questions) --- ## 1. Architecture Overview ### 1.1 High-Level Architecture Diagram ``` ┌─────────────────────────────────────────────────────────────────────────┐ │ OPENCLAW GATEWAY │ │ (Node.js 22+ / TypeScript / ESM) │ │ │ │ ┌──────────┐ ┌──────────┐ ┌───────────┐ ┌────────────────────────┐ │ │ │ HTTP API │ │ WS API │ │ Control UI│ │ Canvas/A2UI Host │ │ │ │ :18789 │ │ :18789 │ │ :18789 │ │ :18789 │ │ │ └────┬─────┘ └────┬─────┘ └─────┬─────┘ └──────────┬────────────┘ │ │ │ │ │ │ │ │ ┌────▼──────────────▼──────────────▼────────────────────▼────────────┐ │ │ │ GATEWAY SERVER (Express v5 + ws) │ │ │ │ ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ │ │ Auth │ │ Config │ │ Routing │ │ Hooks │ │ Plugins │ │ │ │ │ │ Layer │ │ System │ │ Engine │ │ System │ │ Registry │ │ │ │ │ └─────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ └───────────────────────────┬───────────────────────────────────────┘ │ │ │ │ │ ┌───────────────────────────▼───────────────────────────────────────┐ │ │ │ AGENT RUNTIME │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ │ │ Provider │ │ Tools │ │ Skills │ │ Memory │ │ │ │ │ │ Manager │ │ Engine │ │ Loader │ │ Backend │ │ │ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ │ │ Session │ │ Subagent │ │ pi-agent │ │ │ │ │ │ Manager │ │ Registry │ │ -core │ │ │ │ │ └──────────┘ └──────────┘ └──────────┘ │ │ │ └───────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ┌───────────────────────────▼───────────────────────────────────────┐ │ │ │ CHANNELS │ │ │ │ Telegram │ Discord │ Slack │ WhatsApp │ Signal │ iMessage │ ... │ │ │ └───────────────────────────────────────────────────────────────────┘ │ └─────────────────────┬───────────────────────────────┬───────────────────┘ │ │ ┌────────────▼────────────┐ ┌────────────▼────────────┐ │ SANDBOX CONTAINERS │ │ NATIVE CLIENT APPS │ │ ┌────────────────────┐ │ │ ┌──────┐ ┌──────┐ │ │ │ sandbox (Debian) │ │ │ │ macOS│ │ iOS │ │ │ │ sandbox-browser │ │ │ │ App │ │ App │ │ │ │ sandbox-common │ │ │ └──────┘ └──────┘ │ │ └────────────────────┘ │ │ ┌──────┐ │ └─────────────────────────┘ │ │ Andrd│ │ │ │ App │ │ │ └──────┘ │ └─────────────────────────┘ ``` ### 1.2 Core Runtime | Attribute | Value | |-----------|-------| | **Language** | TypeScript (ESM, strict mode) | | **Runtime** | Node.js 22.12.0+ (required) | | **Package Manager** | pnpm 10.23.0 (primary), Bun supported | | **HTTP Framework** | Express v5 | | **WebSocket** | `ws` library | | **CLI Framework** | Commander.js | | **Schema Validation** | Zod v4 (config), TypeBox (tool schemas) | | **AI Agent Core** | `@mariozechner/pi-agent-core` | | **Entry Point** | `openclaw.mjs` → `dist/entry.js` → `src/cli/run-main.ts` | | **Binary Name** | `openclaw` (installed via npm) | | **Default Port** | 18789 (gateway HTTP + WS multiplexed) | | **Config Format** | JSON5 (`~/.openclaw/openclaw.json`) | ### 1.3 Package/Module Structure **Root-level directories:** | Directory | Purpose | |-----------|---------| | `src/` | Core TypeScript source — CLI, gateway, agents, routing, plugins, channels | | `extensions/` | Plugin packages — chat channels, memory backends, auth providers, tools | | `skills/` | Markdown-based knowledge packages injected into agent context | | `apps/` | Native client apps — `apps/shared/OpenClawKit/` (Swift), `apps/ios/`, `apps/macos/`, `apps/android/` | | `ui/` | Control UI web frontend (built React app served by gateway) | | `docs/` | Mintlify documentation site source | | `test/` | Test fixtures, helpers, mocks | | `vendor/` | Vendored dependencies (`a2ui`) | | `Swabble/` | Swift package for Swabble integration | | `scripts/` | Build, test, and release helper scripts | | `changelog/` | Changelog fragment system | | `assets/` | Static assets (Chrome extension) | **`src/` module breakdown:** | Module | Purpose | Key Files | |--------|---------|-----------| | `src/entry.ts` | Binary entry point — warning filter, env normalization, respawn | Single file | | `src/index.ts` | Library entry point — builds Commander program, exports public API | Single file | | `src/cli/` | CLI wiring — Commander program builder, command registration, deps injection | `run-main.ts`, `gateway-cli.ts`, `deps.ts` | | `src/config/` | Config loading, validation, schema, paths, migrations | `io.ts`, `zod-schema.ts`, `paths.ts` | | `src/agents/` | Agent runtime — LLM providers, model selection, tools, skills, sessions, subagents | ~200+ files | | `src/gateway/` | Gateway server — HTTP/WS, auth, channels, cron, discovery, hooks | `server.impl.ts`, `server-http.ts`, `auth.ts` | | `src/hooks/` | Internal hook system — event bus for gateway/session/command/message events | `internal-hooks.ts`, `loader.ts` | | `src/routing/` | Message routing — agent route resolution, session key construction, bindings | `resolve-route.ts`, `session-key.ts` | | `src/plugins/` | Plugin loader, registry, service lifecycle, hook runner | `loader.ts`, `registry.ts`, `types.ts`, `tools.ts` | | `src/plugin-sdk/` | Public SDK for extension authors (re-exports from `src/plugins/types.ts`) | `index.ts` | | `src/channels/` | Channel plugin infrastructure — plugin registry, chat type definitions | `index.ts` | | `src/providers/` | Provider-specific auth helpers (GitHub Copilot, Google, Qwen, Kilocode) | Per-provider files | | `src/infra/` | Infrastructure — dotenv, env, ports, locks, path safety, exec approvals, TLS, SSH, Tailscale, mDNS | Various | | `src/security/` | Security audit system | `audit.ts` | | `src/process/` | Process supervisor, command queue, child process bridge | `supervisor/supervisor.ts` | | `src/memory/` | Memory backend — SQLite + FTS5 + sqlite-vec vector search | Various | | `src/browser/` | Browser automation — Playwright + CDP control server | `server.ts`, `pw-tools-core.*.ts` | | `src/media/` | Media pipeline — MIME detection, image ops, audio, file I/O | Various | | `src/media-understanding/` | Multi-provider AI pipeline for audio/video/image understanding | `runner.ts`, `providers/` | | `src/web/` | Web provider (Pi/Claude.ai web session) | Various | | `src/telegram/` | Telegram channel (grammy) | Various | | `src/discord/` | Discord channel (discord.js) | Various | | `src/slack/` | Slack channel (@slack/bolt) | Various | | `src/whatsapp/` | WhatsApp channel (Baileys) | Various | | `src/signal/` | Signal channel | Various | | `src/imessage/` | iMessage channel | Various | | `src/line/` | LINE channel | Various | | `src/acp/` | Agent Client Protocol session management | Various | | `src/canvas-host/` | Canvas/A2UI artifact host server | Various | | `src/auto-reply/` | Auto-reply engine — trigger detection, dispatch, templating | Various | | `src/daemon/` | System service installer (launchd, systemd, schtasks) | `service.ts` | | `src/tui/` | Terminal UI mode | Various | | `src/tts/` | Text-to-speech abstraction | Various | | `src/commands/` | High-level CLI command implementations | ~100+ files | | `src/wizard/` | Onboarding wizard | Various | | `src/logging/` | Structured logging (tslog-based) | Various | | `src/sessions/` | Session store, session key types | Various | | `src/terminal/` | Terminal UI utilities (tables, palette, progress) | Various | | `src/markdown/` | Markdown rendering/transformation | Various | | `src/link-understanding/` | Link preview/unfurl | Various | | `src/docs/` | Docs helpers | Various | | `src/cron/` | Cron scheduling (croner) | Various | | `src/pairing/` | Device pairing protocol | Various | | `src/shared/` | Shared test utilities | Various | | `src/types/` | Shared TypeScript types | Various | | `src/utils/` | General utility functions | Various | | `src/scripts/` | Build/test helper scripts | Various | ### 1.4 Internal Dependency Graph ``` Layer 0 (Foundation): src/infra/ ← env, dotenv, ports, paths, fetch, exec-approvals src/logging/ ← tslog-based structured logging src/types/ ← shared TypeScript types src/utils/ ← utility functions Layer 1 (Config): src/config/ ← paths, schema (Zod), io, sessions, migrations depends on: infra, logging Layer 2 (Agent Core): src/agents/ ← model-auth, model-selection, models-config, pi-embedded-runner, skills, subagent-registry, tools, sessions, workspace depends on: config, infra, logging Layer 3 (Routing & Hooks): src/routing/ ← resolve-route, session-key, bindings src/hooks/ ← internal-hooks, loader, gmail-watcher depends on: config, agents Layer 4 (Plugins): src/plugins/ ← loader, registry, services, hook runner src/plugin-sdk/ ← public API surface for extension authors depends on: config, agents, hooks Layer 5 (Gateway): src/gateway/ ← server.impl, server-http, server-channels, auth, cron, discovery depends on: plugins, hooks, routing, agents, config Layer 6 (CLI): src/cli/ ← run-main, program, command-registry, deps injection src/commands/ ← high-level command implementations depends on: gateway, plugins, agents, config Layer 7 (Entry): src/entry.ts ← binary entry point depends on: cli ``` ### 1.5 What is OpenClawKit? **Path:** `apps/shared/OpenClawKit/` OpenClawKit is a **Swift Package** (Swift 6.2, iOS 18+, macOS 15+) that serves as the shared native client library for the macOS menu bar app and iOS app. It is NOT part of the server-side runtime. It consists of three library products: | Product | Purpose | |---------|---------| | **OpenClawProtocol** | Swift-side representation of the gateway's JSON-over-WebSocket wire protocol. Auto-generated from TypeScript via `scripts/protocol-gen-swift.ts` | | **OpenClawKit** | Main client library — WebSocket connection management, device auth, TLS pinning, hardware command builders (camera, screen, location, calendar, contacts), tool display metadata | | **OpenClawChatUI** | SwiftUI chat interface components used by both macOS and iOS apps | **Relationship to core:** OpenClawKit connects to the gateway server over WebSocket using the protocol defined in `src/gateway/protocol/`. The gateway's `server-mobile-nodes.ts` handles mobile node registration and event subscription. The protocol types are kept in sync via code generation. **Relevance to LetsBe:** Not directly relevant for server-side provisioning. However, if LetsBe ever wants to offer native mobile apps to SMB customers, OpenClawKit provides the client framework. For our VPS deployment, the gateway is what matters. --- ## 2. Startup & Bootstrap Sequence ### 2.1 Entry Point Chain ``` openclaw.mjs ← shebang binary (#!/usr/bin/env node) │ ├─ module.enableCompileCache() ← Node compile cache for faster startup ├─ import dist/warning-filter.js ← suppress ExperimentalWarning noise └─ import dist/entry.js ← actual entry point │ src/entry.ts ├─ process.title = "openclaw" ├─ installProcessWarningFilter() ├─ normalizeEnv() ← ZAI_API_KEY alias normalization ├─ handle --no-color flag ├─ RESPAWN GUARD: ← re-spawns with --disable-warning=ExperimentalWarning │ if not already respawned if not already in NODE_OPTIONS │ (bounded by OPENCLAW_NODE_OPTIONS_READY=1) ├─ parseCliProfileArgs() ← extracts --profile from argv ├─ applyCliProfileEnv() ← loads ~/.openclaw/profiles/.env └─ dynamic import ./cli/run-main.js → runCli(argv) │ src/cli/run-main.ts ├─ normalizeWindowsArgv(argv) ├─ loadDotEnv({ quiet: true }) ← loads .env, ~/.openclaw/.env ├─ normalizeEnv() ├─ ensureOpenClawCliOnPath() ← ensures `openclaw` is in PATH ├─ assertSupportedRuntime() ← verifies Node >= 22 ├─ tryRouteCli(argv) ← fast-path subcli routing ├─ enableConsoleCapture() ← structured log capture ├─ buildProgram() ← builds Commander tree ├─ installUnhandledRejectionHandler() ├─ registerCoreCliByName() ├─ register plugin CLI commands └─ program.parseAsync(argv) ← dispatches to subcommand ``` ### 2.2 Gateway Startup Sequence When `openclaw gateway run` is invoked, it calls `startGatewayServer()` from `src/gateway/server.impl.ts`: ``` startGatewayServer(port, opts) │ Phase 1: Configuration ├─ readConfigFileSnapshot() ← read ~/.openclaw/openclaw.json (JSON5) ├─ auto-migrate legacy config keys ├─ validate config (Zod schema) ← throws with "run openclaw doctor" if invalid ├─ check OPENCLAW_PLUGIN_* env vars ← auto-enable matching plugins │ Phase 2: Auth Bootstrap ├─ ensureGatewayStartupAuth() ← generate/validate gateway auth token │ Phase 3: Diagnostics ├─ startDiagnosticHeartbeat() ← if diagnostics.enabled │ Phase 4: Registry Init ├─ initSubagentRegistry() ← initialize subagent tracking ├─ loadGatewayPlugins() ← discover and load all extensions │ Phase 5: Server Config ├─ resolveGatewayRuntimeConfig() ← bind host, TLS, auth mode, control UI ├─ create auth rate limiters ├─ resolve control UI asset path ├─ load TLS if enabled │ Phase 6: Server Creation ├─ create Express + ws HTTP/WS server ├─ bind to port 18789 ├─ start canvas host (if enabled) │ Phase 7: Onboarding Check ├─ run interactive setup wizard (if fresh install) │ Phase 8: Sidecars ├─ startGatewaySidecars(): │ ├─ clean stale session lock files │ ├─ start browser control server │ ├─ start Gmail watcher (if hooks.gmail.account configured) │ ├─ load internal hooks from config and discovery dirs │ ├─ start channels (Telegram, Discord, Slack, WhatsApp, etc.) │ │ (skipped if OPENCLAW_SKIP_CHANNELS=1) │ ├─ trigger gateway:startup internal hook │ ├─ start plugin services (background services registered by extensions) │ ├─ reconcile ACP session identities │ ├─ start memory backend (builtin SQLite or qmd) │ └─ schedule restart sentinel wake │ Phase 9: Discovery & Networking ├─ startGatewayDiscovery() ← mDNS and/or wide-area DNS ├─ configure Tailscale exposure │ Phase 10: Watchers & Maintenance ├─ startGatewayConfigReloader() ← file watcher for hot config reload ├─ start channel health monitor ├─ start maintenance timers (session cleanup, update checks) │ Phase 11: Boot Script ├─ check for BOOT.md in workspace ← runs one-shot agent turn if exists │ Phase 12: Ready └─ emit startup log (bound address, auth mode, channels, skills) ``` ### 2.3 Config File Loading Pipeline **Config file location:** `~/.openclaw/openclaw.json` (JSON5 format, supports comments and trailing commas) Override via: `OPENCLAW_CONFIG_PATH` or `CLAWDBOT_CONFIG_PATH` env var. **Loading pipeline** (defined in `src/config/io.ts`): 1. Read raw file (JSON5 parser) 2. Resolve `$include` directives (file includes with circular-include detection) 3. Resolve `${ENV_VAR}` substitutions in string values 4. Apply dotenv fallbacks (shell env import if `env.shellEnv.enabled`) 5. Apply legacy migration if legacy keys detected 6. Validate against Zod schema (`OpenClawSchema` from `src/config/zod-schema.ts`) 7. Apply runtime defaults (model defaults, agent defaults, logging defaults, session defaults) 8. Apply runtime overrides (from `OPENCLAW_RUNTIME_OVERRIDES` env) 9. Apply config env vars (`env.vars`) to `process.env` **Dotenv precedence** (highest → lowest): 1. Process env vars 2. `./.env` (project root) 3. `~/.openclaw/.env` 4. `openclaw.json` `env` block ### 2.4 Environment Variables — Complete Reference #### Paths and State | Variable | Description | Default | Required | |----------|-------------|---------|----------| | `OPENCLAW_STATE_DIR` | State/data directory | `~/.openclaw` | No | | `OPENCLAW_CONFIG_PATH` | Config file path | `$STATE_DIR/openclaw.json` | No | | `OPENCLAW_HOME` | Home directory override | `os.homedir()` | No | | `OPENCLAW_AGENT_DIR` | Agent data directory | `$STATE_DIR/agent` | No | | `OPENCLAW_OAUTH_DIR` | OAuth credentials directory | `$STATE_DIR/credentials` | No | #### Gateway Runtime | Variable | Description | Default | Required | |----------|-------------|---------|----------| | `OPENCLAW_GATEWAY_TOKEN` | Auth token for gateway | (generated) | Yes (if auth=token) | | `OPENCLAW_GATEWAY_PASSWORD` | Auth password | none | Yes (if auth=password) | | `OPENCLAW_GATEWAY_PORT` | Gateway listen port | `18789` | No | | `OPENCLAW_GATEWAY_BIND` | Bind mode: `loopback`/`lan`/`tailnet`/`auto` | `auto` | No | #### Process Control | Variable | Description | Default | |----------|-------------|---------| | `OPENCLAW_NO_RESPAWN` | Skip entry-point respawn | unset | | `OPENCLAW_NODE_OPTIONS_READY` | Already respawned guard | unset | | `OPENCLAW_SKIP_CHANNELS` | Skip starting messaging channels | unset | | `OPENCLAW_SKIP_BROWSER_CONTROL_SERVER` | Skip browser control server | unset | | `OPENCLAW_SKIP_GMAIL_WATCHER` | Skip Gmail watcher startup | unset | | `OPENCLAW_SKIP_CANVAS_HOST` | Skip canvas host server | unset | | `OPENCLAW_SKIP_CRON` | Skip cron service | unset | | `OPENCLAW_DISABLE_CONFIG_CACHE` | Bypass config file cache | unset | | `OPENCLAW_LOAD_SHELL_ENV` | Import login shell environment | unset | | `OPENCLAW_SHELL_ENV_TIMEOUT_MS` | Shell env import timeout | `15000` | | `OPENCLAW_PROFILE` | CLI profile name | unset | | `OPENCLAW_RAW_STREAM` | Enable raw stream logging | unset | | `OPENCLAW_NIX_MODE` | Running under Nix (disables auto-install) | unset | #### Model Provider API Keys | Variable | Provider | |----------|----------| | `ANTHROPIC_API_KEY` | Anthropic Claude | | `OPENAI_API_KEY` | OpenAI | | `GEMINI_API_KEY` / `GOOGLE_API_KEY` | Google Gemini | | `OPENROUTER_API_KEY` | OpenRouter | | `GROQ_API_KEY` | Groq | | `XAI_API_KEY` | xAI (Grok) | | `MISTRAL_API_KEY` | Mistral | | `CEREBRAS_API_KEY` | Cerebras | | `TOGETHER_API_KEY` | Together AI | | `MOONSHOT_API_KEY` / `KIMI_API_KEY` | Moonshot/Kimi | | `NVIDIA_API_KEY` | NVIDIA NIM | | `VENICE_API_KEY` | Venice AI | | `LITELLM_API_KEY` | LiteLLM | | `VOYAGE_API_KEY` | Voyage (embeddings) | | `ZAI_API_KEY` | ZAI (z.ai) | | `MINIMAX_API_KEY` | MiniMax | | `OLLAMA_API_KEY` | Ollama (local) | | `VLLM_API_KEY` | vLLM (local) | | `QIANFAN_API_KEY` | Baidu Qianfan | | `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY` | AWS Bedrock | | `COPILOT_GITHUB_TOKEN` / `GH_TOKEN` | GitHub Copilot | | `HUGGINGFACE_HUB_TOKEN` / `HF_TOKEN` | HuggingFace | | `OPENAI_API_KEYS` / `ANTHROPIC_API_KEYS` / `GEMINI_API_KEYS` | Comma-separated key rotation | #### Channel Tokens | Variable | Channel | |----------|---------| | `TELEGRAM_BOT_TOKEN` | Telegram | | `DISCORD_BOT_TOKEN` | Discord | | `SLACK_BOT_TOKEN` / `SLACK_APP_TOKEN` | Slack | | `MATTERMOST_BOT_TOKEN` / `MATTERMOST_URL` | Mattermost | | `ZALO_BOT_TOKEN` | Zalo | | `OPENCLAW_TWITCH_ACCESS_TOKEN` | Twitch | #### Tools and Media | Variable | Purpose | |----------|---------| | `BRAVE_API_KEY` | Brave Search API | | `PERPLEXITY_API_KEY` | Perplexity search | | `FIRECRAWL_API_KEY` | Firecrawl web scraping | | `ELEVENLABS_API_KEY` / `XI_API_KEY` | ElevenLabs TTS | | `DEEPGRAM_API_KEY` | Deepgram speech recognition | #### Docker-Specific | Variable | Purpose | Default | |----------|---------|---------| | `OPENCLAW_CONFIG_DIR` | Config dir mount target | `~/.openclaw` | | `OPENCLAW_WORKSPACE_DIR` | Workspace mount target | `~/.openclaw/workspace` | | `OPENCLAW_BRIDGE_PORT` | Bridge TCP port | `18790` | | `OPENCLAW_IMAGE` | Docker image name | `openclaw:local` | | `OPENCLAW_EXTRA_MOUNTS` | Extra Docker bind mounts | unset | | `OPENCLAW_HOME_VOLUME` | Named Docker volume for /home/node | unset | | `OPENCLAW_DOCKER_APT_PACKAGES` | Extra apt packages for image build | unset | | `OPENCLAW_INSTALL_BROWSER` | Bake Chromium into main image | unset | ### 2.5 Services/Connections Established at Startup | Service | Connection Type | When | |---------|----------------|------| | Config file | File read (JSON5) | Phase 1 | | SQLite memory DB | File-based DB (`node:sqlite` + `sqlite-vec`) | Phase 8 (memory backend) | | Browser control server | Local HTTP (Playwright + CDP) | Phase 8 | | Gmail watcher | Google OAuth → Gmail API | Phase 8 (if configured) | | Telegram | HTTPS long-poll (grammy) | Phase 8 (if configured) | | Discord | WebSocket (discord.js) | Phase 8 (if configured) | | Slack | WebSocket (Socket Mode via @slack/bolt) | Phase 8 (if configured) | | WhatsApp | WebSocket (Baileys) | Phase 8 (if configured) | | mDNS discovery | UDP multicast | Phase 9 (if configured) | | Tailscale | Local Tailscale API | Phase 9 (if configured) | | Config file watcher | Chokidar (inotify/FSEvents) | Phase 10 | **No external databases** (Postgres, Redis, etc.) are required. OpenClaw uses flat-file JSON/JSONL + embedded SQLite exclusively. ### 2.6 Minimum Viable Config To get OpenClaw running with minimal configuration: ```json5 // ~/.openclaw/openclaw.json { "models": { "providers": { "anthropic": { "apiKey": "${ANTHROPIC_API_KEY}" } } } } ``` ```bash # ~/.openclaw/.env ANTHROPIC_API_KEY=sk-ant-your-key-here OPENCLAW_GATEWAY_TOKEN=your-64-char-hex-token ``` ```bash # Start command openclaw gateway run --bind loopback --port 18789 ``` This gives you: a gateway server with Anthropic Claude as the LLM, no messaging channels, token auth, loopback binding. --- ## 3. Plugin/Extension System ### 3.1 Architecture: Extensions vs Skills vs Hooks OpenClaw has three distinct extension mechanisms: | Mechanism | Type | Runs Code | Location | Purpose | |-----------|------|-----------|----------|---------| | **Extensions** | TypeScript/JS packages | Yes | `extensions/` | Register tools, channels, providers, services, CLI commands, HTTP routes | | **Skills** | Markdown documents | No | `skills/` | Inject knowledge/instructions into agent context window | | **Hooks** | TypeScript/JS modules | Yes | `src/hooks/bundled/`, workspace `hooks/` | React to events (message received, session start, etc.) | ### 3.2 Extension API (Plugin SDK) **Import path:** `openclaw/plugin-sdk` (resolved via Jiti alias at runtime) **Source:** `src/plugin-sdk/index.ts` → re-exports from `src/plugins/types.ts` #### Plugin Definition Interface ```typescript // An extension must default-export one of these: type OpenClawPluginModule = | OpenClawPluginDefinition | ((api: OpenClawPluginApi) => void | Promise); type OpenClawPluginDefinition = { id?: string; name?: string; description?: string; version?: string; kind?: PluginKind; // currently only "memory" configSchema?: OpenClawPluginConfigSchema; register?: (api: OpenClawPluginApi) => void | Promise; activate?: (api: OpenClawPluginApi) => void | Promise; // alias for register }; ``` #### Plugin API — What Extensions Can Do ```typescript type OpenClawPluginApi = { // Identity id: string; name: string; version?: string; source: string; config: OpenClawConfig; pluginConfig?: Record; runtime: PluginRuntime; logger: PluginLogger; // Register an agent tool (direct object or factory function) registerTool(tool: AnyAgentTool | OpenClawPluginToolFactory, opts?: { name?: string; names?: string[]; optional?: boolean; // only included if explicitly allowlisted }): void; // Register event hooks (two styles) registerHook(events: string | string[], handler: InternalHookHandler, opts?: OpenClawPluginHookOptions): void; on(hookName: K, handler: PluginHookHandlerMap[K], opts?: { priority?: number }): void; // Register a messaging channel (Telegram, Discord, etc.) registerChannel(registration: OpenClawPluginChannelRegistration | ChannelPlugin): void; // Register an AI model provider registerProvider(provider: ProviderPlugin): void; // Register HTTP routes on the gateway registerHttpHandler(handler: OpenClawPluginHttpHandler): void; registerHttpRoute(params: { path: string; handler: OpenClawPluginHttpRouteHandler }): void; // Register gateway WebSocket methods registerGatewayMethod(method: string, handler: GatewayRequestHandler): void; // Register CLI commands registerCli(registrar: OpenClawPluginCliRegistrar, opts?: { commands?: string[] }): void; // Register background services registerService(service: OpenClawPluginService): void; // Register slash-style commands (bypass LLM) registerCommand(command: OpenClawPluginCommandDefinition): void; // Resolve paths relative to plugin root resolvePath(input: string): string; }; ``` #### Tool Factory Pattern ```typescript type OpenClawPluginToolFactory = ( ctx: OpenClawPluginToolContext ) => AnyAgentTool | AnyAgentTool[] | null | undefined; type OpenClawPluginToolContext = { config?: OpenClawConfig; workspaceDir?: string; agentDir?: string; agentId?: string; sessionKey?: string; messageChannel?: string; agentAccountId?: string; sandboxed?: boolean; }; ``` #### Tool Interface (from `@mariozechner/pi-agent-core`) ```typescript type AnyAgentTool = AgentTool & { ownerOnly?: boolean; // OpenClaw extension: restrict to owner senders }; // AgentTool has: // name: string // label: string // description: string // parameters: TSchema (TypeBox schema) // execute: (toolCallId: string, args: Input) => Promise> type AgentToolResult = { content: Array< | { type: "text"; text: string } | { type: "image"; data: string; mimeType: string } >; details?: T; }; ``` ### 3.3 Extension Loading & Lifecycle **Loader:** `src/plugins/loader.ts` → `loadOpenClawPlugins(options)` #### Discovery Sequence 1. **Normalize config** — `normalizePluginsConfig(cfg.plugins)` resolves enable state, allow/deny lists 2. **Discover candidates** — scans 4 locations in priority order: - `config` origin: paths from `plugins.loadPaths` in config - `workspace` origin: `/.openclaw/extensions/` - `global` origin: `~/.openclaw/extensions/` - `bundled` origin: compiled-in extensions directory 3. **Load manifests** — reads `openclaw.plugin.json` from each candidate #### Per-Plugin Load Sequence 1. Check for duplicate IDs (workspace/global wins over bundled) 2. **Resolve enable state** — checks `plugins.enabled`, `plugins.allow`, `plugins.deny`, per-plugin `entries[id].enabled` 3. **Security check** — verifies entry file doesn't escape plugin root; checks file ownership on Unix 4. **Load module** via Jiti (supports `.ts`, `.tsx`, `.js`, `.mjs`) 5. Extract `register` function from default export 6. **Validate config** against `configSchema` (JSON Schema via AJV) 7. **Memory slot gating** — only one `kind: "memory"` plugin activates (`plugins.slots.memory` selects) 8. **Call `register(api)`** — synchronously 9. Push to registry #### Enable/Disable Configuration ```json5 { "plugins": { "enabled": true, // master switch "allow": ["slack", "memory-core"], // allowlist (if non-empty, only these load) "deny": [], // blocklist (always blocked) "slots": { "memory": "memory-core" // only one memory plugin active }, "entries": { "slack": { "enabled": true, "config": { /* plugin-specific config */ } } }, "loadPaths": ["/path/to/custom/extensions"] } } ``` ### 3.4 Typed Plugin Hooks (Lifecycle Events) Extensions can register for strongly-typed lifecycle events via `api.on()`: | Hook Name | When Fired | Can Modify? | Return Type | |-----------|------------|-------------|-------------| | `before_model_resolve` | Before LLM model selection | Yes — override model/provider | `{ modelOverride?, providerOverride? }` | | `before_prompt_build` | Before system prompt assembly | Yes — inject context | `{ systemPrompt?, prependContext? }` | | `before_agent_start` | Before agent run (legacy) | Yes — combines above | `{ prependContext?, systemPrompt? }` | | `llm_input` | LLM request payload ready | No (fire-and-forget) | void | | `llm_output` | LLM response received | No (fire-and-forget) | void | | `agent_end` | Conversation turn complete | No (fire-and-forget) | void | | `before_compaction` | Before session compaction | No | void | | `after_compaction` | After compaction | No | void | | `before_reset` | Before /new or /reset | No | void | | `message_received` | Inbound message | No | void | | `message_sending` | Outbound message | Yes — modify or cancel | `{ content?, cancel? }` | | `message_sent` | After send | No | void | | **`before_tool_call`** | **Before tool execution** | **Yes — modify params or BLOCK** | **`{ params?, block?, blockReason? }`** | | **`after_tool_call`** | **After tool execution** | **No (fire-and-forget)** | void | | `tool_result_persist` | Before JSONL write (SYNC) | Yes — modify message | `{ message? }` | | `before_message_write` | Before message JSONL write (SYNC) | Yes — block or modify | void | | `session_start` | New session started | No | void | | `session_end` | Session ended | No | void | | `subagent_spawning` | Subagent about to spawn | Yes — can return error | `{ error? }` | | `subagent_spawned` | Subagent spawned | No | void | | `subagent_ended` | Subagent ended | No | void | | `gateway_start` | Gateway started | No | void | | `gateway_stop` | Gateway stopping | No | void | **Critical for Safety Wrapper:** The `before_tool_call` hook is the primary interception point. It fires before every tool call and can: - Modify the parameters - Block the call entirely with a reason - Observe tool name, params, session context The `after_tool_call` hook provides audit logging capability after execution. ### 3.5 Extension File Structure ``` extensions/my-plugin/ ├── openclaw.plugin.json ← REQUIRED: manifest ├── package.json ← npm package metadata └── index.ts ← entry: default exports OpenClawPluginDefinition ``` **Manifest (`openclaw.plugin.json`):** ```json { "id": "my-plugin", "name": "My Plugin", "description": "What it does", "version": "1.0.0", "configSchema": { "type": "object", "properties": { "apiKey": { "type": "string" } } }, "uiHints": { "apiKey": { "label": "API Key", "sensitive": true } } } ``` **`package.json` entry point:** ```json { "openclaw": { "extensions": ["./index.ts"] } } ``` ### 3.6 Complete Extension Catalog #### Chat Channel Extensions | Extension | Description | Relevance to LetsBe | |-----------|-------------|---------------------| | `discord` | Discord channel plugin | Low — consumer chat | | `slack` | Slack channel plugin | Medium — some SMBs use Slack | | `telegram` | Telegram channel plugin | Low | | `whatsapp` | WhatsApp channel plugin | Medium — business messaging | | `signal` | Signal channel plugin | Low | | `imessage` | iMessage channel plugin | Low | | `msteams` | Microsoft Teams channel plugin | **High** — many SMBs use Teams | | `matrix` | Matrix channel plugin | Low | | `mattermost` | Mattermost channel plugin | Low | | `googlechat` | Google Chat channel plugin | **High** — SMBs on Google Workspace | | `irc` | IRC channel plugin | Low | | `line` | LINE messaging channel plugin | Low | | `feishu` | Feishu/Lark channel plugin | Low | | `bluebubbles` | iMessage via BlueBubbles relay | Low | | `nostr` | Nostr NIP-04 encrypted DMs | Low | | `synology-chat` | Synology Chat channel plugin | Low | | `tlon` | Tlon/Urbit channel plugin | Low | | `twitch` | Twitch channel plugin | Low | | `zalo` / `zalouser` | Zalo channel plugin | Low | | `nextcloud-talk` | Nextcloud Talk channel plugin | Low | #### Memory Extensions | Extension | Description | Relevance to LetsBe | |-----------|-------------|---------------------| | `memory-core` | Built-in file-backed memory search (SQLite + FTS5 + sqlite-vec) | **Critical** — default memory | | `memory-lancedb` | LanceDB vector memory with auto-recall/capture | **High** — advanced RAG | #### Auth Provider Extensions | Extension | Description | Relevance to LetsBe | |-----------|-------------|---------------------| | `copilot-proxy` | GitHub Copilot OAuth provider | Low | | `google-gemini-cli-auth` | Gemini CLI OAuth provider | Medium | | `minimax-portal-auth` | MiniMax Portal OAuth | Low | | `qwen-portal-auth` | Qwen Portal OAuth | Low | #### Tool & Utility Extensions | Extension | Description | Relevance to LetsBe | |-----------|-------------|---------------------| | `llm-task` | Structured JSON LLM tool for workflow automation | **High** — workflow tasks | | `lobster` | Typed workflow tool with resumable approvals | **High** — business workflows | | `open-prose` | OpenProse VM skill pack with /prose command | Low | | `phone-control` | Arm/disarm high-risk phone node commands | Low | | `device-pair` | Setup codes and device pairing approval | Low | | `diagnostics-otel` | OpenTelemetry diagnostics exporter | Medium — observability | | `talk-voice` | Voice selection management | Low | | `voice-call` | Voice call plugin | Low | | `thread-ownership` | Prevents multi-agent collisions in threads | Medium — multi-agent safety | | `acpx` | ACP runtime backend (pinned CLI) | Low | ### 3.7 Skills System Skills are **NOT code**. They are Markdown documents injected into the agent's context window at inference time. #### Skill Anatomy ``` skills/my-skill/ ├── SKILL.md ← REQUIRED: YAML frontmatter + markdown instructions ├── scripts/ ← optional executable scripts ├── references/ ← optional documentation for context └── assets/ ← optional output files ``` #### SKILL.md Frontmatter ```yaml --- name: my-skill description: "What the skill does and when to use it" homepage: https://... metadata: openclaw: emoji: "📧" requires: bins: ["himalaya"] install: - id: brew kind: brew formula: himalaya bins: ["himalaya"] --- (SKILL.md body with agent instructions goes here) ``` #### Three-Level Progressive Disclosure 1. **Metadata** (name + description) — always in context (~100 words) 2. **SKILL.md body** — injected when skill triggers (< 5k words target) 3. **Bundled resources** — loaded by agent as needed (references/, scripts/, assets/) Skills are loaded from `skills/` and workspace directories. The agent reads the SKILL.md and uses the instructions as procedural guidance for invoking CLI tools via the `bash` tool. ### 3.8 Building a Custom Extension (Pseudocode) Here's what a minimal Safety Wrapper extension would look like: ```typescript // extensions/letsbe-safety-wrapper/index.ts import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; const safetyWrapperPlugin = { id: "letsbe-safety-wrapper", name: "LetsBe Safety Wrapper", version: "1.0.0", configSchema: { type: "object", properties: { policyEndpoint: { type: "string" }, strictMode: { type: "boolean" } } }, register(api: OpenClawPluginApi) { // Intercept every tool call BEFORE execution api.on("before_tool_call", async (event, ctx) => { const { toolName, params } = event; // Check against safety policy const decision = await checkSafetyPolicy(toolName, params, api.pluginConfig); if (decision.blocked) { return { block: true, blockReason: decision.reason }; } if (decision.modifiedParams) { return { params: decision.modifiedParams }; } return {}; // allow }, { priority: 1000 }); // high priority = runs first // Audit log every tool call AFTER execution api.on("after_tool_call", async (event) => { await auditLog(event.toolName, event.params, event.result, event.durationMs); }); // Intercept outbound messages api.on("message_sending", async (event) => { const filtered = await contentFilter(event.content); if (filtered.blocked) { return { cancel: true }; } return { content: filtered.content }; }); } }; export default safetyWrapperPlugin; ``` --- ## 4. AI Agent Runtime ### 4.1 Core Architecture The AI agent runtime is the largest subsystem in OpenClaw (~200+ files in `src/agents/`). It is built on top of `@mariozechner/pi-agent-core`, a TypeScript agent SDK that provides the core conversation loop. **Key runtime components:** | Component | Location | Purpose | |-----------|----------|---------| | `pi-embedded-runner/` | `src/agents/pi-embedded-runner/` | Core agent run loop wrapping pi-agent-core | | `model-auth.ts` | `src/agents/model-auth.ts` | Multi-provider API key resolution | | `model-selection.ts` | `src/agents/model-selection.ts` | Model selection and validation | | `models-config.ts` | `src/agents/models-config.ts` | Provider catalog (implicit + explicit) | | `models-config.providers.ts` | `src/agents/models-config.providers.ts` | Built-in provider definitions | | `tool-policy.ts` | `src/agents/tool-policy.ts` | Tool allowlist/denylist enforcement | | `skills.ts` | `src/agents/skills.ts` | Skill loading and prompt injection | | `subagent-registry.ts` | `src/agents/subagent-registry.ts` | Active subagent tracking | | `workspace.ts` | `src/agents/workspace.ts` | Workspace directory management | | `identity.ts` | `src/agents/identity.ts` | Agent identity resolution | | `defaults.ts` | `src/agents/defaults.ts` | Default provider/model | ### 4.2 Supported LLM Providers **Default provider:** `anthropic` **Default model:** `claude-opus-4-6` Defined in `src/agents/defaults.ts` and `src/agents/models-config.providers.ts`: | Provider ID | Models (examples) | Auth Method | |-------------|-------------------|-------------| | `anthropic` | claude-opus-4-6, claude-sonnet-4-6, claude-haiku-4-5 | API key | | `openai` | gpt-5.1-codex, o3, gpt-4o | API key | | `google` | gemini-2.5-pro, gemini-2.5-flash | API key | | `openrouter` | Any model via OpenRouter | API key | | `groq` | llama, mixtral, gemma | API key | | `xai` | grok models | API key | | `mistral` | mistral-large, codestral | API key | | `cerebras` | cerebras models | API key | | `together` | various open models | API key | | `ollama` | any local model | Local (no key) | | `vllm` | any local model | Local (no key) | | `amazon-bedrock` | claude, titan, llama via AWS | AWS credentials | | `google-vertex` | gemini via Vertex AI | Google ADC | | `github-copilot` | copilot models | GitHub OAuth | | `minimax` / `minimax-portal` | MiniMax models | API key / OAuth | | `moonshot` / `kimi-coding` | Kimi models | API key | | `qwen-portal` | Qwen models | OAuth | | `nvidia` | NVIDIA NIM models | API key | | `venice` | Venice AI models | API key | | `litellm` | any model via LiteLLM proxy | API key | | `volcengine` / `byteplus` | ByteDance models | API key | | `qianfan` | Baidu models | API key | | `huggingface` | HF models | Token | | `kilocode` / `opencode` | Specialized models | API key | | `zai` | z.ai models | API key | | `xiaomi` | Mimo models | API key | | `chutes` | Chutes models | OAuth / API key | | `vercel-ai-gateway` / `cloudflare-ai-gateway` | Gateway proxy | API key | | `synthetic` | Testing only | API key | **API key resolution chain** (from `src/agents/model-auth.ts`): 1. Auth profile store (`~/.openclaw/agents//auth-profiles.json`) 2. Environment variables (`ANTHROPIC_API_KEY`, etc.) 3. Config file (`models.providers..apiKey`) 4. AWS SDK (for Bedrock) 5. Key rotation lists (`OPENAI_API_KEYS=sk-1,sk-2`) ### 4.3 Tool/Function Calling #### How Tools Are Registered Tools come from three sources: 1. **Core built-in tools** — defined in `src/agents/tools/` (bash, browser, web_search, etc.) 2. **Plugin tools** — registered via `api.registerTool()` in extensions 3. **Skill-derived tools** — skills inject instructions for using CLI tools via the `bash` tool #### Tool Registration Flow ``` Plugin discovery → loadOpenClawPlugins() → for each plugin: call register(api) → api.registerTool(toolOrFactory, opts) → toolOrFactory stored in registry At agent start → resolvePluginTools() → for each registered tool factory: → call factory(OpenClawPluginToolContext) → returns AnyAgentTool[] or null → merge with core tools → apply tool policy (allowlist/denylist) → pass to pi-agent-core ``` #### Tool Policy Pipeline Defined in `src/agents/tool-policy.ts` and `src/agents/tool-policy-pipeline.ts`: ```typescript // Tools can be controlled via config: { "tools": { "allowlist": ["exec", "web_search", "browser"], // only these tools "denylist": ["sessions_spawn"], // never these tools "groups": { "plugins": true // enable all plugin tools } } } ``` Tool schemas use **TypeBox** (not Zod) for LLM-compatible JSON Schema generation. Important constraint from CLAUDE.md: avoid `Type.Union` — use `stringEnum`/`optionalStringEnum` instead. ### 4.4 Agent Execution Loop The core execution loop lives in `src/agents/pi-embedded-runner/`: ``` User Message Arrives (via channel, HTTP API, or WS) │ ├─ resolveAgentRoute() ← determine which agent handles this ├─ construct session key ← e.g., "agent:main:direct:telegram:12345" │ ├─ Load agent config ← agents.list[agentId] from config ├─ resolveAgentIdentity() ← name, avatar, ack reaction ├─ Load workspace ← MEMORY.md, SYSTEM.md, bootstrap files │ ├─ Resolve model ← model-auth + model-selection ├─ Load skills ← inject SKILL.md content into system prompt ├─ Resolve tools ← core + plugin tools, apply policy │ ├─ Fire "before_model_resolve" hook ├─ Fire "before_prompt_build" hook ├─ Fire "before_agent_start" hook │ ├─ Build system prompt ← agent identity + skills + workspace context ├─ Load session history ← from JSONL transcript file │ ├─ queueEmbeddedPiMessage() ← enqueue message for processing │ │ │ └─ pi-agent-core run loop: │ ├─ Send messages to LLM provider │ ├─ Fire "llm_input" hook │ ├─ Stream response tokens │ ├─ Fire "llm_output" hook │ │ │ ├─ If tool call requested: │ │ ├─ Fire "before_tool_call" hook ← CAN BLOCK OR MODIFY │ │ ├─ Execute tool │ │ ├─ Fire "after_tool_call" hook │ │ ├─ Fire "tool_result_persist" hook (SYNC) │ │ ├─ Write tool result to session JSONL │ │ └─ Loop back to LLM with tool result │ │ │ └─ If text response: │ ├─ Fire "message_sending" hook ← CAN MODIFY OR CANCEL │ ├─ Fire "before_message_write" hook (SYNC) │ ├─ Write to session JSONL │ ├─ Deliver to channel │ └─ Fire "message_sent" hook │ ├─ Fire "agent_end" hook └─ Update session state ``` ### 4.5 Multi-Turn Conversations & Context **Session persistence:** Each conversation session is stored as a JSONL file at `~/.openclaw/agents//sessions/.jsonl`. Each line is a transcript event (user message, assistant message, tool call, tool result). **Session key format:** `agent-////` **DM scope options** (control session isolation): - `"main"` — all DMs share one session - `"per-peer"` — one session per peer across channels - `"per-channel-peer"` — one session per channel+peer - `"per-account-channel-peer"` — maximum isolation **Context window management:** - Session history is loaded from JSONL at each turn - When context exceeds the model's window, **compaction** is triggered - `compactEmbeddedPiSession()` summarizes older messages to fit within limits - `before_compaction` and `after_compaction` hooks fire around this **Memory/RAG:** - The memory backend (SQLite + FTS5 + sqlite-vec) indexes workspace markdown files and session transcripts - `memory_search` tool performs hybrid BM25 + vector similarity search - Auto-recall (via `memory-lancedb` extension) can inject relevant memories before each turn ### 4.6 Agent Configuration Agents are configured in `openclaw.json`: ```json5 { "agents": { "defaults": { "model": "claude-sonnet-4-6", "provider": "anthropic", "sandbox": { "mode": "off", // "off" | "non-main" | "all" "scope": "agent" // "session" | "agent" | "shared" } }, "list": [ { "id": "main", "default": true, "model": "claude-opus-4-6", "systemPrompt": "You are a helpful business assistant.", "tools": { "allowlist": ["exec", "web_search", "browser", "memory_search"] } }, { "id": "researcher", "model": "gemini-2.5-pro", "systemPrompt": "You are a research specialist." } ] } } ``` **Routing** (from `src/routing/resolve-route.ts`) determines which agent handles a message: Resolution tiers (highest priority first): 1. `binding.peer` — exact peer match 2. `binding.peer.parent` — thread parent match 3. `binding.guild+roles` — Discord guild + role 4. `binding.guild` — Discord guild 5. `binding.team` — Slack team 6. `binding.account` — account-level 7. `binding.channel` — channel wildcard 8. `default` — first agent with `default: true` ### 4.7 Subagent System OpenClaw supports spawning child agent sessions: ```typescript // From src/agents/subagent-registry.ts initSubagentRegistry() // initialize at gateway start // From src/agents/subagent-spawn.ts spawnSubagent(opts) // spawn child agent session ``` Subagents are tracked by the registry with depth limits to prevent infinite recursion. Session keys for subagents contain `:subagent:` marker. The `sessions_spawn` tool allows the primary agent to delegate tasks to specialized subagents. --- ## 5. Tool & Integration Catalog ### 5.1 Core Built-in Tools These are always available (subject to tool policy): | Tool | File | What It Does | Protocol/API | |------|------|-------------|--------------| | `exec` | `bash-tools.ts` | Shell command execution | Local shell + optional PTY | | `browser` | `browser-tool.ts` | Full browser automation (navigate, click, type, snapshot, screenshot, PDF) | Playwright + CDP | | `web_search` | `web-search.ts` | Web search via Brave, Perplexity, Grok, Gemini, or Kimi | REST APIs | | `web_fetch` | `web-fetch.ts` | Fetch/scrape URLs (markdown extraction, Firecrawl) | HTTP + Firecrawl API | | `image` | `image-tool.ts` | Describe/analyze images using vision models | LLM vision API | | `memory_search` | `memory-tool.ts` | Semantic memory search (hybrid BM25 + vector) | Local SQLite | | `memory_get` | `memory-tool.ts` | Raw memory file read | Local filesystem | | `message` | `message-tool.ts` | Send/read/react/edit messages across all channels | Channel APIs | | `canvas` | `canvas-tool.ts` | Present HTML on connected Mac/iOS/Android nodes | WebSocket to nodes | | `nodes` | `nodes-tool.ts` | Camera, screen record, location, notifications on connected devices | WebSocket to nodes | | `cron` | `cron-tool.ts` | Create/update/remove scheduled jobs | croner library | | `tts` | `tts-tool.ts` | Text-to-speech output | ElevenLabs, Deepgram, etc. | | `sessions_spawn` | `sessions-spawn-tool.ts` | Spawn sub-agents | Internal | | `sessions_send` | `sessions-send-tool.ts` | Send messages to other sessions | Internal | | `sessions_list` | `sessions-list-tool.ts` | List active sessions | Internal | | `sessions_history` | `sessions-history-tool.ts` | Read session history | Local JSONL | | `session_status` | `session-status-tool.ts` | Current session info | Internal | | `agents_list` | `agents-list-tool.ts` | List available agents | Internal | | `gateway` | `gateway-tool.ts` | Direct gateway method calls | Internal WS | ### 5.2 Google Integration (DETAILED) **Skill:** `skills/gog/SKILL.md` **CLI:** `gog` binary (external Golang CLI wrapping Google Workspace APIs) #### Supported Google APIs | Service | Operations | |---------|-----------| | **Gmail** | Search threads, search messages, send (plain/HTML/body-file), create/send drafts, reply, list attachments | | **Calendar** | List events, create events (with color IDs 1-11), update events, list calendars | | **Drive** | Search files/folders | | **Contacts** | List contacts | | **Sheets** | Get ranges, update cells, append rows, clear ranges, sheet metadata | | **Docs** | Export docs (txt/PDF), cat doc content | #### OAuth Setup ```bash # 1. Register credentials (client_secret.json from Google Cloud Console) gog auth credentials /path/to/client_secret.json # 2. Add account + select scopes gog auth add user@gmail.com --services gmail,calendar,drive,contacts,docs,sheets # 3. Verify gog auth list ``` The OAuth flow uses a standard Google OAuth2 browser redirect. The `gog` CLI handles token storage locally in its own config directory. An optional convenience env var: ```bash export GOG_ACCOUNT=user@gmail.com # avoid repeating --account ``` #### Tool Exposure The `gog` skill does NOT register a structured plugin tool. Instead, it teaches the agent to invoke the `gog` CLI via the built-in `exec` (bash) tool. Command patterns: ```bash gog gmail search 'newer_than:7d' --max 10 gog gmail send --to recipient@example.com --subject "Subject" --body-file - gog calendar events --from 2026-02-26T00:00:00Z --to 2026-02-27T00:00:00Z gog calendar create --summary "Meeting" --from --to --event-color 7 gog drive search "quarterly report" --max 10 gog contacts list --max 20 gog sheets get "Sheet1!A1:D10" --json gog sheets update "Sheet1!A1:B2" --values-json '[["A","B"]]' gog docs export --format txt --out /tmp/doc.txt ``` #### Configuration for LetsBe For each customer VPS, we would need to: 1. Pre-install the `gog` binary 2. Create a Google Cloud project with OAuth credentials 3. Run `gog auth credentials` with the client_secret.json 4. Run `gog auth add` for the customer's Google account 5. Store tokens in the gog config directory within the container **There is also a `goplaces` skill** for Google Places API (New): text search, place details, reviews via a separate `goplaces` binary. ### 5.3 IMAP/Himalaya Email (DETAILED) **Skill:** `skills/himalaya/SKILL.md` **CLI:** `himalaya` binary (external Rust CLI email client) #### Configuration Config file: `~/.config/himalaya/config.toml` ```toml [accounts.personal] email = "user@example.com" display-name = "User Name" default = true backend.type = "imap" backend.host = "imap.example.com" backend.port = 993 backend.encryption.type = "tls" backend.login = "user@example.com" backend.auth.type = "password" backend.auth.cmd = "pass show email/imap" # or keyring command message.send.backend.type = "smtp" message.send.backend.host = "smtp.example.com" message.send.backend.port = 587 message.send.backend.encryption.type = "start-tls" message.send.backend.login = "user@example.com" message.send.backend.auth.cmd = "pass show email/smtp" ``` Supported backends: IMAP, SMTP, Notmuch, Sendmail. Passwords retrieved via `pass`, keyring, or any shell command. #### Operations Exposed to Agent ```bash # Listing / searching himalaya envelope list # INBOX himalaya envelope list --folder "Sent" himalaya envelope list --page 1 --page-size 20 himalaya envelope list from john@example.com subject meeting # Reading himalaya message read 42 himalaya message export 42 --full # raw MIME # Composing / sending himalaya template send < /dev/stdin himalaya message write -H "To:r@e.com" -H "Subject:Hi" "body" # Replying / forwarding himalaya message reply 42 himalaya message reply 42 --all himalaya message forward 42 # Moving / organizing himalaya message move 42 "Archive" himalaya message copy 42 "Important" himalaya message delete 42 himalaya flag add 42 --flag seen # Attachments himalaya attachment download 42 --dir ~/Downloads # Multi-account himalaya --account work envelope list # JSON output himalaya envelope list --output json ``` Message composition supports MML (MIME Meta Language) syntax for rich emails with attachments. #### Configuration for LetsBe For each customer VPS: 1. Pre-install `himalaya` binary 2. Configure `~/.config/himalaya/config.toml` with customer's IMAP/SMTP settings 3. Store email credentials securely (password command or keyring) 4. Test with `himalaya envelope list` ### 5.4 Web Search (Brave Search) **Location:** `src/agents/tools/web-search.ts` Built directly into the core `web_search` tool (no separate skill needed). **Supported providers:** `brave`, `perplexity`, `grok`, `gemini`, `kimi` **Brave Search config:** - Endpoint: `https://api.search.brave.com/res/v1/web/search` - API key from config (`tools.web.search.apiKey`) or `BRAVE_API_KEY` env var - Returns up to 10 results per query - Supports country, language, freshness filters **Tool schema:** ```typescript const WebSearchSchema = Type.Object({ query: Type.String(), count: Type.Optional(Type.Number({ minimum: 1, maximum: 10 })), country: Type.Optional(Type.String()), // "US", "DE" search_lang: Type.Optional(Type.String()), // "en", "de" freshness: Type.Optional(Type.String()), // "pd", "pw", "pm", "py" }); ``` ### 5.5 Browser Automation **Location:** `src/browser/` Full browser automation via Playwright + Chrome DevTools Protocol (CDP). **Browser tool actions:** | Action | What It Does | |--------|-------------| | `status` | Check browser state | | `start` / `stop` | Start/stop browser | | `profiles` | List Chrome profiles | | `tabs` | List open tabs | | `open` | Open new tab | | `focus` / `close` | Focus/close tab | | `navigate` | Go to URL | | `snapshot` | Accessibility tree (aria or ai format) | | `screenshot` | Capture PNG/JPEG | | `console` | Get console messages | | `pdf` | Save as PDF | | `upload` | File upload | | `dialog` | Handle browser dialogs | | `act` | Perform interaction (click, type, press, hover, drag, select, fill, resize, wait, evaluate) | **Security:** Navigation guarded by `navigation-guard.ts` (SSRF policy via `src/infra/net/ssrf.ts`). Content wrapped with `wrapExternalContent()`. ### 5.6 Calendar **No native Cal.com integration exists.** Calendar capabilities come from: 1. **Google Calendar** via the `gog` skill 2. **Apple Calendar/Reminders** via `apple-reminders` skill (macOS only) 3. **Cron tool** for scheduled reminders ### 5.7 Complete Skills Catalog #### Communication / Messaging | Skill | What It Does | Requires | |-------|-------------|----------| | `discord` | Send/read/react/edit messages, polls, threads, search | Discord bot token | | `slack` | Send/read/edit messages, react, pin, member info | Slack bot token | | `bluebubbles` | iMessage via BlueBubbles server | BlueBubbles config | | `imsg` | iMessage/SMS via CLI | `imsg` binary (macOS) | | `wacli` | WhatsApp send + history search | `wacli` binary | | `voice-call` | Start voice calls | voice-call plugin | | `xurl` | X/Twitter: post, reply, DM, search, follow | X API key | #### Google / Productivity | Skill | What It Does | Requires | |-------|-------------|----------| | `gog` | Gmail, Calendar, Drive, Contacts, Sheets, Docs | `gog` binary + OAuth | | `goplaces` | Google Places search, details, reviews | `goplaces` binary | | `gemini` | One-shot Q&A, summaries via Gemini | `gemini` CLI | | `notion` | Notion pages, databases, blocks | Notion API key | | `obsidian` | Obsidian vault read/write | `obsidian-cli` binary | | `apple-notes` | Apple Notes via `memo` | `memo` binary (macOS) | | `apple-reminders` | Apple Reminders management | `remindctl` binary (macOS) | | `bear-notes` | Bear notes management | `grizzly` binary (macOS) | | `things-mac` | Things 3 todos/projects | `things` binary (macOS) | | `trello` | Trello boards, lists, cards | Trello API key/token | #### Email | Skill | What It Does | Requires | |-------|-------------|----------| | `himalaya` | Full IMAP/SMTP email management | `himalaya` binary + TOML config | #### Developer / Code | Skill | What It Does | Requires | |-------|-------------|----------| | `github` | Issues, PRs, CI, code review, API queries | `gh` binary | | `coding-agent` | Delegate coding tasks to Codex/Claude Code agents | `bash` with PTY | | `gh-issues` | Fetch issues, spawn fix agents, open PRs | `gh` binary | | `tmux` | Remote-control tmux sessions | `tmux` binary | #### Media / Audio / Vision | Skill | What It Does | Requires | |-------|-------------|----------| | `openai-whisper` | Local speech-to-text (offline) | `whisper` binary | | `openai-whisper-api` | Speech-to-text via OpenAI API | OpenAI API key | | `sherpa-onnx-tts` | Local TTS (offline) | sherpa-onnx binary | | `sag` | ElevenLabs TTS | `sag` binary | | `video-frames` | Extract frames/clips from video | `ffmpeg` | | `openai-image-gen` | Image generation via OpenAI | OpenAI API key | | `nano-banana-pro` | Image gen/edit via Gemini 3 Pro | Gemini API key | | `peekaboo` | macOS UI capture + automation | Peekaboo app | | `camsnap` | RTSP/ONVIF camera capture | `camsnap` CLI | #### Smart Home / Hardware | Skill | What It Does | Requires | |-------|-------------|----------| | `openhue` | Philips Hue lights/scenes | `openhue` CLI | | `sonoscli` | Sonos speakers | `sonoscli` binary | | `blucli` | BluOS speaker control | `blu` binary | | `eightctl` | Eight Sleep pod control | `eightctl` binary | | `spotify-player` | Spotify playback/search | Spotify account | #### Web / Search / Content | Skill | What It Does | Requires | |-------|-------------|----------| | `summarize` | Summarize/transcribe URLs, podcasts, files | `summarize` binary | | `blogwatcher` | Monitor blogs and RSS/Atom feeds | `blogwatcher` CLI | | `weather` | Weather and forecasts | `curl` (no API key) | #### Security / System | Skill | What It Does | Requires | |-------|-------------|----------| | `1password` | 1Password secrets management | `op` binary | | `healthcheck` | Security audit, firewall, SSH checks | various | | `session-logs` | Search/analyze session logs | `jq`, `rg` | | `model-usage` | Per-model cost/usage summary | `codexbar` CLI | #### Agent / Platform | Skill | What It Does | Requires | |-------|-------------|----------| | `canvas` | Display HTML on connected nodes | Canvas host | | `clawhub` | Search/install/publish skills | `clawhub` npm CLI | | `skill-creator` | Create/update agent skills | — | | `mcporter` | MCP server bridge | `mcporter` CLI | | `nano-pdf` | Edit PDFs with natural language | `nano-pdf` CLI | ### 5.8 Tool Execution & Sandboxing **Tool execution flow:** 1. LLM generates tool call request 2. `before_tool_call` plugin hook fires (can block or modify) 3. Tool's `execute()` function runs in the gateway Node.js process 4. For shell commands (`exec` tool): execution happens either locally or in a Docker sandbox container 5. `after_tool_call` plugin hook fires 6. Result returned to LLM **Sandbox modes** (from `agents.defaults.sandbox.mode`): - `"off"` — no sandboxing (default) - `"non-main"` — sandbox non-main sessions only - `"all"` — every session sandboxed **When sandboxed,** tool calls that execute shell commands run inside Docker containers with hardened defaults (see Section 7 for container details). ### 5.9 Media Understanding Pipeline **Location:** `src/media-understanding/` Multi-provider AI pipeline for understanding audio, video, and images: | Capability | Providers | |-----------|-----------| | Audio transcription | Groq (Whisper), OpenAI (Whisper), Google (Gemini), Deepgram | | Image description | OpenAI (GPT-4o vision), Anthropic (Claude vision), Google (Gemini), Mistral, Moonshot | | Video description | Google (Gemini — native video support) | Provider auto-selection cascades from the primary LLM provider if it supports the capability, falling back to available alternatives. --- ## 6. Data & Storage ### 6.1 Storage Architecture OpenClaw uses **no external databases**. All data is stored as flat files and embedded SQLite: ``` ~/.openclaw/ ← OPENCLAW_STATE_DIR ├── openclaw.json ← Main config (JSON5) ├── credentials/ ← OAuth tokens, provider auth ├── identity/ │ └── device.json ← Ed25519 keypair + deviceId ├── pairing/ ← Node pairing state ├── agents/ │ └── / │ ├── auth-profiles.json ← Per-agent auth profiles │ ├── sessions/ │ │ └── .jsonl ← Conversation transcripts │ └── memory.db ← SQLite memory index (if builtin) ├── workspace/ ← Agent workspace │ ├── MEMORY.md ← Persistent memory (markdown) │ └── memory/ ← Additional memory files ├── sandboxes/ ← Sandbox workspaces ├── extensions/ ← User-installed extensions ├── hooks/ ← User hooks └── .env ← Environment variable overrides ``` ### 6.2 Data Model #### Conversations and Messages Sessions are stored as JSONL files. Each line is a transcript event: ```jsonl {"type":"user","content":"What's on my calendar today?","timestamp":"2026-02-26T10:00:00Z","channel":"telegram","peer":{"id":"123","kind":"direct"}} {"type":"assistant","content":"Let me check your calendar.","timestamp":"2026-02-26T10:00:01Z"} {"type":"tool_call","id":"tc_1","name":"exec","params":{"command":"gog calendar events primary --from 2026-02-26T00:00:00Z --to 2026-02-27T00:00:00Z"}} {"type":"tool_result","id":"tc_1","content":[{"type":"text","text":"Meeting at 2pm: Team sync"}]} {"type":"assistant","content":"You have a meeting at 2pm: Team sync.","timestamp":"2026-02-26T10:00:05Z"} ``` #### Session Keys Format: `agent-////` Special prefixes: - `cron:*` — cron run sessions - `subagent:*` — sub-agent sessions - `acp:*` — ACP sessions #### Users/Agents Agents are defined in config (`agents.list[]`). There is no separate user database — OpenClaw's trust model is single-operator (one trusted user per gateway). ### 6.3 Credentials Storage | What | Where | Format | |------|-------|--------| | Gateway auth token | `openclaw.json` → `gateway.auth.token` or env `OPENCLAW_GATEWAY_TOKEN` | Hex string | | LLM provider API keys | `openclaw.json` → `models.providers..apiKey` or env vars | String | | OAuth tokens | `~/.openclaw/credentials/` directory | JSON files | | Per-agent auth profiles | `~/.openclaw/agents//auth-profiles.json` | JSON | | Device identity | `~/.openclaw/identity/device.json` | Ed25519 keypair | **Security concern:** Config file `openclaw.json` may contain plaintext API keys. The security audit (`src/security/audit.ts`) checks for world-readable permissions. ### 6.4 Memory/Knowledge Persistence #### Built-in Memory Backend (`src/memory/`) Uses Node.js experimental `node:sqlite` module with extensions: | Component | Technology | Purpose | |-----------|-----------|---------| | Full-text search | SQLite FTS5 (`chunks_fts` table) | BM25 keyword search | | Vector similarity | `sqlite-vec` extension (`chunks_vec` table) | Cosine similarity search | | Embedding cache | SQLite table (`embedding_cache`) | Avoid re-embedding | **Embedding providers:** OpenAI, Gemini, Voyage, Mistral, local (llama.cpp via `node-llama`) **Search strategy:** Hybrid BM25 keyword + cosine vector similarity with MMR (Maximal Marginal Relevance) reranking and temporal decay scoring. **Sources indexed:** 1. Workspace markdown files (`MEMORY.md`, `memory/*.md`) 2. Session JSONL transcripts **`MemoryIndexManager`** manages per-agent SQLite DBs with: - Batch embedding with configurable concurrency - File watching via chokidar for incremental re-indexing - Snippet max: 700 chars per chunk - Max batch failures before lockout: 2 #### memory-core Extension The `memory-core` extension (default) wraps the built-in memory backend, exposing `memory_search` and `memory_get` tools plus CLI commands. #### memory-lancedb Extension Alternative memory backend using LanceDB (vector database) with: - OpenAI embeddings (text-embedding-3-small or text-embedding-3-large) - Auto-capture from user messages - Auto-recall: inject relevant memories before each agent turn - Tools: `memory_recall`, `memory_store`, `memory_forget` ### 6.5 Temp File Management **Location:** `src/infra/tmp-openclaw-dir.ts` - Preferred: `/tmp/openclaw` (validated: writable, owned by current user, not group/world writable) - Fallback: `os.tmpdir()/openclaw` or `openclaw-` - Used for media handoff between host and sandbox --- ## 7. Deployment & Configuration ### 7.1 Docker Images OpenClaw ships four Dockerfiles: #### Main Gateway Image (`Dockerfile`) ```dockerfile # Base: node:22-bookworm (pinned by digest) # Build process: # 1. Install Bun (required for build scripts) # 2. Enable corepack (pnpm) # 3. pnpm install --frozen-lockfile (NODE_OPTIONS=--max-old-space-size=2048) # 4. Optional: OPENCLAW_INSTALL_BROWSER=1 bakes Chromium + Playwright (~300MB extra) # 5. pnpm build && pnpm ui:build # 6. Runs as non-root 'node' user (uid 1000) # CMD: node openclaw.mjs gateway --allow-unconfigured # Default bind: 127.0.0.1 (loopback) # For containers: override CMD with --bind lan ``` **Key build args:** - `OPENCLAW_DOCKER_APT_PACKAGES` — extra apt packages - `OPENCLAW_INSTALL_BROWSER=1` — bake Chromium into the image #### Sandbox Image (`Dockerfile.sandbox`) ```dockerfile # Base: debian:bookworm-slim (pinned by digest) # Installs: bash, ca-certificates, curl, git, jq, python3, ripgrep # Creates 'sandbox' user (non-root) # CMD: sleep infinity (stays alive for exec injection) # Image name: openclaw-sandbox:bookworm-slim ``` Minimal container for executing agent shell commands in isolation. #### Sandbox Browser Image (`Dockerfile.sandbox-browser`) ```dockerfile # Base: debian:bookworm-slim (pinned by digest) # Installs: bash, ca-certificates, chromium, curl, fonts-liberation, # fonts-noto-color-emoji, git, jq, novnc, python3, socat, # websockify, x11vnc, xvfb # Exposes: # 9222 — Chrome DevTools Protocol (CDP) # 5900 — VNC # 6080 — noVNC web viewer # CMD: openclaw-sandbox-browser (entrypoint script) # Image name: openclaw-sandbox-browser:bookworm-slim ``` Browser-enabled sandbox with Chromium, virtual display (Xvfb), VNC, and noVNC. #### Extended Sandbox Image (`Dockerfile.sandbox-common`) ```dockerfile # Base: openclaw-sandbox:bookworm-slim (parameterized) # Adds: nodejs, npm, python3, golang-go, rustc, cargo, pnpm, Bun, Homebrew Linux # Sets PATH to include Bun (/opt/bun) and Homebrew bins # Image name: openclaw-sandbox-common:bookworm-slim ``` Full development environment for agents that need to build/run code. ### 7.2 Docker Compose **File:** `docker-compose.yml` Two services: ```yaml services: openclaw-gateway: image: ${OPENCLAW_IMAGE:-openclaw:local} environment: - HOME - TERM - OPENCLAW_GATEWAY_TOKEN - CLAUDE_AI_SESSION_KEY - CLAUDE_WEB_SESSION_KEY - CLAUDE_WEB_COOKIE volumes: - ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw - ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace ports: - "${OPENCLAW_GATEWAY_PORT:-18789}:18789" # Gateway HTTP + WS - "${OPENCLAW_BRIDGE_PORT:-18790}:18790" # Bridge (legacy) command: node dist/index.js gateway --bind ${OPENCLAW_GATEWAY_BIND:-lan} --port 18789 init: true restart: unless-stopped openclaw-cli: # Same image, stdin/tty, no ports entrypoint: node dist/index.js stdin_open: true tty: true ``` ### 7.3 Docker Setup Script (`docker-setup.sh`) Step-by-step operations: 1. **Token resolution:** Reads `gateway.auth.token` from `~/.openclaw/openclaw.json` (via Python3 or Node), or generates a new 64-char hex token via `openssl rand -hex 32` 2. **Path validation:** Validates mount paths (no whitespace, no control chars), validates named volume names 3. **Directory creation:** Creates `~/.openclaw`, `~/.openclaw/workspace`, `~/.openclaw/identity` 4. **Write `.env`:** Writes all config variables to `.env` file via `upsert_env`: - `OPENCLAW_CONFIG_DIR`, `OPENCLAW_WORKSPACE_DIR` - `OPENCLAW_GATEWAY_PORT` (default 18789), `OPENCLAW_BRIDGE_PORT` (default 18790) - `OPENCLAW_GATEWAY_BIND` (default `lan`) - `OPENCLAW_GATEWAY_TOKEN`, `OPENCLAW_IMAGE` - `OPENCLAW_EXTRA_MOUNTS`, `OPENCLAW_HOME_VOLUME`, `OPENCLAW_DOCKER_APT_PACKAGES` 5. **Build or pull image:** If `IMAGE_NAME=openclaw:local`: `docker build`. Otherwise: `docker pull` 6. **Onboarding:** `docker compose run --rm openclaw-cli onboard --no-install-daemon` 7. **CORS config:** Configures `gateway.controlUi.allowedOrigins` for non-loopback binds 8. **Start:** `docker compose up -d openclaw-gateway` ### 7.4 Sandbox Architecture **How sandboxing works** (from `src/process/supervisor/`): The gateway stays on the host. Tool execution (shell commands) can be isolated inside Docker containers. **Configuration:** ```json5 { "agents": { "defaults": { "sandbox": { "mode": "off", // "off" | "non-main" | "all" "scope": "agent", // "session" | "agent" | "shared" "docker": { "readOnlyRoot": true, "tmpfs": ["/tmp", "/var/tmp", "/run"], "network": "none", "user": "1000:1000", "capDrop": ["ALL"], "pidsLimit": 256, "memory": "1g", "memorySwap": "2g", "cpus": 1, "ulimits": { "nofile": { "soft": 1024, "hard": 2048 }, "nproc": 256 } } } } } } ``` **Scope options:** - `"session"` — one container per session - `"agent"` — one container per agent (default) - `"shared"` — one container for all sessions (no cross-session isolation) **Workspace access:** - `"none"` (default) — sandbox uses `~/.openclaw/sandboxes`; agent workspace not visible - `"ro"` — agent workspace read-only at `/agent` - `"rw"` — agent workspace read/write at `/workspace` **Networking:** `network: "none"` by default. `host` blocked. `container:` blocked. **Container lifecycle:** Gateway spawns containers on-demand; reused per scope. Auto-pruned after idle >24h or age >7 days. **Default tool allow/deny in sandbox:** - Allow: `exec`, `process`, `read`, `write`, `edit`, `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn`, `session_status` - Deny: `browser`, `canvas`, `nodes`, `cron`, `discord`, `gateway` **Process Supervisor** (`src/process/supervisor/supervisor.ts`): `createProcessSupervisor()` manages ManagedRun instances with UUID tracking and state machine (`starting` → `exiting`). ### 7.5 Ports | Port | Service | Default Bind | Notes | |------|---------|--------------|-------| | **18789** | Gateway (HTTP + WS multiplexed) | `127.0.0.1` | All API endpoints | | **18790** | Bridge (legacy TCP) | configured | Deprecated | | **9222** | Chromium CDP | sandbox-browser | Chrome DevTools Protocol | | **5900** | VNC | sandbox-browser | x11vnc | | **6080** | noVNC | sandbox-browser | Web-based VNC viewer | **Bind modes:** - `"loopback"` → `127.0.0.1` (most secure, default) - `"lan"` → `0.0.0.0` (all interfaces) - `"tailnet"` → Tailscale IPv4 address - `"auto"` → prefer loopback, else LAN ### 7.6 Volumes | Volume | Container Path | Purpose | |--------|---------------|---------| | `${OPENCLAW_CONFIG_DIR}` | `/home/node/.openclaw` | Config, credentials, sessions, memory | | `${OPENCLAW_WORKSPACE_DIR}` | `/home/node/.openclaw/workspace` | Agent workspace files | | `${OPENCLAW_EXTRA_MOUNTS}` | various | Additional bind mounts | ### 7.7 Minimum System Requirements Based on Dockerfile analysis and runtime characteristics: | Resource | Minimum | Recommended | |----------|---------|-------------| | **RAM** | 1 GB | 2-4 GB (with browser: 4 GB) | | **CPU** | 1 vCPU | 2 vCPU | | **Disk** | 2 GB (base image) | 5-10 GB (with browser + tools) | | **Node.js** | 22.12.0+ | Latest 22.x LTS | | **Docker** | 20.10+ | Latest stable | The build process sets `NODE_OPTIONS=--max-old-space-size=2048` to reduce OOM on small VMs. The `OPENCLAW_INSTALL_BROWSER=1` build arg adds ~300MB for Chromium. ### 7.8 Single-Command VPS Deployment Based on `docker-setup.sh` and `docs/install/docker.md`: ```bash # On a fresh VPS with Docker installed: git clone https://github.com/openclaw/openclaw.git cd openclaw bash docker-setup.sh ``` This will: 1. Generate an auth token 2. Create the config directory 3. Build the Docker image 4. Run interactive onboarding 5. Start the gateway For non-interactive deployment: ```bash # Pre-configure mkdir -p ~/.openclaw cat > ~/.openclaw/openclaw.json << 'EOF' { "models": { "providers": { "anthropic": { "apiKey": "${ANTHROPIC_API_KEY}" } } } } EOF # Set env vars export ANTHROPIC_API_KEY=sk-ant-... export OPENCLAW_GATEWAY_TOKEN=$(openssl rand -hex 32) export OPENCLAW_GATEWAY_BIND=lan # Build and start docker build -t openclaw:local . docker compose up -d openclaw-gateway ``` ### 7.9 Daemon/Service Mode For non-Docker deployments, OpenClaw can be installed as a system service: | Platform | Service Type | Install Method | |----------|-------------|----------------| | **macOS** | LaunchAgent | `launchctl` via `src/daemon/launchd.ts` | | **Linux** | systemd user unit | `systemctl --user` via `src/daemon/systemd.ts` | | **Windows** | Scheduled Task | `schtasks.exe` via `src/daemon/schtasks.ts` | CLI: `openclaw daemon install/uninstall/start/stop/restart/status` --- ## 8. API Surface ### 8.1 HTTP Endpoints All served on port **18789** (same as WebSocket): | Endpoint | Method | Auth | Purpose | |----------|--------|------|---------| | `POST /tools/invoke` | POST | Bearer token | Invoke any agent tool directly | | `POST /v1/chat/completions` | POST | Bearer token | OpenAI-compatible chat API | | `POST /v1/responses` | POST | Bearer token | OpenAI Responses API compatible | | `GET /__openclaw__/canvas/*` | GET | Bearer or node WS | Canvas host | | `GET /__openclaw__/a2ui/*` | GET | Bearer or local | A2UI host | | `ALL /api/channels/*` | ALL | Bearer token | Plugin channel HTTP routes | | `POST /hooks/*` | POST | Hook token | Webhook receivers | | `ALL /slack/*` | ALL | Slack signing secret | Slack HTTP events | | `GET /` (Control UI) | GET | None (assets) / Device auth (WS) | Built React web UI | ### 8.2 Tool Invocation API **`POST /tools/invoke`** (from `src/gateway/tools-invoke-http.ts`) ```http POST /tools/invoke Authorization: Bearer Content-Type: application/json { "tool": "exec", "action": "run", "args": { "command": "echo hello" }, "sessionKey": "agent:main:direct:api:user1", "dryRun": false } ``` **Response:** ```json { "ok": true, "result": { "content": [{ "type": "text", "text": "hello\n" }] } } ``` - Max body: 2 MB - Applies full tool policy pipeline - Hard default deny list: `sessions_spawn`, `sessions_send`, `gateway`, `whatsapp_login` - Status codes: 200, 400, 401, 404, 405, 429, 500 ### 8.3 OpenAI-Compatible Chat API **`POST /v1/chat/completions`** (from `src/gateway/openai-http.ts`) ```http POST /v1/chat/completions Authorization: Bearer Content-Type: application/json { "model": "openclaw:main", "messages": [ { "role": "user", "content": "What's on my calendar today?" } ], "stream": true } ``` - Agent routing via `model` field (`openclaw:`) or `x-openclaw-agent-id` header - Session key via `x-openclaw-session-key` header - SSE streaming: `Content-Type: text/event-stream`, ends with `data: [DONE]` - Non-streaming returns standard OpenAI response format **This is the primary API for integrating with OpenClaw programmatically.** Any OpenAI-compatible client library can be pointed at the gateway. ### 8.4 WebSocket API (Gateway Protocol) All WebSocket methods on port 18789 via the `wss` server. Key method groups: #### Core | Method | Purpose | |--------|---------| | `health` | Health check | | `status` | System status | | `logs.tail` | Stream gateway logs | #### Chat & Agent | Method | Purpose | |--------|---------| | `send` | Send message to agent | | `agent` | Run agent task | | `agent.wait` | Wait for agent completion | | `chat.send` | Send chat message | | `chat.history` | Get chat history | | `chat.abort` | Abort active run | #### Configuration | Method | Purpose | |--------|---------| | `config.get` | Read config | | `config.set` | Set config key | | `config.apply` | Apply full config | | `config.patch` | Patch config | | `config.schema` | Get config schema | #### Sessions | Method | Purpose | |--------|---------| | `sessions.list` | List sessions | | `sessions.preview` | Preview session | | `sessions.patch` | Update session | | `sessions.reset` | Reset session | | `sessions.delete` | Delete session | | `sessions.compact` | Compact session | #### Agents | Method | Purpose | |--------|---------| | `agents.list` | List agents | | `agents.create` | Create agent | | `agents.update` | Update agent | | `agents.delete` | Delete agent | | `agents.files.*` | Manage agent files | #### Management | Method | Purpose | |--------|---------| | `models.list` | List available models | | `tools.catalog` | List available tools | | `skills.status` | Skill status | | `skills.install` | Install skill | | `cron.list/add/update/remove/run` | Manage cron jobs | | `node.pair.*` | Node pairing | | `device.pair.*` | Device pairing | | `exec.approvals.*` | Exec approval management | | `browser.request` | Browser control | | `tts.*` | TTS management | | `usage.status/cost` | Usage tracking | | `wizard.*` | Onboarding wizard | | `update.run` | Trigger update | ### 8.5 Authentication **Auth modes** (from `src/gateway/auth.ts`): | Mode | How It Works | Config | |------|-------------|--------| | `"token"` | Bearer token in `Authorization` header. Constant-time comparison. | `gateway.auth.token` or `OPENCLAW_GATEWAY_TOKEN` env | | `"password"` | Password-based. | `gateway.auth.password` or `OPENCLAW_GATEWAY_PASSWORD` env | | `"trusted-proxy"` | Delegates to reverse proxy via header (e.g., `x-forwarded-user`). Validates request from `gateway.trustedProxies` IPs. | `gateway.auth.trustedProxy` config | | `"none"` | No auth (dangerous) | Explicit config | **Tailscale auth:** When `allowTailscale: true` and `tailscaleMode: "serve"`, validates via `tailscale whois` API. **Rate limiting** (from `src/gateway/auth-rate-limit.ts`): - Configurable: `gateway.auth.rateLimit.{maxAttempts, windowMs, lockoutMs}` - Hook auth hard limit: 20 failures per 60s - Returns `429 Too Many Requests` with `Retry-After` header **Device auth:** Each gateway host generates an Ed25519 keypair at `~/.openclaw/identity/device.json`. The Control UI browser must be approved as a known device. ### 8.6 Gateway Documentation Additional API docs in the repo: - `docs/gateway/authentication.md` — OAuth/API key setup, key rotation - `docs/gateway/network-model.md` — Network architecture - `docs/gateway/tools-invoke-http-api.md` — `/tools/invoke` endpoint - `docs/gateway/openai-http-api.md` — `/v1/chat/completions` endpoint - `docs/gateway/openresponses-http-api.md` — `/v1/responses` endpoint - `docs/gateway/trusted-proxy-auth.md` — Reverse proxy auth - `docs/gateway/sandboxing.md` — Sandbox architecture - `docs/gateway/tailscale.md` — Tailscale integration --- ## 9. Security Model ### 9.1 Trust Model From `SECURITY.md`: OpenClaw operates as a **"personal assistant"** — one trusted operator per gateway, NOT multi-tenant. Key implications: - Authenticated gateway callers are treated as **fully trusted operators** - Session identifiers (`sessionKey`) are routing controls, NOT per-user auth boundaries - The gateway does not implement per-user authorization - `agents.defaults.sandbox.mode` defaults to `"off"` **This means:** For LetsBe's multi-tenant use case, each customer MUST get their own isolated gateway instance. You cannot serve multiple customers from a single gateway. ### 9.2 Security Audit System **Location:** `src/security/audit.ts` `runSecurityAudit()` checks for: | Check | Severity | What It Detects | |-------|----------|----------------| | `gateway.bind_no_auth` | CRITICAL | Non-loopback bind without auth token | | `gateway.loopback_no_auth` | CRITICAL | Loopback bind without auth (proxy risk) | | `gateway.control_ui.allowed_origins_required` | CRITICAL | Non-loopback Control UI without CORS origins | | `gateway.token_too_short` | WARN | Token < 24 chars | | `gateway.auth_no_rate_limit` | WARN | No rate limiting on non-loopback | | `gateway.tailscale_funnel` | CRITICAL | Public Tailscale Funnel exposure | | `gateway.tools_invoke_http.dangerous_allow` | CRITICAL/WARN | Dangerous tools re-enabled over HTTP | | `gateway.trusted_proxy_auth` | CRITICAL | Trusted-proxy auth mode issues | | `fs.state_dir.perms_world_writable` | CRITICAL | `~/.openclaw` world-writable | | `fs.config.perms_world_readable` | CRITICAL | `openclaw.json` world-readable | | `tools.exec.safe_bins_interpreter_unprofiled` | WARN | Shell interpreters in safeBins | | `browser.control_no_auth` | CRITICAL | Browser control without auth | | `logging.redact_off` | WARN | Logging redaction disabled | | `tools.elevated.allowFrom.*.wildcard` | CRITICAL | Wildcard in elevated exec allowlist | | `discovery.mdns_full_mode` | WARN/CRITICAL | mDNS leaking host metadata | **Run via:** `openclaw security audit` ### 9.3 Secrets Management | Secret Type | Storage | Protection | |-------------|---------|-----------| | Gateway token | Config or env var | File permissions check | | LLM API keys | Config, env, or auth profiles | env var interpolation (`${VAR}`) | | OAuth tokens | `~/.openclaw/credentials/` | File permissions | | Channel tokens | Config or env vars | env var interpolation | | Device keypair | `~/.openclaw/identity/device.json` | File permissions | **Credential resolution precedence** (from `src/gateway/credentials.ts`): - Configurable as `env-first` or `config-first` - Legacy env vars supported: `CLAWDBOT_GATEWAY_TOKEN`, `CLAWDBOT_GATEWAY_PASSWORD` ### 9.4 Sandboxing for Tool Execution When `agents.defaults.sandbox.mode` is enabled: **Container hardening defaults:** ```json5 { "readOnlyRoot": true, "tmpfs": ["/tmp", "/var/tmp", "/run"], "network": "none", "user": "1000:1000", "capDrop": ["ALL"], "pidsLimit": 256, "memory": "1g", "memorySwap": "2g", "cpus": 1, "ulimits": { "nofile": { "soft": 1024, "hard": 2048 }, "nproc": 256 } } ``` **Network isolation:** `network: "none"` by default. `host` and `container:` modes are blocked. **Break-glass:** `dangerouslyAllowContainerNamespaceJoin: true` can be set for edge cases. ### 9.5 Attack Surfaces For LetsBe deployments, key attack surfaces to be aware of: | Surface | Risk | Mitigation | |---------|------|-----------| | **Gateway HTTP/WS API** | Unauthorized access if token leaked | Strong token + rate limiting + bind to loopback behind reverse proxy | | **LLM prompt injection** | Agent executing malicious tool calls | Safety Wrapper (our integration), tool policy, sandbox | | **Tool execution** | Arbitrary command execution | Sandbox mode, tool allowlist/denylist | | **Config file** | API keys in plaintext | File permissions, env var interpolation | | **Browser automation** | SSRF, data exfiltration | Navigation guard, SSRF policy | | **Channel tokens** | Messaging channel compromise | Env vars, not plaintext config | | **Memory/RAG** | Data leakage across sessions | Single-operator model (one gateway per customer) | | **Webhook endpoints** | Unauthorized hook triggers | Hook tokens, rate limiting | ### 9.6 Where to Insert a Proxy Layer Based on the architecture, there are four insertion points for our Safety Wrapper: 1. **Plugin `before_tool_call` hook** (RECOMMENDED) — intercepts every tool call before execution 2. **HTTP API proxy** — reverse proxy in front of `/tools/invoke` and `/v1/chat/completions` 3. **Custom tool wrappers** — replace built-in tools with wrapped versions 4. **`message_sending` hook** — filter outbound messages before delivery The `before_tool_call` hook is the cleanest integration point because it: - Runs inside the same process (low latency) - Has access to full context (session, agent, config) - Can block or modify any tool call - Doesn't require forking the codebase - Is the officially supported extension mechanism --- ## 10. Integration Points for LetsBe Safety Wrapper ### 10.1 Primary Interception Point: `before_tool_call` Hook The `before_tool_call` typed plugin hook is the **single best integration point** for the Safety Wrapper. It fires before every tool call in the agent execution loop. **Hook signature:** ```typescript api.on("before_tool_call", async (event, ctx) => { // event contains: // toolName: string — e.g., "exec", "web_search", "message" // params: Record — the tool call arguments // sessionKey: string — identifies the conversation // agentId: string — which agent is running // // Return options: // { } — allow the call // { params: modified } — allow with modified parameters // { block: true, blockReason: "..." } — block the call entirely return {}; }, { priority: 1000 }); // higher priority = runs first ``` **What it can intercept:** - Shell command execution (`exec` tool) — inspect the command string - Web searches (`web_search`) — inspect query terms - Web fetches (`web_fetch`) — inspect target URLs - Browser automation (`browser`) — inspect navigation targets and actions - Message sending (`message`) — inspect outbound messages - File operations — inspect paths - Memory operations — inspect search queries - Subagent spawning (`sessions_spawn`) — control delegation ### 10.2 Secondary Interception Points | Hook | Purpose for Safety Wrapper | |------|---------------------------| | `after_tool_call` | Audit logging — record every tool call with result and duration | | `message_sending` | Content filtering — modify or block outbound messages | | `before_message_write` | PII scrubbing — filter data before it's persisted to JSONL | | `tool_result_persist` | Redaction — scrub sensitive data from tool results before persistence | | `before_prompt_build` | Inject safety instructions into the system prompt | | `subagent_spawning` | Control/limit subagent creation | | `llm_input` / `llm_output` | Observe all LLM traffic for monitoring | ### 10.3 Can We Use the Extension System? YES The extension system is the recommended approach. A Safety Wrapper extension would: 1. Live in `extensions/letsbe-safety-wrapper/` (or be installed globally at `~/.openclaw/extensions/`) 2. Register `before_tool_call` with highest priority (runs first) 3. Register `after_tool_call` for audit logging 4. Register `message_sending` for content filtering 5. Optionally register an HTTP route for external policy API 6. Optionally register a background service for heartbeat/telemetry ### 10.4 Minimal Safety Wrapper Extension ```typescript // extensions/letsbe-safety-wrapper/index.ts import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; interface SafetyPolicy { blockedCommands: RegExp[]; blockedUrls: RegExp[]; blockedTools: string[]; maxExecTimeoutMs: number; auditEndpoint?: string; } function loadPolicy(config: Record): SafetyPolicy { return { blockedCommands: (config.blockedCommandPatterns as string[] || []).map(p => new RegExp(p, "i")), blockedUrls: (config.blockedUrlPatterns as string[] || []).map(p => new RegExp(p, "i")), blockedTools: config.blockedTools as string[] || [], maxExecTimeoutMs: (config.maxExecTimeoutMs as number) || 30000, auditEndpoint: config.auditEndpoint as string | undefined, }; } const safetyWrapper = { id: "letsbe-safety-wrapper", name: "LetsBe Safety Wrapper", version: "1.0.0", configSchema: { type: "object", properties: { blockedCommandPatterns: { type: "array", items: { type: "string" } }, blockedUrlPatterns: { type: "array", items: { type: "string" } }, blockedTools: { type: "array", items: { type: "string" } }, maxExecTimeoutMs: { type: "number" }, auditEndpoint: { type: "string" }, piiRedactionEnabled: { type: "boolean" }, }, }, register(api: OpenClawPluginApi) { const policy = loadPolicy(api.pluginConfig || {}); // INTERCEPT: Block dangerous tool calls api.on("before_tool_call", async (event) => { const { toolName, params } = event; // Block entire tools if (policy.blockedTools.includes(toolName)) { return { block: true, blockReason: `Tool '${toolName}' is disabled by safety policy` }; } // Block dangerous shell commands if (toolName === "exec" && typeof params.command === "string") { for (const pattern of policy.blockedCommands) { if (pattern.test(params.command)) { return { block: true, blockReason: `Command blocked by safety policy: ${pattern}` }; } } } // Block dangerous URLs if ((toolName === "web_fetch" || toolName === "browser") && typeof params.url === "string") { for (const pattern of policy.blockedUrls) { if (pattern.test(params.url)) { return { block: true, blockReason: `URL blocked by safety policy` }; } } } return {}; // allow }, { priority: 10000 }); // highest priority — runs before all other hooks // AUDIT: Log every tool call api.on("after_tool_call", async (event) => { if (policy.auditEndpoint) { fetch(policy.auditEndpoint, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ timestamp: new Date().toISOString(), toolName: event.toolName, params: event.params, durationMs: event.durationMs, error: event.error, }), }).catch(() => {}); // fire-and-forget } }); // FILTER: Scrub outbound messages api.on("message_sending", async (event) => { // Add content filtering logic here return {}; }); api.logger.info("LetsBe Safety Wrapper loaded"); }, }; export default safetyWrapper; ``` **Manifest (`openclaw.plugin.json`):** ```json { "id": "letsbe-safety-wrapper", "name": "LetsBe Safety Wrapper", "version": "1.0.0", "configSchema": { "type": "object", "properties": { "blockedCommandPatterns": { "type": "array", "items": { "type": "string" } }, "blockedUrlPatterns": { "type": "array", "items": { "type": "string" } }, "blockedTools": { "type": "array", "items": { "type": "string" } }, "maxExecTimeoutMs": { "type": "number", "default": 30000 }, "auditEndpoint": { "type": "string" }, "piiRedactionEnabled": { "type": "boolean", "default": true } } } } ``` ### 10.5 What CANNOT Be Intercepted | Action | Why Not | Workaround | |--------|---------|-----------| | LLM token streaming | No hook exists for per-token filtering | Use `llm_output` for post-hoc analysis | | Config file reads | No hook | File permissions | | Plugin loading itself | Plugins load before hooks register | Control via `plugins.allow`/`plugins.deny` | | Direct file I/O by extensions | Extensions run in-process | Code review of extensions | | Gateway WS method calls | Some methods bypass tool pipeline | Restrict via auth + `gateway.tools_invoke_http` | ### 10.6 Recommendation: Extension Approach | Approach | Pros | Cons | |----------|------|------| | **Extension (RECOMMENDED)** | Clean API, officially supported, no fork needed, runs in-process (fast), access to full context | Must trust OpenClaw's hook execution order | | Reverse proxy | Language-agnostic, external to OpenClaw | Higher latency, loses session context, can't intercept internal tool calls | | Fork | Full control | Maintenance burden, merge conflicts on updates | **Recommendation:** Build the Safety Wrapper as an OpenClaw extension. Install it at `~/.openclaw/extensions/letsbe-safety-wrapper/` in the base Docker image. Configure it per-customer via `plugins.entries.letsbe-safety-wrapper.config` in `openclaw.json`. --- ## 11. Provisioning Blueprint ### 11.1 Step-by-Step Provisioning Sequence | Step | Action | Est. Time | Pre-bakeable? | |------|--------|-----------|---------------| | 1 | Provision VPS (2 vCPU, 4GB RAM, 20GB SSD) | 30-60s | N/A | | 2 | Apply base image with Docker + OpenClaw pre-built | 0s (snapshot) | **YES** | | 3 | Create customer config directory (`~/.openclaw/`) | 1s | YES (in image) | | 4 | Write customer-specific `openclaw.json` | 1s | Template + inject | | 5 | Write customer-specific `.env` | 1s | Template + inject | | 6 | Install Safety Wrapper extension | 0s (in base image) | **YES** | | 7 | Install business tool binaries (`gog`, `himalaya`, etc.) | 0s (in base image) | **YES** | | 8 | Configure Google OAuth (customer-specific) | Manual or API | No | | 9 | Configure email (customer IMAP/SMTP) | 1s (write config) | Template | | 10 | Generate gateway auth token | 1s | At provision time | | 11 | Start gateway container | 5-10s | No | | 12 | Run health check | 2-3s | No | | **Total** | | **~45-75s** (excl. OAuth) | | ### 11.2 What to Pre-bake into Base Image Build a custom Docker image extending OpenClaw: ```dockerfile FROM openclaw:local # Pre-install business tool binaries RUN apt-get update && apt-get install -y curl && \ # Install himalaya curl -L https://github.com/pimalaya/himalaya/releases/latest/download/himalaya-linux-x86_64 \ -o /usr/local/bin/himalaya && chmod +x /usr/local/bin/himalaya && \ # Install gog curl -L https://github.com/user/gog/releases/latest/download/gog-linux-amd64 \ -o /usr/local/bin/gog && chmod +x /usr/local/bin/gog # Pre-install Safety Wrapper extension COPY extensions/letsbe-safety-wrapper /home/node/.openclaw/extensions/letsbe-safety-wrapper/ # Pre-install skill files (if customized) # COPY skills/ /app/skills/ # Set env defaults ENV OPENCLAW_GATEWAY_BIND=lan ENV OPENCLAW_SKIP_CHANNELS=1 ``` ### 11.3 Per-Customer Config Template ```json5 // ~/.openclaw/openclaw.json — TEMPLATE { "gateway": { "auth": { "mode": "token", "token": "${OPENCLAW_GATEWAY_TOKEN}" }, "bind": "lan", "port": 18789 }, "models": { "providers": { "anthropic": { "apiKey": "${ANTHROPIC_API_KEY}" } } }, "agents": { "defaults": { "model": "claude-sonnet-4-6", "provider": "anthropic", "sandbox": { "mode": "all", "scope": "session" } }, "list": [ { "id": "main", "default": true, "systemPrompt": "You are a business assistant for {{COMPANY_NAME}}. Follow all safety policies." } ] }, "tools": { "allowlist": ["exec", "web_search", "web_fetch", "memory_search", "memory_get", "browser"] }, "plugins": { "enabled": true, "allow": ["memory-core", "letsbe-safety-wrapper", "llm-task"], "slots": { "memory": "memory-core" }, "entries": { "letsbe-safety-wrapper": { "enabled": true, "config": { "blockedCommandPatterns": ["rm\\s+-rf", "shutdown", "reboot", "mkfs", "dd\\s+if="], "blockedUrlPatterns": [".*\\.onion", "localhost", "127\\.0\\.0\\.1", "169\\.254\\."], "blockedTools": ["sessions_spawn", "gateway"], "maxExecTimeoutMs": 30000, "auditEndpoint": "https://hub.letsbe.ai/api/audit/{{CUSTOMER_ID}}", "piiRedactionEnabled": true } } } }, "skills": { "bundled": { "allowlist": ["gog", "himalaya", "weather", "summarize"] } }, "logging": { "redactSensitive": true } } ``` Variables to inject per-customer: `OPENCLAW_GATEWAY_TOKEN`, `ANTHROPIC_API_KEY`, `COMPANY_NAME`, `CUSTOMER_ID`. ### 11.4 Health Check Sequence ```bash #!/bin/bash # health-check.sh — run after provisioning TOKEN="${OPENCLAW_GATEWAY_TOKEN}" HOST="localhost:18789" # 1. Check gateway is listening curl -sf "http://$HOST/health" -H "Authorization: Bearer $TOKEN" || exit 1 # 2. Check via CLI openclaw health --token "$TOKEN" || exit 1 # 3. Send test message and verify response curl -sf "http://$HOST/v1/chat/completions" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"openclaw:main","messages":[{"role":"user","content":"ping"}]}' \ | jq -e '.choices[0].message.content' || exit 1 # 4. Run security audit openclaw security audit || echo "WARN: Security audit findings" echo "Health check passed" ``` ### 11.5 Minimum Viable OpenClaw Setup **Fewest containers:** 1 (gateway only, sandbox mode off) **Simplest config:** Anthropic API key + gateway token **No channels needed** — use HTTP API (`/v1/chat/completions`) exclusively **No browser needed** — skip `OPENCLAW_INSTALL_BROWSER` **No sandbox needed** — if Safety Wrapper provides sufficient controls ```bash # Absolute minimum: docker run -d \ -e ANTHROPIC_API_KEY=sk-ant-... \ -e OPENCLAW_GATEWAY_TOKEN=$(openssl rand -hex 32) \ -p 18789:18789 \ openclaw:local \ node openclaw.mjs gateway --bind lan --port 18789 --allow-unconfigured ``` --- ## 12. Risks, Limitations & Open Questions ### 12.1 Maturity Assessment | Component | Maturity | Notes | |-----------|----------|-------| | Core gateway | **High** | Actively maintained, well-structured | | Plugin SDK | **Medium-High** | Well-defined API, typed hooks, but limited documentation | | Config system | **High** | Comprehensive Zod schema, JSON5 with env var interpolation | | Memory backend | **Medium** | Experimental `node:sqlite` module; `sqlite-vec` for vectors | | Sandbox system | **Medium** | Docker-based, comprehensive hardening defaults, but complex | | Browser automation | **Medium** | Playwright-based, works but adds significant complexity | | Channel plugins | **Medium-High** | Many channels, but quality varies by channel | | Skills system | **Medium** | Markdown-only, no structured API — agents execute CLI commands via bash | ### 12.2 Scaling Limitations | Limitation | Impact | Mitigation | |-----------|--------|-----------| | **Single-operator trust model** | Cannot serve multiple customers from one gateway | One VPS per customer (our plan) | | **Flat-file storage** | No concurrent write safety for sessions | Session write locks exist but limited | | **In-process tools** | Tools run in gateway Node.js process | Enable sandbox mode for isolation | | **No horizontal scaling** | Single gateway process per instance | One instance per customer (our plan) | | **Memory backend uses node:sqlite** | Experimental Node.js API, may change | Pin Node.js version | | **No message queue** | Direct in-process event dispatch | Acceptable for single-tenant | ### 12.3 Missing Features We'd Need to Build | Feature | Why We Need It | Effort | |---------|---------------|--------| | **Multi-tenant auth** | Isolate customers on shared infra (future) | High — architectural change | | **Usage metering/billing** | Track per-customer LLM costs | Medium — hook into `after_tool_call` + `llm_output` | | **Customer onboarding API** | Automated provisioning without interactive wizard | Medium — script the config writing | | **Centralized logging** | Aggregate logs from all customer instances | Low — forward logs to central service | | **Health monitoring** | Monitor all customer instances | Low — health endpoint polling | | **Auto-update mechanism** | Update OpenClaw across fleet | Medium — rolling Docker image updates | | **Backup/restore** | Customer data portability | Low — backup `~/.openclaw/` directory | ### 12.4 Licensing **MIT License** — fully permissive for commercial use. No concerns for our use case. We can modify, distribute, and sublicense. Copyright notice must be preserved. ### 12.5 Version Pinning Strategy - OpenClaw uses **calendar versioning**: `YYYY.M.D` (e.g., `2026.2.26`) - Release channels: `stable` (tagged), `beta` (prerelease), `dev` (main branch) - **Recommendation:** Pin to specific stable releases in our Docker image. Test beta releases in staging before rolling out. - Dependencies with `pnpm.patchedDependencies` must use exact versions (no `^`/`~`) - Node.js engine requirement: `>=22.12.0` ### 12.6 Open Questions 1. **`node:sqlite` stability** — OpenClaw uses the experimental `node:sqlite` module. What's the Node.js team's timeline for stabilization? Should we pin a specific Node.js patch version? 2. **Gateway memory usage under load** — With 30 containerized tools + memory indexing + browser automation, what's the actual RAM footprint per customer? Need load testing. 3. **OAuth token refresh** — The `gog` CLI handles its own OAuth token storage. How does token refresh work when running headless in a container? Does it require periodic re-auth? 4. **Himalaya auth in containers** — The `himalaya` config uses `auth.cmd` for password retrieval (e.g., `pass show email/imap`). In a container, how do we securely provide IMAP/SMTP credentials? 5. **Extension stability across updates** — When OpenClaw updates, do plugin SDK interfaces maintain backward compatibility? Is there a versioned plugin API? 6. **Session JSONL file growth** — Sessions are append-only JSONL files. For long-running business agents, these could grow large. What's the compaction behavior? Is there auto-archival? 7. **MCP integration via mcporter** — OpenClaw uses `mcporter` for MCP server integration. Should our 30 containerized tools be exposed as MCP servers via mcporter, or as native OpenClaw skills/extensions? 8. **Browser sandbox networking** — The sandbox has `network: "none"` by default. Business tools (email, calendar, web search) need network access. What's the recommended network policy for business use? 9. **Config hot reload scope** — The gateway watches `openclaw.json` for hot reload. Which config changes take effect without restart vs. requiring restart? 10. **Concurrent agent runs** — If the same customer sends multiple messages rapidly, how does OpenClaw handle concurrent agent runs for the same session? Is there queuing? --- ## Appendix A: Key File Reference | What | Path | |------|------| | Entry point | `openclaw.mjs` → `src/entry.ts` | | CLI main | `src/cli/run-main.ts` | | Gateway server | `src/gateway/server.impl.ts` | | HTTP endpoints | `src/gateway/server-http.ts` | | Auth | `src/gateway/auth.ts` | | Config schema | `src/config/zod-schema.ts` | | Config loader | `src/config/io.ts` | | Agent defaults | `src/agents/defaults.ts` | | Agent runner | `src/agents/pi-embedded-runner/` | | Model auth | `src/agents/model-auth.ts` | | Provider catalog | `src/agents/models-config.providers.ts` | | Tool policy | `src/agents/tool-policy.ts` | | Plugin types | `src/plugins/types.ts` | | Plugin loader | `src/plugins/loader.ts` | | Plugin SDK | `src/plugin-sdk/index.ts` | | Hook system | `src/hooks/internal-hooks.ts` | | Routing | `src/routing/resolve-route.ts` | | Memory backend | `src/memory/` | | Security audit | `src/security/audit.ts` | | Browser tools | `src/browser/` | | Web search | `src/agents/tools/web-search.ts` | | Sandbox supervisor | `src/process/supervisor/supervisor.ts` | | Docker setup | `docker-setup.sh` | | Main Dockerfile | `Dockerfile` | | Sandbox Dockerfile | `Dockerfile.sandbox` | | Docker compose | `docker-compose.yml` | | Security policy | `SECURITY.md` | | Env example | `.env.example` | | Google skill | `skills/gog/SKILL.md` | | Email skill | `skills/himalaya/SKILL.md` | | OpenClawKit (Swift) | `apps/shared/OpenClawKit/` | --- *End of OpenClaw Architecture Analysis* *Document generated: 2026-02-26* *OpenClaw version analyzed: 2026.2.26*