2694 lines
108 KiB
Markdown
2694 lines
108 KiB
Markdown
# 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 <name> from argv
|
|
├─ applyCliProfileEnv() ← loads ~/.openclaw/profiles/<name>.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<void>);
|
|
|
|
type OpenClawPluginDefinition = {
|
|
id?: string;
|
|
name?: string;
|
|
description?: string;
|
|
version?: string;
|
|
kind?: PluginKind; // currently only "memory"
|
|
configSchema?: OpenClawPluginConfigSchema;
|
|
register?: (api: OpenClawPluginApi) => void | Promise<void>;
|
|
activate?: (api: OpenClawPluginApi) => void | Promise<void>; // 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<string, unknown>;
|
|
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<K extends PluginHookName>(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<any, unknown> & {
|
|
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<AgentToolResult<OutputDetails>>
|
|
|
|
type AgentToolResult<T> = {
|
|
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: `<workspaceDir>/.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/<agentId>/auth-profiles.json`)
|
|
2. Environment variables (`ANTHROPIC_API_KEY`, etc.)
|
|
3. Config file (`models.providers.<id>.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/<agentId>/sessions/<sessionKey>.jsonl`. Each line is a transcript event (user message, assistant message, tool call, tool result).
|
|
|
|
**Session key format:** `agent-<agentId>/<channel>/<accountId>/<peerKind>/<peerId>`
|
|
|
|
**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 <calendarId> --from 2026-02-26T00:00:00Z --to 2026-02-27T00:00:00Z
|
|
gog calendar create <calendarId> --summary "Meeting" --from <iso> --to <iso> --event-color 7
|
|
gog drive search "quarterly report" --max 10
|
|
gog contacts list --max 20
|
|
gog sheets get <sheetId> "Sheet1!A1:D10" --json
|
|
gog sheets update <sheetId> "Sheet1!A1:B2" --values-json '[["A","B"]]'
|
|
gog docs export <docId> --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/
|
|
│ └── <agentId>/
|
|
│ ├── auth-profiles.json ← Per-agent auth profiles
|
|
│ ├── sessions/
|
|
│ │ └── <sessionKey>.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-<agentId>/<channel>/<accountId>/<peerKind>/<peerId>`
|
|
|
|
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.<id>.apiKey` or env vars | String |
|
|
| OAuth tokens | `~/.openclaw/credentials/` directory | JSON files |
|
|
| Per-agent auth profiles | `~/.openclaw/agents/<agentId>/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-<uid>`
|
|
- 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:<id>` 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 <gateway_token>
|
|
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 <gateway_token>
|
|
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:<agentId>`) 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:<id>` 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<string, unknown> — 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<string, unknown>): 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*
|