# 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*
