@clawhub-maxwellmelo-2308bd384c
Complete OpenClaw reference: architecture, gateway, CLI (50+ commands), 25+ channels, 25+ providers, tools, plugins, automation, security, installation, plat...
---
name: openclaw-expert
description: "Complete OpenClaw reference: architecture, gateway, CLI (50+ commands), 25+ channels, 25+ providers, tools, plugins, automation, security, installation, platforms. Use when answering OpenClaw config/setup/behavior questions, debugging issues (gateway, channels, failover, sandboxing, heartbeat, cron), writing openclaw.json, setting up channels (WhatsApp/Telegram/Discord/Signal/Slack/iMessage/etc.), configuring providers (Anthropic/OpenAI/Gemini/xAI/Ollama/MiniMax/etc.), managing agents/sessions/memory/workspace, CLI commands, automation (cron/hooks/heartbeat/tasks), installing on any platform (macOS/Linux/Windows/Docker/VPS/RPi/iOS/Android), using tools (exec/browser/image/video/TTS/music/sub-agents/ACP), plugins, security hardening, troubleshooting, or gateway protocol. NOT for writing OpenClaw source code."
---
# OpenClaw Expert Skill
Complete OpenClaw reference compiled from 426+ official documentation pages.
12 reference files covering every aspect of the platform.
## Reference File Index
Load the relevant file based on the user's question. **Read only what's needed.**
| # | File | Topics | Lines |
|---|------|--------|-------|
| 01 | `references/01-core-concepts.md` | Architecture, agent runtime/loop, workspace, active memory, compaction, context engine, delegate architecture, dreaming, experimental features, markdown, memory (builtin/honcho/QMD/search), messages, model failover, model providers, models CLI, multi-agent routing, OAuth, presence, QA E2E automation, command queue, retry policy, sessions (management/pruning/tools), SOUL.md personality, streaming/chunking, system prompt, timezones, TypeBox schema, typing indicators, usage tracking, GPT-5.4/Codex agentic parity, Pi integration architecture, OpenProse | 2647 |
| 02 | `references/02-gateway.md` | Gateway runbook, authentication, background process, Bonjour/mDNS discovery, CLI backends, config (agents/channels/tools), configuration reference/examples, diagnostics export, doctor, gateway lock, health checks, heartbeat, local models, logging, multiple gateways, network model, OpenAI Chat Completions HTTP API, OpenResponses HTTP API, OpenShell, gateway-owned pairing, gateway protocol, remote access/setup, sandboxing (Docker/SSH/OpenShell), secrets management, security audit checks | 1538 |
| 03 | `references/03-cli.md` | All 50+ CLI subcommands: ACP, agent, agents, approvals/exec-policy, backup, browser, channels, completion, config, configure, cron, dashboard, devices, directory, DNS, docs, doctor, flows/tasks, gateway, health, hooks, infer/capability, logs, MCP, memory, message, models, node, nodes, onboard, pairing, plugins, proxy, QR, reset, sandbox, secrets, security, sessions, setup, skills, status, system, tasks, TUI/chat/terminal, uninstall, update, voicecall, webhooks, wiki + quick reference table | 3299 |
| 04 | `references/04-channels.md` | Channel overview/routing, groups, pairing, broadcast groups, location parsing, QA channel, BlueBubbles, Discord, Feishu/Lark, Google Chat, iMessage (legacy), IRC, LINE, Matrix (+ push rules), Mattermost, MS Teams, Nextcloud Talk, Nostr, QQ Bot, Signal, Slack, Synology Chat, Telegram, Tlon/Urbit, Twitch, WeChat, WhatsApp, Zalo (bot + personal), DM/group policy, multi-account, troubleshooting, feature comparison table | 2765 |
| 05 | `references/05-providers.md` | Anthropic, OpenAI, Gemini, OpenRouter, MiniMax, DeepSeek, Groq, Ollama, Together AI, Mistral, Fireworks, xAI, Perplexity, Amazon Bedrock, Cloudflare AI Gateway, Z.AI/GLM (Zhipu), 40+ additional providers (Arcee, Bedrock Mantle, Chutes, Claude Max Proxy, ComfyUI, GitHub Copilot, Gradium, Kilocode, LiteLLM, SGLang, Synthetic, Tencent, Vercel AI GW, vLLM, Volcengine, Vydra, Xiaomi, and more), model selection/failover config, env vars quick reference | 1689 |
| 06 | `references/06-tools.md` | Tool architecture, exec, code_execution, browser, message (agent send), image_generate, video_generate, music_generate, tts, web_fetch, sessions_spawn (sub-agents), web_search, tool configuration, gateway tool, memory_search/memory_get, session_status, plugin-provided tools | 1448 |
| 07 | `references/07-plugins.md` | Plugin system (install/develop/lifecycle), bundled plugins, voice-call, music-2.6, channel plugins, SDK conventions, registerCliBackend, registerAgentToolResultMiddleware, createOptionalChannelSetupSurface | 1190 |
| 08 | `references/08-automation.md` | Cron jobs (create/list/edit/delete/run/logs/doctor), background tasks (ledger), task flow, hooks (HTTP webhooks), standing orders, program examples (weekly status, content/social, financial, system monitoring), heartbeat, isolated sessions, failureDestination, model switch auth, Gmail PubSub, how they work together | 948 |
| 09 | `references/09-installation.md` | Getting started, updating, Docker (SSH/OpenShell sandbox, HEALTHCHECK, shared-network, VM runtime), Nix, Raspberry Pi, uninstall, onboarding wizard, VPS/Linux server deployment, migration guide, platform notes, Control UI custom build | 1019 |
| 10 | `references/10-security-and-misc.md` | Security model, exec approvals, sandbox modes, autoApproveCidrs, trusted-proxy, audit checks, FAQ, troubleshooting (gateway probe, doctor, channels --probe), debugging (watcher, PI_RAW_STREAM), env vars, scripts reference, nodes (remote exec, token rotation, SecretRef), diagnostics (export/recorder/privacy/flags), CI pipeline, RPC adapters | 1226 |
| 11 | `references/11-platforms.md` | macOS (app, discovery, --json), iOS (APNS relay, autoApproveCidrs, relay refresh), Android (autoApproveCidrs, foreground service, auto-reconnect, wide-area discover), Windows (companion, source dev loop, status), Linux (exe.dev, enable-linger), VPS hosting (shared agent, nodes, TimeoutStartSec), Web UI — Control UI (identity, auth gating, approval upgrade, base-hash guard, SecretRef preflight, abort, optimistic messages, compact button, Talk WebRTC, cron panel, schema.lookup, embedSandbox, chat.inject, config.apply) + WebChat (maxChars, abort metadata, tools panel) | 965 |
| 12 | `references/12-reference.md` | Configuration system overview, full config schema (gateway/agents/session/channels/cron/hooks/browser/UI/diagnostics/env), agent config reference, heartbeat reference (directPolicy/lightContext/isolatedSession/activeHours/ackMaxChars/multi-account/per-agent merge), workspace file map (BOOT.md hooks, bootstrap size limits, sandbox seed, Git backup), session management (threadBindings/maintenance/identityLinks), authentication (trusted-proxy fail-closed, HTTP API), gateway WS protocol (handshake/framing/roles/scopes/broadcast/RPC method families incl. diagnostics/secrets/sessions/approval/pairing), RPC adapters, CLI reference, onboarding wizard reference (providers table incl. MiniMax/StepFun/Synthetic/Moonshot/Kimi), file locations, env vars (OOM_SCORE_ADJ/PLUGIN_STAGE_DIR/ALLOW_INSECURE_PRIVATE_WS/PI_RAW_STREAM/DEBUG_TIMING/NODE_COMPILE_CACHE) | 1546 |
## Routing Guide
Match the user's question to the best starting file(s). Many topics span multiple files — follow cross-references.
| Question Pattern | Start With | Then Check |
|---|---|---|
| Configuration / openclaw.json schema | `12-reference.md` | `02-gateway.md` |
| How do I set up X channel? | `04-channels.md` | |
| How do I configure X provider? | `05-providers.md` | |
| CLI command help / "how do I run..." | `03-cli.md` | |
| Debugging / "not working" / errors | `10-security-and-misc.md` | `03-cli.md` (doctor/probe) |
| Heartbeat config / behavior | `12-reference.md` (heartbeat ref) | `08-automation.md`, `02-gateway.md` |
| Cron jobs / scheduled tasks | `08-automation.md` | `03-cli.md` (cron CLI) |
| Hooks / webhooks | `08-automation.md` | `03-cli.md` (hooks CLI) |
| Standing orders / task flow | `08-automation.md` | |
| Installation / getting started | `09-installation.md` | `11-platforms.md` |
| Docker setup | `09-installation.md` | `02-gateway.md` (sandboxing) |
| VPS / headless / PM2 / systemd | `09-installation.md` | `11-platforms.md` |
| Platform-specific apps (macOS/iOS/Android/Windows) | `11-platforms.md` | |
| Control UI / WebChat / Web UI | `11-platforms.md` | |
| Security / sandboxing / exec approvals | `10-security-and-misc.md` | `02-gateway.md`, `03-cli.md` |
| Tools (exec/browser/image/video/TTS/music) | `06-tools.md` | |
| Sub-agents / ACP / sessions_spawn | `06-tools.md` | `01-core-concepts.md` |
| Plugins (install/develop/voice-call/music) | `07-plugins.md` | |
| Architecture / how OpenClaw works | `01-core-concepts.md` | |
| Memory systems (builtin/honcho/QMD) | `01-core-concepts.md` | `03-cli.md` (memory CLI) |
| Agent workspace / bootstrap files | `01-core-concepts.md` | `12-reference.md` (workspace ref) |
| Sessions / multi-agent / routing | `01-core-concepts.md` | `12-reference.md` (session ref) |
| Model failover / retry / fallback chains | `01-core-concepts.md` | `05-providers.md` |
| System prompt / SOUL.md / personality | `01-core-concepts.md` | |
| Streaming / typing indicators / presence | `01-core-concepts.md` | |
| Usage tracking / token costs | `01-core-concepts.md` | |
| Gateway protocol / WebSocket RPC | `12-reference.md` | `02-gateway.md` |
| Nodes / remote exec / token rotation | `10-security-and-misc.md` | `12-reference.md` |
| Diagnostics / export / recorder | `10-security-and-misc.md` | |
| Environment variables | `10-security-and-misc.md` | `12-reference.md` |
| Migration / updating | `09-installation.md` | |
| Bonjour / mDNS / discovery | `02-gateway.md` | |
| OpenAI-compatible API / OpenResponses | `02-gateway.md` | |
| GPT-5.4 / Codex parity / strict-agentic | `01-core-concepts.md` | |
| Pi integration / createAgentSession | `01-core-concepts.md` | |
| OpenProse / .prose workflows | `01-core-concepts.md` | `07-plugins.md` |
| FAQ | `10-security-and-misc.md` | |
## Key Quick-Reference
### Config File Location
`~/.openclaw/openclaw.json` (JSON5 with comments/trailing commas)
### Essential CLI Commands
```bash
openclaw onboard # Interactive setup
openclaw doctor # Diagnose issues
openclaw doctor --fix # Auto-repair
openclaw gateway start # Start daemon
openclaw gateway status # Check status
openclaw status --deep # Full health probe
openclaw config get <path> # Read config value
openclaw config set <path> <value> # Write config value
openclaw cron list # List cron jobs
openclaw models list # List available models
openclaw models scan # Probe all configured models
```
### Model Format
Always `provider/model` — e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5.4`, `minimax/MiniMax-M2.7`
### DM Policy Options
`pairing` (default) → `allowlist` → `open` → `disabled`
### Sandbox Modes
`off` (default) | `non-main` | `all` — scope: `session` | `agent` | `shared`
### Heartbeat Defaults
- Interval: `30m` (or `1h` for Anthropic OAuth/token auth)
- Target: `none` (set `last` for delivery to last contact)
- Reply `HEARTBEAT_OK` when nothing needs attention
### Session Scoping
`main` | `per-peer` | `per-channel-peer` (recommended) | `per-account-channel-peer`
### File Precedence (workspace context)
Bootstrap files loaded every session: `AGENTS.md`, `SOUL.md`, `USER.md`, `IDENTITY.md`, `TOOLS.md`, `HEARTBEAT.md`
Per-file limit: `bootstrapMaxChars` (default 12000), total: `bootstrapTotalMaxChars` (default 60000)
## Grep Patterns for Efficient Lookup
All reference files are large. Use targeted reads instead of loading entire files:
```bash
# Find a specific topic's section
grep -n '^## \|^### ' references/01-core-concepts.md # List all headings
grep -n 'heartbeat' references/12-reference.md # Find heartbeat mentions
grep -n '^## Telegram' references/04-channels.md # Jump to Telegram section
grep -n '^## Anthropic' references/05-providers.md # Jump to Anthropic section
```
**Strategy for large files:**
1. Read the first 40 lines (TOC or section overview) to locate the heading
2. `grep -n '^## TARGET'` to find the line number
3. Read only that section with offset/limit
## Common Multi-File Lookups
Some topics are spread across files. Read in this order:
| Topic | Primary | Secondary | Tertiary |
|-------|---------|-----------|----------|
| Heartbeat | `12-reference.md` §Heartbeat Reference | `08-automation.md` §Heartbeat | `02-gateway.md` §Heartbeat |
| Sandboxing | `02-gateway.md` §Sandboxing | `10-security-and-misc.md` §Security | `03-cli.md` §Sandbox |
| Memory | `01-core-concepts.md` §Memory | `03-cli.md` §Memory | `07-plugins.md` §Memory Plugins |
| Exec approvals | `10-security-and-misc.md` §Security | `03-cli.md` §Approvals | `11-platforms.md` §Control UI |
| Docker | `09-installation.md` §Docker | `02-gateway.md` §Sandboxing | |
| WebSocket | `12-reference.md` §Gateway Protocol | `02-gateway.md` §Gateway Protocol | `01-core-concepts.md` §TypeBox |
## Notes
- All 12 reference files have a Table of Contents at the top — read that first to locate the relevant section.
- Cross-reference between files is common. The routing guide and multi-file lookup table above cover the main overlaps.
- All content verified against live docs at docs.openclaw.ai as of April 2026.
FILE:README.md
# 🧠 OpenClaw Expert Skill
> The most comprehensive OpenClaw reference available — 20,000+ lines distilled from 426+ official documentation pages into 12 structured reference files.
[](https://clawhub.ai)
[]()
[]()
[]()
---
## What Is This?
An **AgentSkill** for [OpenClaw](https://github.com/openclaw/openclaw) that turns any AI agent into an OpenClaw expert. Once installed, your agent can answer detailed questions about OpenClaw's architecture, configuration, CLI, channels, providers, tools, plugins, security, installation, and more — without needing internet access or doc lookups.
Think of it as a **compressed, searchable brain** containing everything in [docs.openclaw.ai](https://docs.openclaw.ai), organized for fast agent retrieval.
---
## 📊 Coverage at a Glance
| Metric | Value |
|--------|-------|
| **Reference files** | 12 |
| **Total lines** | 20,446 |
| **H2 sections** | 297 |
| **H3 subsections** | 1,396 |
| **Official doc pages covered** | 426+ |
| **Providers documented** | 52+ |
| **Channels documented** | 25+ |
| **CLI commands documented** | 50+ |
| **Config schema entries** | Full `openclaw.json` schema |
| **Last verified against live docs** | April 25, 2026 |
---
## 📁 Reference File Map
Each file covers a focused domain. The agent loads **only the relevant file** based on the user's question — not all 20K lines at once.
| # | File | What It Covers | Lines |
|---|------|----------------|-------|
| 01 | [`01-core-concepts.md`](references/01-core-concepts.md) | Architecture, agent runtime/loop, workspace, active memory, compaction, context engine, delegate architecture, dreaming, experimental features, markdown rendering, memory engines (builtin/Honcho/QMD/search), messages, model failover, model providers, models CLI, multi-agent routing, OAuth, presence, QA E2E automation, command queue, retry policy, sessions (management/pruning/tools), SOUL.md personality, streaming/chunking, system prompt, timezones, TypeBox schema, typing indicators, usage tracking, GPT-5.4/Codex agentic parity, Pi integration architecture, OpenProse | 2,662 |
| 02 | [`02-gateway.md`](references/02-gateway.md) | Gateway runbook, authentication, background process, Bonjour/mDNS, CLI backends, config deep-dives, diagnostics export, doctor, gateway lock, health checks, heartbeat, local models, logging, multiple gateways, network model, OpenAI-compatible HTTP API, OpenResponses HTTP API, OpenShell, pairing, gateway protocol, remote access, sandboxing (Docker/SSH/OpenShell), secrets management, security audit checks | 1,579 |
| 03 | [`03-cli.md`](references/03-cli.md) | All 50+ CLI subcommands with full syntax, flags, examples, and edge cases. ACP, agent, agents, approvals, backup, browser, channels, completion, config, configure, cron, dashboard, devices, directory, DNS, docs, doctor, flows, gateway, health, hooks, infer, logs, MCP, memory, message, models, node, nodes, onboard, pairing, plugins, proxy, QR, reset, sandbox, secrets, security, sessions, setup, skills, status, system, tasks, TUI, uninstall, update, voicecall, webhooks, wiki | 3,299 |
| 04 | [`04-channels.md`](references/04-channels.md) | Channel architecture, routing, groups, pairing, broadcast. Per-channel guides: BlueBubbles, Discord, Feishu/Lark, Google Chat, iMessage, IRC, LINE, Matrix, Mattermost, MS Teams, Nextcloud Talk, Nostr, QQ Bot, Signal, Slack, Synology Chat, Telegram, Tlon/Urbit, Twitch, WeChat, WhatsApp, Zalo. DM/group policy, multi-account, troubleshooting, feature comparison table | 2,765 |
| 05 | [`05-providers.md`](references/05-providers.md) | In-depth: Anthropic, OpenAI, Gemini, OpenRouter, MiniMax, DeepSeek, Groq, Ollama, Together AI, Mistral, Fireworks, xAI, Perplexity, Amazon Bedrock, Cloudflare AI Gateway, Z.AI/GLM. Additional: 40+ more providers (Arcee, Chutes, Claude Max Proxy, ComfyUI, GitHub Copilot, LiteLLM, vLLM, Volcengine, and more). Model selection, failover config, env vars reference | 1,689 |
| 06 | [`06-tools.md`](references/06-tools.md) | Tool architecture, exec, code_execution, browser automation, message (agent→channel), image_generate, video_generate, music_generate, TTS, web_fetch, sessions_spawn (sub-agents/ACP), web_search, tool config, gateway tool, memory_search/memory_get, session_status, plugin-provided tools | 1,468 |
| 07 | [`07-plugins.md`](references/07-plugins.md) | Plugin system (install/develop/lifecycle), bundled plugins, voice-call plugin, music-2.6 plugin, channel plugins, SDK conventions, registerCliBackend, registerAgentToolResultMiddleware, createOptionalChannelSetupSurface | 1,215 |
| 08 | [`08-automation.md`](references/08-automation.md) | Cron jobs (full CRUD + logs + doctor), background tasks (ledger), task flow, hooks (HTTP webhooks), standing orders, program examples (weekly status, content/social, financial, system monitoring), heartbeat system, isolated sessions, failureDestination, model switch auth, Gmail PubSub | 962 |
| 09 | [`09-installation.md`](references/09-installation.md) | Getting started guide, updating, Docker (SSH/OpenShell sandbox, HEALTHCHECK, shared-network, VM runtime), Nix/NixOS, Raspberry Pi, uninstall, onboarding wizard, VPS/Linux server deployment, migration guide, platform notes, Control UI custom build | 1,032 |
| 10 | [`10-security-and-misc.md`](references/10-security-and-misc.md) | Security model, exec approvals, sandbox modes, autoApproveCidrs, trusted-proxy, audit checks, FAQ, troubleshooting (gateway probe, doctor, channels --probe), debugging (watcher, PI_RAW_STREAM), env vars, scripts reference, nodes (remote exec, token rotation, SecretRef), diagnostics (export/recorder/privacy), CI pipeline, RPC adapters | 1,239 |
| 11 | [`11-platforms.md`](references/11-platforms.md) | macOS (app, discovery, --json), iOS (APNS relay, autoApproveCidrs), Android (foreground service, auto-reconnect, wide-area discover), Windows (companion, dev loop), Linux (exe.dev, enable-linger), VPS hosting, Web UI — Control UI (identity, auth, approval upgrade, cron panel, WebRTC, embedSandbox, chat.inject) + WebChat (maxChars, abort, tools panel) | 975 |
| 12 | [`12-reference.md`](references/12-reference.md) | Complete config schema (gateway/agents/session/channels/cron/hooks/browser/UI/diagnostics/env), agent config reference, heartbeat reference (all options), workspace file map (BOOT.md hooks, bootstrap limits, sandbox seed), session management, authentication, gateway WS protocol (handshake/framing/roles/scopes/RPC methods), onboarding wizard reference, file locations, env vars | 1,561 |
---
## 🚀 Installation
### Via ClawHub (recommended)
```bash
clawhub install maxwellmelo/openclaw-expert-skill
```
### Manual
```bash
# Clone into your skills directory
cd ~/clawd/skills/ # or wherever your skills live
git clone https://github.com/maxwellmelo/openclaw-expert-skill.git
# Or copy the .skill package
clawhub install ./openclaw-expert-skill.skill
```
### Verify
```bash
# The skill should appear in your agent's available skills
openclaw skills list
```
---
## 🔍 How It Works
### For the Agent
When a user asks an OpenClaw-related question, the agent:
1. **Reads the SKILL.md** — contains a routing guide mapping question patterns → reference files
2. **Loads only the relevant file** — e.g., `04-channels.md` for "how do I set up Telegram?"
3. **Uses the TOC** at the top of each file to jump to the exact section
4. **Follows cross-references** when topics span multiple files
The agent never loads all 20K lines at once. Typical lookup reads 200-500 lines.
### Routing Examples
| User Question | File Loaded | Section Found |
|---------------|------------|---------------|
| "How do I set up WhatsApp?" | `04-channels.md` | `## WhatsApp` |
| "What's the cron syntax?" | `08-automation.md` | `## Cron Jobs` |
| "My gateway won't start" | `10-security-and-misc.md` | `## Troubleshooting` |
| "How do I add Ollama?" | `05-providers.md` | `## Ollama` |
| "What goes in openclaw.json?" | `12-reference.md` | `## Full Config Schema` |
| "How does memory work?" | `01-core-concepts.md` | `## Memory Overview` |
| "Docker sandbox setup" | `09-installation.md` | `## Docker` |
| "How do I use the browser tool?" | `06-tools.md` | `## Browser` |
---
## 📋 Topics Covered
### Architecture & Core
- Gateway architecture (single daemon, WS protocol, typed RPC)
- Agent runtime and loop lifecycle
- System prompt construction and caching
- Context engine (workspace files, bootstrap, skills, dynamic injection)
- Pi SDK integration (`createAgentSession`, embedded runner)
- TypeBox schema system for gateway protocol
- GPT-5.4/Codex agentic parity program
### Channels (25+)
BlueBubbles · Discord · Feishu/Lark · Google Chat · iMessage · IRC · LINE · Matrix · Mattermost · MS Teams · Nextcloud Talk · Nostr · QQ Bot · Signal · Slack · Synology Chat · Telegram · Tlon/Urbit · Twitch · WeChat · WhatsApp · Zalo (bot + personal)
### Providers (52+)
Anthropic · OpenAI · Gemini · OpenRouter · MiniMax · DeepSeek · Groq · Ollama · Together AI · Mistral · Fireworks · xAI · Perplexity · Amazon Bedrock · Cloudflare AI Gateway · Z.AI/GLM · Arcee · Bedrock Mantle · Cerebras · Chutes · Claude Max Proxy · ComfyUI · GitHub Copilot · Gradium · Hyperbolic · Inferrs · Kilocode · LiteLLM · Moonshot/Kimi · OpenCode Go · Qianfan · SambaNova · SGLang · StepFun · Synthetic · Tencent · Vercel AI GW · vLLM · Volcengine · Vydra · Xiaomi · and more
### CLI (50+ commands)
Every subcommand documented with full syntax, flags, examples, and edge cases.
### Tools
exec · code_execution · browser · message · image_generate · video_generate · music_generate · tts · web_fetch · web_search · sessions_spawn · memory_search · memory_get · session_status · gateway tool · plugin tools
### Automation
Cron jobs · Background tasks · Task flow · Hooks (webhooks) · Standing orders · Heartbeat system · Isolated sessions · Gmail PubSub
### Security
Exec approvals · Sandbox modes (off/non-main/all) · autoApproveCidrs · Trusted-proxy · Audit checks · Nodes (remote exec, token rotation) · SecretRef · Diagnostics privacy
### Memory Systems
Builtin engine · Honcho · QMD engine · Memory search · Active memory · Compaction · Dreaming
### Platforms
macOS · iOS · Android · Windows · Linux · VPS/headless · Docker · Nix · Raspberry Pi · Control UI · WebChat
### Workflows
OpenProse (.prose files, /prose CLI, multi-agent orchestration)
---
## 🏗️ How This Was Built
1. **Crawled** all 426+ pages from [docs.openclaw.ai](https://docs.openclaw.ai) via the `llms.txt` index
2. **Organized** content into 12 thematic reference files
3. **Cross-referenced** every section against the live documentation
4. **Audited** each file with specialized agents (4 parallel auditors)
5. **Verified** corrections against live pages (not guessed — fetched and confirmed)
6. **Validated** the final package against ClawHub's AgentSkill spec
Three audit passes were performed:
- **Audit 1:** Bulk correction of ~244 issues across all 12 files
- **Audit 2:** Per-file deep review against live docs (12 sequential reviews)
- **Audit 3:** Coverage gap analysis — added 20 missing providers, 3 missing architecture sections
---
## 📐 Skill Spec Compliance
| Constraint | Status |
|-----------|--------|
| SKILL.md body ≤ 500 lines | ✅ 149 lines |
| Description ≤ 1024 chars | ✅ 819 chars |
| No angle brackets in description | ✅ |
| Files >100 lines have TOC | ✅ All 12 files |
| No scripts/, assets/, symlinks | ✅ Clean structure |
| Validates via `quick_validate.py` | ✅ "Skill is valid!" |
---
## 📂 Repository Structure
```
openclaw-expert-skill/
├── SKILL.md # Skill manifest (routing guide + quick reference)
├── README.md # This file
└── references/
├── 01-core-concepts.md # Architecture, memory, sessions, agents
├── 02-gateway.md # Gateway operations, sandboxing, health
├── 03-cli.md # All 50+ CLI commands
├── 04-channels.md # 25+ messaging channels
├── 05-providers.md # 52+ LLM providers
├── 06-tools.md # Agent tools (exec, browser, media, etc.)
├── 07-plugins.md # Plugin system and bundled plugins
├── 08-automation.md # Cron, hooks, tasks, heartbeat
├── 09-installation.md # Install on any platform
├── 10-security-and-misc.md # Security, debugging, FAQ
├── 11-platforms.md # Platform-specific guides + Web UI
└── 12-reference.md # Full config schema + protocol spec
```
---
## 🤝 Contributing
Found a gap or inaccuracy? Contributions welcome:
1. Fork the repo
2. Check the relevant section against [docs.openclaw.ai](https://docs.openclaw.ai)
3. Make your correction with a clear commit message
4. Open a PR describing what changed and why
**Guidelines:**
- Every claim should be verifiable against official docs
- Keep the existing structure (12 files, numbered, with TOCs)
- Files >100 lines must maintain their Table of Contents
- Run `quick_validate.py` before submitting
---
## 📄 License
MIT — use freely, attribute if you share.
---
## 🔗 Links
- **OpenClaw:** [github.com/openclaw/openclaw](https://github.com/openclaw/openclaw)
- **OpenClaw Docs:** [docs.openclaw.ai](https://docs.openclaw.ai)
- **ClawHub (skill marketplace):** [clawhub.ai](https://clawhub.ai)
- **Community:** [Discord](https://discord.com/invite/clawd)
---
*Built with ⚡ by [MX3 Dev](https://github.com/maxwellmelo) — autonomous AI operations at scale.*
FILE:references/01-core-concepts.md
# OpenClaw Core Concepts — Complete Reference
> Comprehensive reference compiled from 38 official OpenClaw documentation pages.
> Last compiled: 2026-04-24.
---
## Table of Contents
1. [Architecture](#architecture)
2. [Agent Runtime](#agent-runtime)
3. [Agent Loop](#agent-loop)
4. [Agent Workspace](#agent-workspace)
5. [Active Memory](#active-memory)
6. [Compaction](#compaction)
7. [Context](#context)
8. [Context Engine](#context-engine)
9. [Delegate Architecture](#delegate-architecture)
10. [Dreaming](#dreaming)
11. [Experimental Features](#experimental-features)
12. [Features](#features)
13. [Markdown Formatting](#markdown-formatting)
14. [Memory Overview](#memory-overview)
15. [Memory — Builtin Engine](#memory--builtin-engine)
16. [Memory — Honcho](#memory--honcho)
17. [Memory — QMD Engine](#memory--qmd-engine)
18. [Memory Search](#memory-search)
19. [Messages](#messages)
20. [Model Failover](#model-failover)
21. [Model Providers](#model-providers)
22. [Models CLI](#models-cli)
23. [Multi-Agent Routing](#multi-agent-routing)
24. [OAuth](#oauth)
25. [Presence](#presence)
26. [QA E2E Automation](#qa-e2e-automation)
27. [Command Queue](#command-queue)
28. [Retry Policy](#retry-policy)
29. [Session Management](#session-management)
30. [Session Pruning](#session-pruning)
31. [Session Tools](#session-tools)
32. [SOUL.md Personality Guide](#soulmd-personality-guide)
33. [Streaming and Chunking](#streaming-and-chunking)
34. [System Prompt](#system-prompt)
35. [Timezones](#timezones)
36. [TypeBox](#typebox)
37. [Typing Indicators](#typing-indicators)
38. [Usage Tracking](#usage-tracking)
39. [GPT-5.4 / Codex Agentic Parity](#gpt-54--codex-agentic-parity)
40. [Pi Integration Architecture](#pi-integration-architecture)
41. [OpenProse](#openprose)
42. [Cross-References](#cross-references)
---
## Architecture
**What it is:** The Gateway is the single long-lived daemon that owns all messaging surfaces and exposes a typed WebSocket API to clients, nodes, and automations.
### How it works
One Gateway per host. It:
- Maintains provider connections (WhatsApp/Baileys, Telegram/grammY, Slack, Discord, Signal, iMessage, WebChat)
- Exposes typed WS API on configured bind host (default `127.0.0.1:18789`)
- Validates inbound frames against JSON Schema
- Emits events: `agent`, `chat`, `presence`, `health`, `heartbeat`, `cron`
**Clients** (macOS app, CLI, web UI): connect over WS, send requests (`health`, `status`, `send`, `agent`, `system-presence`), subscribe to events (`tick`, `agent`, `presence`, `shutdown`).
**Nodes** (macOS/iOS/Android/headless): connect with `role: node`, declare caps/commands, expose `canvas.*`, `camera.*`, `screen.record`, `location.get`.
**Canvas** served at:
- `/__openclaw__/canvas/` (agent-editable HTML/CSS/JS)
- `/__openclaw__/a2ui/` (A2UI host)
### Wire Protocol
- Transport: WebSocket, text frames, JSON payloads
- First frame **must** be `connect`
- Requests: `{type:"req", id, method, params}` → `{type:"res", id, ok, payload|error}`
- Events: `{type:"event", event, payload, seq?, stateVersion?}`
- Idempotency keys required for side-effecting methods (`send`, `agent`)
- Auth: `connect.params.auth.token` or `.password`; also Tailscale Serve (`gateway.auth.allowTailscale: true`) or trusted-proxy mode
### Connection Lifecycle
```
Client → req:connect → Gateway
Gateway → res:hello-ok (snapshot: presence + health)
Gateway → event:presence, event:tick
Client → req:agent
Gateway → res:agent (ack: runId, status: "accepted")
Gateway → event:agent (streaming)
Gateway → res:agent (final: runId, status, summary)
```
### Pairing + Local Trust
- All WS clients include a **device identity** on `connect`
- New device IDs require pairing approval; Gateway issues a **device token**
- Direct local loopback can be auto-approved
- All connects must sign the `connect.challenge` nonce
- Signature payload `v3` binds `platform` + `deviceFamily`
- Non-local connects always require explicit approval
- `gateway.auth.*` applies to **all** connections, local or remote
### Remote Access
Preferred: Tailscale or VPN. Alternative: SSH tunnel:
```bash
ssh -N -L 18789:127.0.0.1:18789 user@host
```
### Key Invariants
- Exactly one Gateway controls a single Baileys session per host
- Handshake is mandatory; non-JSON or non-connect first frame = hard close
- Events are not replayed; clients must refresh on gaps
---
## Agent Runtime
**What it is:** OpenClaw runs a single embedded agent runtime — one agent process per Gateway, with its own workspace, bootstrap files, and session store.
### Bootstrap Files (injected at session start)
| File | Purpose |
|------|---------|
| `AGENTS.md` | Operating instructions + "memory" |
| `SOUL.md` | Persona, boundaries, tone |
| `TOOLS.md` | User-maintained tool notes (guidance only) |
| `BOOTSTRAP.md` | One-time first-run ritual (delete after) |
| `IDENTITY.md` | Agent name/vibe/emoji |
| `USER.md` | User profile + preferred address |
Blank files skipped. Large files trimmed with marker. Missing files get a "missing file" marker line.
`BOOTSTRAP.md` only created for brand-new workspaces. If deleted after ritual, not recreated.
To disable bootstrap file creation: `{ agents: { defaults: { skipBootstrap: true } } }`
**Note:** The `apply_patch` tool is optional and gated by `tools.exec.applyPatch` in config.
### Skills Loading (highest precedence first)
1. Workspace: `<workspace>/skills`
2. Project agent skills: `<workspace>/.agents/skills`
3. Personal agent skills: `~/.agents/skills`
4. Managed/local: `~/.openclaw/skills`
5. Bundled (shipped with install)
6. Extra skill folders: `skills.load.extraDirs`
### Session Transcripts
```
~/.openclaw/agents/<agentId>/sessions/<SessionId>.jsonl
```
### Steering While Streaming
- `steer` mode: inbound messages injected into current run after current tool calls finish, before next LLM call
- **Note:** Steering no longer skips remaining tool calls from the current assistant message; it injects the queued message at the next model boundary instead.
- `followup`/`collect`: messages held until current turn ends, then new agent turn starts
- Block streaming: **off by default** (`agents.defaults.blockStreamingDefault: "off"`)
- Boundary: `agents.defaults.blockStreamingBreak` (`text_end` vs `message_end`)
- Chunking: `agents.defaults.blockStreamingChunk` (800–1200 chars default)
- Coalescing: `agents.defaults.blockStreamingCoalesce`
- Non-Telegram channels require explicit `*.blockStreaming: true`
### Minimal Configuration
```json5
{
agents: {
defaults: {
workspace: "~/.openclaw/workspace"
}
},
channels: {
whatsapp: {
allowFrom: ["+1234567890"]
}
}
}
```
---
## Agent Loop
**What it is:** The full agentic execution cycle: intake → context assembly → model inference → tool execution → streaming replies → persistence.
### Entry Points
- Gateway RPC: `agent` and `agent.wait`
- CLI: `agent` command
### Execution Sequence
1. `agent` RPC validates params, resolves session, persists metadata, returns `{runId, acceptedAt}` immediately
2. `agentCommand`: resolves model + thinking/verbose/trace, loads skills snapshot, calls `runEmbeddedPiAgent`, emits lifecycle end/error
3. `runEmbeddedPiAgent`: serializes runs via per-session + global queues, resolves model + auth profile, subscribes to pi events, enforces timeout
4. Event bridging: tool events → `stream:"tool"`, assistant deltas → `stream:"assistant"`, lifecycle → `stream:"lifecycle"` (`phase: "start"|"end"|"error"`)
5. `agent.wait`: waits for lifecycle end/error, returns `{status: ok|error|timeout, startedAt, endedAt, error?}`
### Queueing + Concurrency
- Serialized per session key (session lane), optionally through global lane
- Session write locks are non-reentrant by default
- Transcript writes protected by file-based session write lock
### Plugin Hook Points
| Hook | When it runs |
|------|-------------|
| `before_model_resolve` | Before model/auth resolution |
| `before_prompt_build` | After session load, before prompt submission |
| `before_agent_reply` | After inline actions, before LLM call |
| `agent_end` | After completion |
| `before_compaction` / `after_compaction` | Around compaction cycles |
| `before_tool_call` / `after_tool_call` | Around tool execution |
| `before_install` | Before skill/plugin installs |
| `tool_result_persist` | Before tool results written to transcript |
| `message_received` / `message_sending` / `message_sent` | Message hooks |
| `session_start` / `session_end` | Session lifecycle |
| `gateway_start` / `gateway_stop` | Gateway lifecycle |
**Decision rules:**
- `before_tool_call { block: true }` → terminal (stops lower-priority handlers)
- `message_sending { cancel: true }` → terminal
### Reply Shaping
- Final payloads: assistant text + optional reasoning + inline tool summaries + error text
- `NO_REPLY` / `no_reply` filtered from outgoing payloads
- Messaging tool duplicates removed from final payload list
- If no renderable payloads remain + tool errored → fallback tool error reply
### Timeouts
- `agent.wait` default: 30s (wait-only, does not stop agent)
- Agent runtime: `agents.defaults.timeoutSeconds` default 172800s (48 hours)
- LLM idle timeout: `agents.defaults.llm.idleTimeoutSeconds` — aborts model request when no chunks arrive; if not set, uses `timeoutSeconds` or 120s default
---
## Agent Workspace
**What it is:** The agent's home directory — the only working directory used for file tools and workspace context.
### Default Location
- Default: `~/.openclaw/workspace`
- If `OPENCLAW_PROFILE` is set: `~/.openclaw/workspace-<profile>`
- Override: `agents.defaults.workspace`
**Important:** The workspace is the **default cwd**, not a hard sandbox. Absolute paths can reach beyond workspace unless sandboxing is enabled.
### Standard Workspace Files
| File | Purpose |
|------|---------|
| `AGENTS.md` | Operating instructions, loaded every session |
| `SOUL.md` | Persona, tone, boundaries |
| `USER.md` | Who the user is |
| `IDENTITY.md` | Agent name/vibe/emoji |
| `TOOLS.md` | Notes about local tools (guidance only) |
| `HEARTBEAT.md` | Optional checklist for heartbeat runs |
| `BOOT.md` | Optional startup checklist on gateway restart |
| `BOOTSTRAP.md` | One-time first-run ritual |
| `memory/YYYY-MM-DD.md` | Daily memory log |
| `MEMORY.md` | Curated long-term memory (load only in private sessions) |
| `skills/` | Workspace-specific skills (highest precedence) |
| `canvas/` | Canvas UI files for node displays |
Per-file inject limit: `agents.defaults.bootstrapMaxChars` (default: 12000 chars)
Total inject limit: `agents.defaults.bootstrapTotalMaxChars` (default: 60000 chars)
Truncation warning: `agents.defaults.bootstrapPromptTruncationWarning` (`off`/`once`/`always`, default: `once`)
### What is NOT in the Workspace
Keep OUT of version control:
- `~/.openclaw/openclaw.json` (config)
- `~/.openclaw/agents/<agentId>/agent/auth-profiles.json` (OAuth + API keys)
- `~/.openclaw/credentials/` (channel/provider state)
- `~/.openclaw/agents/<agentId>/sessions/` (session transcripts)
- `~/.openclaw/skills/` (managed skills)
### Git Backup (Recommended — Use Private Repo)
```bash
cd ~/.openclaw/workspace
git init
git add AGENTS.md SOUL.md TOOLS.md IDENTITY.md USER.md HEARTBEAT.md memory/
git commit -m "Add agent workspace"
```
Suggested `.gitignore`:
```gitignore
.DS_Store
.env
**/*.key
**/*.pem
**/secrets*
```
### Moving to a New Machine
1. Clone repo to desired path
2. Set `agents.defaults.workspace` in `~/.openclaw/openclaw.json`
3. Run `openclaw setup --workspace <path>` to seed missing files
4. Copy `~/.openclaw/agents/<agentId>/sessions/` separately if needed
---
## Active Memory
**What it is:** An optional plugin-owned **blocking memory sub-agent** that runs before the main reply for eligible conversational sessions, surfacing relevant memory proactively.
### How it works
```
User Message → Build Memory Query → Active Memory Sub-Agent
→ (if NONE/empty) → Main Reply
→ (if relevant summary) → Inject hidden <active_memory_plugin> context → Main Reply
```
The blocking sub-agent can only use `memory_search` and `memory_get`. Returns `NONE` when connection is weak.
**Gate conditions (ALL must be true):**
1. Plugin enabled (`plugins.entries.active-memory.enabled: true`)
2. Agent id in `config.agents`
3. Allowed chat type (`config.allowedChatTypes`)
4. Eligible interactive persistent chat session
**Does NOT run for:** headless one-shot runs, heartbeat/background runs, sub-agent/internal helper execution.
### Quick Start
```json5
{
plugins: {
entries: {
"active-memory": {
enabled: true,
config: {
enabled: true,
agents: ["main"],
allowedChatTypes: ["direct"],
modelFallback: "google/gemini-3-flash",
queryMode: "recent",
promptStyle: "balanced",
timeoutMs: 15000,
maxSummaryChars: 220,
persistTranscripts: false,
logging: true,
},
},
},
},
}
```
### Query Modes (`config.queryMode`)
| Mode | What it sends | Best for | Recommended `timeoutMs` |
|------|-------------|---------|------------------------|
| `message` | Latest user message only | Fastest, stable preference recall | 3000–5000ms |
| `recent` | Latest message + small recent tail (default) | Balance of speed and context | ~15000ms |
| `full` | Full conversation | Best recall quality | 15000ms+ |
### Prompt Styles (`config.promptStyle`)
| Style | Behavior |
|-------|---------|
| `balanced` | General-purpose default for `recent` mode |
| `strict` | Least eager; best for low bleed |
| `contextual` | Most continuity-friendly |
| `recall-heavy` | More willing to surface on softer matches |
| `precision-heavy` | Aggressively prefers NONE unless obvious |
| `preference-only` | Optimized for favorites, habits, routines |
Default mapping: `message` → `strict`, `recent` → `balanced`, `full` → `contextual`
### Model Fallback Policy
Resolution order: explicit `config.model` → current session model → agent primary model → `config.modelFallback` → skip turn
### Configuration Keys
| Key | Type | Meaning |
|-----|------|---------|
| `enabled` | boolean | Enables the plugin |
| `config.agents` | string[] | Agent ids that may use active memory |
| `config.model` | string | Optional dedicated model ref |
| `config.modelFallback` | string | Fallback when session model unresolvable |
| `config.modelFallbackPolicy` | string | **Deprecated.** Retained only as a compatibility field for older configs; no longer changes runtime behavior. |
| `config.queryMode` | "message"\|"recent"\|"full" | Context sent to sub-agent |
| `config.promptStyle` | string | Eagerness/strictness of recall |
| `config.thinking` | string | Thinking override. Valid values: `"off"` (default), `"minimal"`, `"low"`, `"medium"`, `"high"`, `"xhigh"`, `"adaptive"`, `"max"` |
| `config.timeoutMs` | number | Hard timeout, max 120000ms |
| `config.maxSummaryChars` | number | Max chars in summary |
| `config.allowedChatTypes` | string[] | Which chat types run active memory |
| `config.persistTranscripts` | boolean | Keep sub-agent transcripts on disk. When true, blocking sub-agent transcripts are stored under `agents/<agentId>/sessions/active-memory/` |
| `config.transcriptDir` | string | Subdirectory for transcripts |
| `config.logging` | boolean | Enable logging |
| `config.promptOverride` | string | Replace default prompt entirely |
| `config.promptAppend` | string | Append extra instructions |
### Session Toggle Commands
```
/active-memory status
/active-memory off
/active-memory on
/active-memory status --global # affects all sessions
/active-memory off --global
```
### Debug
```
/verbose on # shows: "Active Memory: status=ok elapsed=842ms query=recent summary=34 chars"
/trace on # shows: "Active Memory Debug: Lemon pepper wings with blue cheese."
/trace raw # shows raw hidden prompt inside <active_memory_plugin> tags
```
### Cerebras Setup for Fast Active Memory
```json5
{
models: {
providers: {
cerebras: {
baseUrl: "https://api.cerebras.ai/v1",
apiKey: "CEREBRAS_API_KEY",
api: "openai-completions",
models: [{ id: "gpt-oss-120b", name: "GPT OSS 120B (Cerebras)" }]
}
}
},
plugins: {
entries: {
"active-memory": {
enabled: true,
config: { model: "cerebras/gpt-oss-120b" }
}
}
}
}
```
---
## Compaction
**What it is:** When a conversation approaches the model's context window limit, OpenClaw **compacts** older messages into a summary so the chat can continue. Full conversation history stays on disk.
### How it works
1. Older turns summarized into a compact entry
2. Summary saved in session transcript
3. Recent messages kept intact
4. Tool calls kept paired with their `toolResult` entries (split boundary moves to preserve pairs)
**Auto-compaction** triggers when session nears context limit OR model returns overflow error (signatures: `request_too_large`, `context length exceeded`, `input exceeds the maximum number of tokens`, `input token count exceeds the maximum number of input tokens`, `input is too long for the model`, `ollama error: context length exceeded`).
**Before compacting:** OpenClaw automatically runs a silent memory flush turn reminding the agent to save important notes to memory files.
### Manual Compaction
```
/compact
/compact Focus on the API design decisions
```
### Monitoring Compaction
- `/status` shows `🧹 Compactions: <count>` (the number of compactions for the current session)
- Verbose mode (`/verbose on`) shows `🧹 Auto-compaction complete` when compaction fires
### Configuration
```json5
{
agents: {
defaults: {
compaction: {
model: "openrouter/anthropic/claude-sonnet-4-6", // optional different model
identifierPolicy: "strict", // "strict" | "off" | "custom"
identifierInstructions: "...", // custom instructions when identifierPolicy is "custom"
notifyUser: true, // show "Compacting context..." messages
provider: "my-provider" // optional pluggable compaction provider
}
}
}
}
```
When `provider` is set, forces `mode: "safeguard"`. Falls back to built-in LLM summarization if provider fails.
### Compaction vs Pruning
| | Compaction | Pruning |
|--|--|--|
| **What** | Summarizes older conversation | Trims old tool results |
| **Saved?** | Yes (in session transcript) | No (in-memory only, per request) |
| **Scope** | Entire conversation | Tool results only |
### Troubleshooting
- **Compacting too often?** Enable session pruning; tool outputs may be large
- **Context feels stale?** Use `/compact Focus on <topic>` or enable memory flush
- **Need clean slate?** `/new` starts fresh without compacting
---
## Context
**What it is:** Everything OpenClaw sends to the model for a run, bounded by the model's context window (token limit).
### Components
- **System prompt** (OpenClaw-built): rules, tools, skills list, time/runtime, injected workspace files
- **Conversation history**: messages + assistant replies
- **Tool calls/results + attachments**: command output, file reads, images/audio
Context ≠ memory: memory is stored on disk and reloaded; context is what's inside the model's current window.
### Inspection Commands
```
/status # quick "how full is my window?" view
/context list # what's injected + rough sizes (per file + totals)
/context detail # deeper breakdown: per-file, per-tool schema sizes, per-skill entry sizes
/usage tokens # append per-reply usage footer
/compact # summarize older history to free window space
```
### What Counts Toward Context Window
- System prompt (all sections)
- Conversation history
- Tool calls + tool results
- Attachments/transcripts (images/audio/files)
- Compaction summaries
- Provider "wrappers" or hidden headers
### Injected Workspace Files (Bootstrap)
By default injects (if present): `AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, `BOOTSTRAP.md`
Per-file limit: `agents.defaults.bootstrapMaxChars` (default: 12000 chars)
Total limit: `agents.defaults.bootstrapTotalMaxChars` (default: 60000 chars)
### Skills: Injected vs Loaded On-Demand
- System prompt includes compact skills list (name + description + location)
- Skill instructions NOT included by default
- Model reads skill's `SKILL.md` only when needed
### Tools: Two Costs
1. Tool list text in system prompt
2. Tool schemas (JSON) — sent to model so it can call tools
### Slash Commands Context
- **Standalone commands**: message that is only `/...` runs as a command
- **Directives**: `/think`, `/verbose`, `/trace`, `/reasoning`, `/elevated`, `/model`, `/queue` stripped before model sees the message
---
## Context Engine
**What it is:** Controls how OpenClaw builds model context for each run: which messages to include, how to summarize older history, and how to manage context across subagent boundaries.
OpenClaw ships with built-in `legacy` engine (default). Install and select a plugin engine for different assembly, compaction, or cross-session recall behavior.
### Lifecycle Points
1. **Ingest** — called when new message added
2. **Assemble** — called before each model run; returns ordered messages + optional `systemPromptAddition`
3. **Compact** — called when context window full or user runs `/compact`
4. **After turn** — called after run completes
### Installing a Context Engine Plugin
```bash
openclaw plugins install @martian-engineering/lossless-claw
```
```json5
{
plugins: {
slots: {
contextEngine: "lossless-claw"
},
entries: {
"lossless-claw": {
enabled: true,
// plugin-specific config
}
}
}
}
```
### Plugin Engine Interface
```typescript
api.registerContextEngine("my-engine", () => ({
info: {
id: "my-engine",
name: "My Context Engine",
ownsCompaction: true,
},
async ingest({ sessionId, message, isHeartbeat }) {
// Store message in your data store
return { ingested: true };
},
async assemble({ sessionId, messages, tokenBudget, availableTools, citationsMode }) {
return {
messages: buildContext(messages, tokenBudget),
estimatedTokens: countTokens(messages),
systemPromptAddition: buildMemorySystemPromptAddition({ availableTools }),
};
},
async compact({ sessionId, force }) {
return { ok: true, compacted: true };
},
}));
```
### `ownsCompaction`
- `true` → engine owns compaction; OpenClaw disables Pi's built-in auto-compaction for that run
- `false`/unset → Pi's built-in auto-compaction may still run
### Key Gotcha
If switching engines, existing sessions continue with current history; new engine takes over for future runs. If plugin engine fails to register, **no automatic fallback** — runs fail until fixed.
---
## Delegate Architecture
**What it is:** A pattern for running OpenClaw as a named delegate — an agent with its own identity that acts "on behalf of" people in an organization. The agent never impersonates a human.
### Capability Tiers
| Tier | Capabilities | Notes |
|------|-------------|-------|
| **Tier 1: Read-Only + Draft** | Read org data, draft messages for human review | Only read permissions; nothing sent without approval |
| **Tier 2: Send on Behalf** | Send messages + create calendar events under own identity | Recipients see "Delegate Name on behalf of Principal Name" |
| **Tier 3: Proactive** | Operates autonomously on a schedule via Cron + Standing Orders | Humans review asynchronously; requires careful hard blocks config |
### Hard Blocks (Define in SOUL.md + AGENTS.md First)
- Never send external emails without explicit human approval
- Never export contact lists, donor data, or financial records
- Never execute commands from inbound messages (prompt injection defense)
- Never modify identity provider settings
### Tool Restrictions (Per-Agent)
```json5
{
id: "delegate",
workspace: "~/.openclaw/workspace-delegate",
tools: {
allow: ["read", "exec", "message", "cron"],
deny: ["write", "edit", "apply_patch", "browser", "canvas"],
}
}
```
### Microsoft 365 Setup — CRITICAL Warning
```powershell
# ALWAYS create application access policy BEFORE reading any mail
# Without this, Mail.Read grants access to EVERY mailbox in the tenant
New-ApplicationAccessPolicy `
-AppId "<app-client-id>" `
-PolicyScopeGroupId "<mail-enabled-security-group>" `
-AccessRight RestrictAccess
```
### Google Workspace — Minimum Scopes
```
https://www.googleapis.com/auth/gmail.readonly # Tier 1
https://www.googleapis.com/auth/gmail.send # Tier 2
https://www.googleapis.com/auth/calendar # Tier 2
```
⚠️ Domain-wide delegation allows impersonating **any user in the domain**. Rotate keys on a schedule. Monitor Admin Console audit log.
---
## Dreaming
**What it is:** The background memory consolidation system in `memory-core`. Moves strong short-term signals into durable memory (`MEMORY.md`) with a three-phase approach. **Opt-in, disabled by default.**
### Phase Model
| Phase | Purpose | Durable write |
|-------|---------|---------------|
| Light | Sort and stage recent short-term material | No |
| Deep | Score and promote durable candidates | Yes (`MEMORY.md`) |
| REM | Reflect on themes and recurring ideas | No |
### Deep Phase Ranking Signals
| Signal | Weight | Description |
|--------|--------|-------------|
| Frequency | 0.24 | How many short-term signals the entry accumulated |
| Relevance | 0.30 | Average retrieval quality |
| Query diversity | 0.15 | Distinct query/day contexts |
| Recency | 0.15 | Time-decayed freshness |
| Consolidation | 0.10 | Multi-day recurrence strength |
| Conceptual richness | 0.06 | Concept-tag density |
### Quick Start
```json
{
"plugins": {
"entries": {
"memory-core": {
"config": {
"dreaming": {
"enabled": true,
"timezone": "America/Los_Angeles",
"frequency": "0 3 * * *"
}
}
}
}
}
}
```
### Slash Commands
```
/dreaming status
/dreaming on
/dreaming off
/dreaming help
```
### CLI Commands
```bash
openclaw memory promote # Preview what would promote
openclaw memory promote --apply # Apply promotions
openclaw memory promote --limit 5 # Limit to 5 candidates
openclaw memory status --deep # Deep status view
openclaw memory promote-explain "router vlan"
openclaw memory rem-harness # Preview REM reflections without writing
openclaw memory rem-backfill --path ./memory
openclaw memory rem-backfill --rollback
```
---
## Experimental Features
**What it is:** Opt-in preview surfaces behind explicit flags. Keep off by default unless docs say to try one. Shape and behavior can change faster than stable config.
### Currently Documented Flags
| Surface | Key | Use it when |
|---------|-----|-------------|
| Local model runtime | `agents.defaults.experimental.localModelLean` | Smaller local backend chokes on full tool surface |
| Memory search | `agents.defaults.memorySearch.experimental.sessionMemory` | Want `memory_search` to index prior session transcripts |
| Structured planning tool | `tools.experimental.planTool` | Want `update_plan` tool for multi-step work tracking |
### Local Model Lean Mode
`agents.defaults.experimental.localModelLean: true` trims heavyweight default tools like `browser`, `cron`, and `message` to make the prompt smaller for small-context or stricter OpenAI-compatible backends. Not the normal path — leave off if backend handles full runtime cleanly.
---
## Features
**What it is:** An overview of all capabilities offered by OpenClaw.
### Channels
- Built-in: Discord, Google Chat, iMessage (legacy), IRC, Signal, Slack, Telegram, WebChat, WhatsApp
- Bundled plugins: BlueBubbles, Feishu, LINE, Matrix, Mattermost, Microsoft Teams, Nextcloud Talk, Nostr, QQ Bot, Synology Chat, Tlon, Twitch, Zalo, Zalo Personal
### Agent
- Embedded runtime with tool streaming
- Multi-agent routing with isolated sessions
- Session management (direct chats → shared `main`; groups → isolated)
- Block streaming and chunking for long responses
### Auth and Providers
- 35+ model providers (Anthropic, OpenAI, Google, and more)
- Subscription auth via OAuth (e.g. OpenAI Codex)
- Custom and self-hosted provider support (vLLM, SGLang, Ollama, any OpenAI/Anthropic-compatible endpoint)
### Media
- Images, audio, video, and documents in and out
- Shared image/video generation capability surfaces
- Voice note transcription; Text-to-speech with multiple providers
### Apps and Interfaces
- WebChat and browser Control UI
- macOS menu bar companion app
- iOS node with pairing, Canvas, camera, screen recording, location, voice
- Android node with pairing, chat, voice, Canvas, camera, device commands
### Tools and Automation
- Browser automation, exec, sandboxing
- Web search (Brave, DuckDuckGo, Exa, Firecrawl, Gemini, Grok, Kimi, MiniMax Search, Ollama Web Search, Perplexity, SearXNG, Tavily)
- Cron jobs and heartbeat scheduling
- Skills, plugins, and workflow pipelines (Lobster)
---
## Markdown Formatting
**What it is:** OpenClaw formats outbound Markdown by converting it into a shared intermediate representation (IR) before rendering channel-specific output.
### Pipeline
1. **Parse Markdown → IR** — IR is plain text plus style spans (bold/italic/strike/code/spoiler) and link spans. Offsets are UTF-16 code units.
2. **Chunk IR (format-first)** — Chunking on IR text before rendering. Inline formatting does not split across chunks.
3. **Render per channel:**
- **Slack:** mrkdwn tokens (`*bold*`, `_italic_`, `~strike~`, `` `code` ``), links as `<url|label>`
- **Telegram:** HTML tags (`<b>`, `<i>`, `<s>`, `<code>`, `<pre><code>`, `<a href>`)
- **Signal:** plain text + `text-style` ranges; links become `label (url)` when label differs
### Table Handling
`markdown.tables` controls conversion per channel:
- `code` — render as code blocks (default for most channels)
- `bullets` — convert each row into bullet points (default for Signal + WhatsApp)
- `off` — disable table parsing; raw table text passes through
```yaml
channels:
discord:
markdown:
tables: code
accounts:
work:
markdown:
tables: off
```
### Gotchas
- Slack angle-bracket tokens (`<@U123>`, `<#C123>`) must be preserved
- Telegram HTML requires escaping text outside tags
- Signal style ranges depend on **UTF-16 offsets** (not code point offsets)
- Preserve trailing newlines for fenced code blocks
- `||spoiler||` markers parsed only for Signal
- Code fences preserved as a single block with trailing newline
---
## Memory Overview
**What it is:** OpenClaw remembers things by writing **plain Markdown files** in the agent's workspace. The model only "remembers" what gets saved to disk.
### Three Memory Files
| File | Purpose |
|------|---------|
| `MEMORY.md` | Long-term memory; durable facts, preferences; loaded every DM session |
| `memory/YYYY-MM-DD.md` | Daily notes; running context; today + yesterday loaded automatically |
| `DREAMS.md` (optional) | Dream Diary and dreaming sweep summaries |
### Memory Tools
- `memory_search` — finds relevant notes using semantic search
- `memory_get` — reads specific memory file or line range
### Memory Backends
| Backend | Description |
|---------|-------------|
| **Builtin** (default) | SQLite-based; keyword + vector similarity + hybrid search; no extra dependencies |
| **QMD** | Local-first sidecar with reranking, query expansion, ability to index extra directories |
| **Honcho** | AI-native cross-session memory with user modeling, semantic search, multi-agent awareness |
### Automatic Memory Flush
Before compaction, OpenClaw runs a silent turn reminding the agent to save important context to memory files. On by default.
### CLI
```bash
openclaw memory status # Check index status and provider
openclaw memory search "query" # Search from command line
openclaw memory index --force # Rebuild the index
```
---
## Memory — Builtin Engine
**What it is:** The default memory backend. Stores the memory index in a per-agent SQLite database. No extra dependencies required.
### What it provides
- **Keyword search** via FTS5 full-text indexing (BM25 scoring)
- **Vector search** via embeddings from any supported provider
- **Hybrid search** combining both
- **CJK support** via trigram tokenization
- Optional sqlite-vec acceleration for in-database vector queries
### Supported Embedding Providers
| Provider | ID | Auto-detected | Notes |
|----------|-----|--------------|-------|
| OpenAI | `openai` | Yes | Default: `text-embedding-3-small` |
| Gemini | `gemini` | Yes | Supports multimodal (image + audio) |
| Voyage | `voyage` | Yes | |
| Mistral | `mistral` | Yes | |
| Ollama | `ollama` | No | Local, set explicitly |
| Local | `local` | Yes (first) | GGUF model, ~0.6 GB download |
Auto-detection picks the first provider whose API key can be resolved. Set `memorySearch.provider` to override.
### Configuration
```json5
// Explicit provider:
{
agents: {
defaults: {
memorySearch: {
provider: "openai"
}
}
}
}
// Local GGUF embeddings (no API key):
{
agents: {
defaults: {
memorySearch: {
provider: "local",
fallback: "none",
local: {
modelPath: "~/.node-llama-cpp/models/embeddinggemma-300m-qat-Q8_0.gguf"
}
}
}
}
}
```
### How Indexing Works
- Indexes `MEMORY.md` and `memory/*.md` into chunks (~400 tokens with 80-token overlap)
- **Index location:** `~/.openclaw/memory/<agentId>.sqlite`
- **File watching:** changes to memory files trigger debounced reindex (1.5s)
- **Auto-reindex:** when embedding provider/model/chunking config changes, entire index rebuilt
- **On-demand:** `openclaw memory index --force`
### Troubleshooting
```bash
openclaw memory status # check index and provider
openclaw memory index --force --agent main # rebuild
```
sqlite-vec not loading → falls back to in-process cosine similarity automatically
---
## Memory — Honcho
**What it is:** [Honcho](https://honcho.dev) adds AI-native memory to OpenClaw. Persists conversations to a dedicated service and builds user and agent models over time.
### What it provides
- **Cross-session memory** — context carries across session resets, compaction, channel switches
- **User modeling** — Honcho maintains a profile for each user (preferences, facts, communication style)
- **Semantic search** — search over observations from past conversations
- **Multi-agent awareness** — parent agents automatically track spawned sub-agents
### Available Tools
| Tool | What it does |
|------|-------------|
| `honcho_context` | Full user representation across sessions |
| `honcho_search_conclusions` | Semantic search over stored conclusions |
| `honcho_search_messages` | Find messages across sessions (filter by sender, date) |
| `honcho_session` | Current session history and summary |
| `honcho_ask` | Ask about the user (LLM-powered; `depth='quick'` or `'thorough'`) |
### Getting Started
```bash
openclaw plugins install @honcho-ai/openclaw-honcho
openclaw honcho setup
openclaw gateway --force
```
### Configuration
```json5
{
plugins: {
entries: {
"openclaw-honcho": {
config: {
apiKey: "your-api-key", // omit for self-hosted
workspaceId: "openclaw", // memory isolation
baseUrl: "https://api.honcho.dev"
}
}
}
}
}
```
### Honcho vs Builtin Memory
| | Builtin / QMD | Honcho |
|--|--|--|
| **Storage** | Workspace Markdown files | Dedicated service |
| **Cross-session** | Via memory files | Automatic, built-in |
| **User modeling** | Manual (write to MEMORY.md) | Automatic profiles |
| **Search** | Vector + keyword (hybrid) | Semantic over observations |
| **Dependencies** | None (builtin) | Plugin install |
---
## Memory — QMD Engine
**What it is:** [QMD](https://github.com/tobi/qmd) is a local-first search sidecar that combines BM25, vector search, and reranking in a single binary.
### What it adds over builtin
- **Reranking and query expansion** for better recall
- **Index extra directories** — project docs, team notes, anything on disk
- **Index session transcripts** — recall earlier conversations
- **Fully local** — runs via Bun + node-llama-cpp, auto-downloads GGUF models
- **Automatic fallback** — if QMD unavailable, falls back to builtin engine
### Prerequisites
```bash
npm install -g @tobilu/qmd # or: bun install -g @tobilu/qmd
brew install sqlite # macOS
# QMD must be on the gateway's PATH
```
### Enable
```json5
{
memory: {
backend: "qmd"
}
}
```
OpenClaw creates a self-contained QMD home under `~/.openclaw/agents/<agentId>/qmd/` and manages the sidecar lifecycle automatically.
### Indexing Extra Paths
```json5
{
memory: {
backend: "qmd",
qmd: {
paths: [{ name: "docs", path: "~/notes", pattern: "**/*.md" }]
}
}
}
```
### Indexing Session Transcripts
```json5
{
memory: {
backend: "qmd",
qmd: {
sessions: { enabled: true }
}
}
}
```
### Model Overrides
```bash
export QMD_EMBED_MODEL="hf:Qwen/Qwen3-Embedding-0.6B-GGUF/Qwen3-Embedding-0.6B-Q8_0.gguf"
export QMD_RERANK_MODEL="/absolute/path/to/reranker.gguf"
export QMD_GENERATE_MODEL="/absolute/path/to/generator.gguf"
```
### Troubleshooting
- **QMD not found?** Ensure binary on PATH: `sudo ln -s ~/.bun/bin/qmd /usr/local/bin/qmd`
- **First search very slow?** Pre-warm: `qmd query "test"` using same XDG dirs
- **Search times out?** Increase `memory.qmd.limits.timeoutMs` (default: 4000ms; try 120000 for slower hardware)
- **Empty results in group chats?** Check `memory.qmd.scope` — default only allows direct and channel sessions
---
## Memory Search
**What it is:** `memory_search` finds relevant notes from memory files using vector similarity, keyword search, or both.
### How it works
```
Query → Embedding → Vector Search ──────┐
→ Tokenize → BM25 Search ─────────┤
Weighted Merge → Top Results
```
When embeddings unavailable → lexical ranking over FTS results (boosting chunks with stronger query-term coverage).
### Supported Providers
| Provider | ID | Needs API key |
|----------|-----|--------------|
| OpenAI | `openai` | Yes (auto-detected) |
| Gemini | `gemini` | Yes (auto-detected, supports image/audio) |
| Voyage | `voyage` | Yes (auto-detected) |
| Mistral | `mistral` | Yes (auto-detected) |
| GitHub Copilot | `github-copilot` | No (auto-detected via subscription) |
| Bedrock | `bedrock` | No (auto-detected via AWS credential chain) |
| Local | `local` | No (GGUF model) |
| Ollama | `ollama` | No (must set explicitly) |
### Improving Search Quality
**Temporal Decay:** Old notes gradually lose ranking weight (default half-life: 30 days). `MEMORY.md` never decayed.
**MMR (diversity):** Reduces redundant results so top results cover different topics.
```json5
{
agents: {
defaults: {
memorySearch: {
query: {
hybrid: {
mmr: { enabled: true },
temporalDecay: { enabled: true }
}
}
}
}
}
}
```
### Session Memory (Experimental)
Optionally index session transcripts so `memory_search` can recall earlier conversations:
`agents.defaults.memorySearch.experimental.sessionMemory`
### Troubleshooting
- **No results?** Run `openclaw memory status`. If empty, run `openclaw memory index --force`
- **Only keyword matches?** Embedding provider may not be configured
- **CJK text not found?** Rebuild FTS index with `openclaw memory index --force`
---
## Messages
**What it is:** How OpenClaw handles the full message lifecycle: inbound messages, sessions, queueing, streaming, and reasoning visibility.
### Message Flow
```
Inbound message
→ routing/bindings → session key
→ queue (if a run is active)
→ agent run (streaming + tools)
→ outbound replies (channel limits + chunking)
```
### Inbound Dedupe
OpenClaw keeps a short-lived cache keyed by channel/account/peer/session/message id to prevent redelivered messages from triggering another agent run.
### Inbound Debouncing
```json5
{
messages: {
inbound: {
debounceMs: 2000,
byChannel: {
whatsapp: 5000,
slack: 1500,
discord: 1500
}
}
}
}
```
- Applies to text-only messages; media/attachments flush immediately
- Control commands bypass debouncing
### Group Chat History Context
For non-direct chats, current message body is prefixed with sender label. History buffers include pending-only messages (messages that did NOT trigger a run due to mention gating), exclude messages already in session transcript.
Config: `messages.groupChat.historyLimit` (also per-channel overrides like `channels.slack.historyLimit`)
### Silent Replies
`NO_REPLY` / `no_reply` (case-insensitive) = "do not deliver user-visible reply":
- Direct conversations disallow silence by default; bare silent reply rewritten to short visible fallback
- Groups/channels allow silence by default
Config: `agents.defaults.silentReply`, `agents.defaults.silentReplyRewrite`, `surfaces.<id>.silentReply`
### Key Config Keys
- `messages.queue` — queueing and debounce behavior
- `agents.defaults.blockStreamingDefault` — on/off (default off)
- `agents.defaults.blockStreamingBreak` — `text_end`/`message_end`
- `agents.defaults.blockStreamingChunk` — min/max chars + breakPreference
- `agents.defaults.blockStreamingCoalesce` — idle-based batching
- `agents.defaults.humanDelay` — human-like pause between block replies
- `messages.responsePrefix` — outbound message prefix (global → channel → account)
- `messages.inbound.debounceMs` — debounce window
---
## Model Failover
**What it is:** A two-stage failure handling system: auth profile rotation within the current provider, then model fallback to the next model in `agents.defaults.model.fallbacks`.
### Runtime Flow
1. Resolve active session model and auth-profile preference
2. Build model candidate chain
3. Try current provider with auth-profile rotation/cooldown rules
4. If provider exhausted with failover-worthy error → move to next model candidate
5. Persist selected fallback override before retry starts
6. If fallback candidate fails → roll back only fallback-owned session override fields
7. If every candidate fails → throw `FallbackSummaryError` with per-attempt detail
### Auth Storage
- Secrets: `~/.openclaw/agents/<agentId>/agent/auth-profiles.json`
- Runtime routing state: `~/.openclaw/agents/<agentId>/agent/auth-state.json`
- Config `auth.profiles` / `auth.order` — metadata + routing only (no secrets)
**Credential types:**
- `type: "api_key"` → `{ provider, key }`
- `type: "oauth"` → `{ provider, access, refresh, expires, email? }`
### Profile IDs
- Default: `provider:default` when no email available
- OAuth with email: `provider:<email>` (e.g. `google-antigravity:[email protected]`)
### Rotation Order
When a provider has multiple profiles:
1. Explicit config: `auth.order[provider]`
2. Configured profiles: `auth.profiles` filtered by provider
3. Stored profiles in `auth-profiles.json`
Round-robin: OAuth before API keys, oldest-used first (within each type). Cooldown/disabled profiles moved to end.
### Session Stickiness
OpenClaw **pins the chosen auth profile per session** to keep provider caches warm. Pinned profile reused until: session reset (`/new`/`/reset`), compaction completes, or profile enters cooldown.
Manual selection: `/model ...@<profileId>` sets user override for that session.
### Cooldowns
Rate-limit bucket: `429`, `Too many concurrent requests`, `ThrottlingException`, `concurrency limit reached`, `workers_ai quota limit exceeded`, `throttled`, `resource exhausted`, `weekly/monthly limit reached`
Format/invalid-request errors (e.g., Cloud Code Assist tool call ID validation failures) are also treated as failover-worthy and use the same cooldowns.
OpenAI-compatible stop-reason errors (`Unhandled stop reason: error`, `stop reason: error`, `reason: error`) are classified as timeout/failover signals.
Provider-scoped transient server text also lands in the timeout bucket:
- **Anthropic:** bare `An unknown error occurred` and JSON `api_error` payloads with text like `internal server error`, `unknown error, 520`, `upstream error`, `backend error` → treated as failover-worthy timeouts
- **OpenRouter:** bare `Provider returned error` → treated as timeout only when provider context is actually OpenRouter
- **Provider-busy:** `ModelNotReadyException` lands in the overloaded bucket
- Generic internal fallback text (`LLM request failed with an unknown error.`) does NOT trigger failover by itself
**Cooldowns can be model-scoped:**
- OpenClaw records `cooldownModel` for rate-limit failures when the failing model id is known
- A sibling model on the same provider can still be tried when the cooldown is scoped to a different model
- Billing/disabled windows still block the whole profile across models
**Exponential backoff:** 1 min → 5 min → 25 min → 1 hour (cap)
**For Stainless SDKs (Anthropic, OpenAI):** OpenClaw caps SDK-internal `retry-after` waits at **60 seconds** by default (then surfaces error to allow failover). Override: `OPENCLAW_SDK_RETRY_MAX_WAIT_SECONDS`
### Billing Disables
Billing/credit failures → marked as **disabled** (longer backoff):
- Starts at **5 hours**, doubles per failure, caps at **24 hours**
- Backoff counters reset if profile hasn't failed for **24 hours** (configurable)
- Temporary `402` usage-window errors (e.g., `weekly usage limit exhausted`, `daily limit reached, resets tomorrow`, `organization spending limit exceeded`) → short cooldown path (classified as `rate_limit`, not billing disable)
### Which Errors Advance to Next Fallback Model
✅ Auth failures, rate limits, overloaded/provider-busy errors, timeout-shaped errors, billing disables, `LiveSessionModelSwitchError`, format/invalid-request errors, other unrecognized errors when candidates remain
❌ Explicit aborts (non-timeout), context overflow errors (stay in compaction/retry logic — signatures: `request_too_large`, `INVALID_ARGUMENT: input exceeds the maximum number of tokens`, `input token count exceeds the maximum number of input tokens`, `The input is too long for the model`, `ollama error: context length exceeded`)
### Key Config
- `auth.profiles` / `auth.order`
- `auth.cooldowns.billingBackoffHours` / `auth.cooldowns.billingMaxHours`
- `auth.cooldowns.overloadedProfileRotations` / `auth.cooldowns.overloadedBackoffMs`
- `auth.cooldowns.rateLimitedProfileRotations`
- `agents.defaults.model.primary` / `agents.defaults.model.fallbacks`
---
## Model Providers
**What it is:** The configuration and capabilities of the 35+ LLM providers OpenClaw supports.
### Quick Rules
- Model refs use `provider/model` (e.g. `opencode/claude-opus-4-6`)
- `agents.defaults.models` acts as an allowlist when set
- CLI helpers: `openclaw onboard`, `openclaw models list`, `openclaw models set <provider/model>`
- `models.providers.*.models[].contextWindow` = native model metadata; `contextTokens` = effective runtime cap
### API Key Rotation
Configure multiple keys via environment variables:
- `OPENCLAW_LIVE_<PROVIDER>_KEY` — single live override (highest priority)
- `<PROVIDER>_API_KEYS` — comma or semicolon list
- `<PROVIDER>_API_KEY` — primary key
- `<PROVIDER>_API_KEY_*` — numbered list
Rotation only on rate-limit responses. Non-rate-limit failures fail immediately.
### Key Built-in Providers
**OpenAI** (`openai`):
```json5
{ agents: { defaults: { model: { primary: "openai/gpt-5.4" } } } }
```
- Auth: `OPENAI_API_KEY`
- Default transport: `auto` (WebSocket-first, SSE fallback)
- Priority processing: `params.serviceTier` or `/fast` command
- `openai/gpt-5.3-codex-spark` is suppressed (API rejects it)
**Anthropic** (`anthropic`):
```json5
{ agents: { defaults: { model: { primary: "anthropic/claude-opus-4-6" } } } }
```
- Auth: `ANTHROPIC_API_KEY`
- Claude CLI reuse also supported (Anthropic staff confirmed usage is allowed)
- Direct public Anthropic requests support `/fast` toggle (maps to `service_tier`)
**OpenAI Codex OAuth** (`openai-codex`):
```json5
{ agents: { defaults: { model: { primary: "openai-codex/gpt-5.5" } } } }
```
- Auth: OAuth (ChatGPT)
- Policy note: OpenAI Codex OAuth is **explicitly supported** for external tools/workflows like OpenClaw
- Context: native `contextWindow = 1000000`, default runtime `contextTokens = 272000`
```json5
{ models: { providers: { "openai-codex": { models: [{ id: "gpt-5.5", contextTokens: 160000 }] } } } }
```
**Google Gemini** (`google`):
- Auth: `GEMINI_API_KEY`
- `google/gemini-3.1-flash-preview` normalizes to `google/gemini-3-flash-preview`
- Supports `cachedContents` handles via `params.cachedContent`
**Google Vertex / Gemini CLI** (`google-vertex`, `google-gemini-cli`):
- Auth: Vertex uses gcloud ADC; Gemini CLI uses its OAuth flow
- ⚠️ **Gemini CLI OAuth caution:** This is an unofficial integration. Some users have reported Google account restrictions after using third-party clients. Review Google terms and use a non-critical account if you choose to proceed.
**OpenCode** (`opencode`/`opencode-go`):
- Auth: `OPENCODE_API_KEY` or `OPENCODE_ZEN_API_KEY`
### Other Bundled Providers
| Provider | Id | Auth env | Example model |
|----------|-----|----------|---------------|
| BytePlus | `byteplus` / `byteplus-plan` | `BYTEPLUS_API_KEY` | `byteplus-plan/ark-code-latest` |
| Cerebras | `cerebras` | `CEREBRAS_API_KEY` | `cerebras/zai-glm-4.7` |
| Cloudflare AI Gateway | `cloudflare-ai-gateway` | `CLOUDFLARE_AI_GATEWAY_API_KEY` | — |
| DeepSeek | `deepseek` | `DEEPSEEK_API_KEY` | `deepseek/deepseek-v4-flash` |
| GitHub Copilot | `github-copilot` | `COPILOT_GITHUB_TOKEN` / `GH_TOKEN` / `GITHUB_TOKEN` | — |
| Groq | `groq` | `GROQ_API_KEY` | — |
| Hugging Face Inference | `huggingface` | `HUGGINGFACE_HUB_TOKEN` or `HF_TOKEN` | `huggingface/deepseek-ai/DeepSeek-R1` |
| Kilo Gateway | `kilocode` | `KILOCODE_API_KEY` | `kilocode/kilo/auto` |
| Kimi Coding | `kimi` | `KIMI_API_KEY` or `KIMICODE_API_KEY` | `kimi/kimi-code` |
| MiniMax | `minimax` / `minimax-portal` | `MINIMAX_API_KEY` / `MINIMAX_OAUTH_TOKEN` | `minimax/MiniMax-M2.7` |
| Mistral | `mistral` | `MISTRAL_API_KEY` | `mistral/mistral-large-latest` |
| Moonshot | `moonshot` | `MOONSHOT_API_KEY` | `moonshot/kimi-k2.6` |
| NVIDIA | `nvidia` | `NVIDIA_API_KEY` | `nvidia/nvidia/llama-3.1-nemotron-70b-instruct` |
| OpenRouter | `openrouter` | `OPENROUTER_API_KEY` | `openrouter/auto` |
| Qianfan | `qianfan` | `QIANFAN_API_KEY` | `qianfan/deepseek-v3.2` |
| Qwen Cloud | `qwen` | `QWEN_API_KEY` / `MODELSTUDIO_API_KEY` / `DASHSCOPE_API_KEY` | `qwen/qwen3.5-plus` |
| StepFun | `stepfun` / `stepfun-plan` | `STEPFUN_API_KEY` | `stepfun/step-3.5-flash` |
| Together | `together` | `TOGETHER_API_KEY` | `together/moonshotai/Kimi-K2.5` |
| Venice | `venice` | `VENICE_API_KEY` | — |
| Vercel AI Gateway | `vercel-ai-gateway` | `AI_GATEWAY_API_KEY` | `vercel-ai-gateway/anthropic/claude-opus-4.6` |
| Volcano Engine (Doubao) | `volcengine` / `volcengine-plan` | `VOLCANO_ENGINE_API_KEY` | `volcengine-plan/ark-code-latest` |
| xAI | `xai` | `XAI_API_KEY` | `xai/grok-4` |
| Xiaomi | `xiaomi` | `XIAOMI_API_KEY` | `xiaomi/mimo-v2-flash` |
| z.ai | `zai` | `ZAI_API_KEY` | `zai/glm-5.1` |
**Kimi K2 Model IDs:** `moonshot/kimi-k2.6`, `kimi-k2.5`, `kimi-k2-thinking`, `kimi-k2-thinking-turbo`, `kimi-k2-turbo`
### Custom Providers via `models.providers`
```json5
{
models: {
mode: "merge",
providers: {
moonshot: {
baseUrl: "https://api.moonshot.ai/v1",
apiKey: "MOONSHOT_API_KEY",
api: "openai-completions",
models: [{ id: "kimi-k2.6", name: "Kimi K2.6" }]
}
}
}
}
```
### Provider Quirks
- **OpenRouter**: applies app-attribution headers only on verified `openrouter.ai` routes
- **xAI**: `/fast` or `params.fastMode: true` rewrites `grok-3`, `grok-3-mini`, `grok-4`, and `grok-4-0709` to their `*-fast` variants
- **Cerebras GLM**: `zai-glm-4.7` / `zai-glm-4.6`; base URL `https://api.cerebras.ai/v1`
- **Kilo Gateway**: `kilocode/kilo/auto` routes through Kilo Gateway's own routing logic. The exact upstream target is owned by Kilo Gateway, not hard-coded in OpenClaw.
- **openai-codex**: `openai-codex/<model>` uses the OpenAI plugin with Codex OAuth. This is distinct from the native Codex app-server harness (`embeddedHarness.runtime: "codex"`), which is a different execution path.
---
## Models CLI
**What it is:** How OpenClaw selects models and the CLI interface for managing them.
### How Model Selection Works
1. **Primary** model (`agents.defaults.model.primary`)
2. **Fallbacks** in `agents.defaults.model.fallbacks` (in order)
3. **Provider auth failover** inside a provider before moving to next model
### Special Model Config Keys
- `agents.defaults.imageModel` — used only when primary model can't accept images
- `agents.defaults.pdfModel` — used by `pdf` tool (falls back to imageModel, then default model)
- `agents.defaults.imageGenerationModel` — shared image-generation capability
- `agents.defaults.musicGenerationModel` — shared music-generation capability
- `agents.defaults.videoGenerationModel` — shared video-generation capability
### "Model is not allowed" Error
If `agents.defaults.models` is set → it becomes the **allowlist**. Model not in allowlist:
```
Model "provider/model" is not allowed. Use /model to list available models.
```
**Fix:** Add model to allowlist, clear allowlist, or pick from `/model list`.
**Allowlist config example:**
```json5
{
agent: {
model: { primary: "anthropic/claude-sonnet-4-6" },
models: {
"anthropic/claude-sonnet-4-6": { alias: "Sonnet" },
"anthropic/claude-opus-4-6": { alias: "Opus" }
}
}
}
```
### Switching Models in Chat
```
/model # compact numbered picker
/model list
/model 3 # select #3 from picker
/model openai/gpt-5.4
/model status # detailed view with auth candidates
```
**Gotchas:**
- Model refs parsed by splitting on **first** `/`
- For OpenRouter-style (model contains `/`): must include provider prefix (`openrouter/moonshotai/kimi-k2`)
- If a run is active: switch marked as pending, applied at next clean retry point
### Safe Allowlist Edits
```bash
openclaw config set agents.defaults.models '{"openai/gpt-5.4":{}}' --strict-json --merge
```
Use `--merge` for additive changes; `--replace` only when provided value should become complete target value.
### CLI Commands
```bash
openclaw models list # configured models
openclaw models list --all # full catalog
openclaw models list --provider moonshot # filter by provider
openclaw models status # resolved primary + fallbacks + auth overview
openclaw models status --check # exit 1 when missing/expired, 2 when expiring
openclaw models status --probe # live auth checks (real requests; may consume tokens)
openclaw models status --probe-provider <name> # probe one provider
openclaw models status --probe-profile <id> # probe specific profiles (repeat or comma-separated)
openclaw models status --probe-timeout <ms> # probe timeout
openclaw models status --probe-concurrency <n> # probe concurrency
openclaw models status --probe-max-tokens <n> # max tokens for probe request
openclaw models status --agent <id> # inspect a configured agent's model/auth state
openclaw models set <provider/model> # set primary model
openclaw models set-image <provider/model> # set image model
openclaw models aliases list
openclaw models aliases add <alias> <provider/model>
openclaw models aliases remove <alias>
openclaw models fallbacks list
openclaw models fallbacks add <provider/model>
openclaw models fallbacks remove <provider/model>
openclaw models fallbacks clear
openclaw models scan # OpenRouter free model catalog
openclaw models scan --no-probe # metadata only, skip live probes
openclaw models scan --min-params 7 # min 7B parameters
openclaw models scan --set-default # set first result as default
```
### Models Status — Probe Buckets
When using `--probe`, each provider profile is classified into one of these status buckets:
`ok`, `auth`, `rate_limit`, `billing`, `timeout`, `format`, `unknown`, `no_model`
`models status` may show `marker(<value>)` in auth output for non-secret placeholders (e.g., `OPENAI_API_KEY`, `secretref-managed`, `minimax-oauth`, `ollama-local`) instead of masking them as secrets.
### Models Registry (`models.json`)
Location: `~/.openclaw/agents/<agentId>/agent/models.json`
Merge mode precedence: non-empty `baseUrl` already in agent `models.json` wins.
---
## Multi-Agent Routing
**What it is:** Running multiple isolated agents — each with its own workspace, state directory, and session history — plus multiple channel accounts in one running Gateway.
### Key Concepts
- **agentId**: one "brain" (workspace, per-agent auth, per-agent session store)
- **accountId**: one channel account instance
- **binding**: routes inbound messages to an `agentId` by `(channel, accountId, peer)`
- **Direct chats** collapse to `agent:<agentId>:<mainKey>`
### Path Quick Map
```
Config: ~/.openclaw/openclaw.json
State dir: ~/.openclaw
Workspace: ~/.openclaw/workspace (or ~/.openclaw/workspace-<agentId>)
Agent dir: ~/.openclaw/agents/<agentId>/agent
Sessions: ~/.openclaw/agents/<agentId>/sessions
```
### Single-Agent Mode (Default)
- `agentId` defaults to **`main`**
- Sessions keyed as `agent:main:<mainKey>`
- Workspace defaults to `~/.openclaw/workspace`
### Creating Multiple Agents
```bash
openclaw agents add coding
openclaw agents add social
openclaw agents list --bindings
```
### Routing Rules (Most-Specific Wins)
1. `peer` match (exact DM/group/channel id)
2. `parentPeer` match (thread inheritance)
3. `guildId + roles` (Discord role routing)
4. `guildId` (Discord)
5. `teamId` (Slack)
6. `accountId` match for a channel
7. channel-level match (`accountId: "*"`)
8. fallback to default agent
**Important:** A binding that omits `accountId` matches the default account only. Use `accountId: "*"` for channel-wide fallback across all accounts.
### Example: Multiple WhatsApp Numbers per Agent
```json5
{
agents: {
list: [
{ id: "home", default: true, workspace: "~/.openclaw/workspace-home", agentDir: "~/.openclaw/agents/home/agent" },
{ id: "work", workspace: "~/.openclaw/workspace-work", agentDir: "~/.openclaw/agents/work/agent" }
]
},
bindings: [
{ agentId: "home", match: { channel: "whatsapp", accountId: "personal" } },
{ agentId: "work", match: { channel: "whatsapp", accountId: "biz" } }
],
channels: {
whatsapp: {
accounts: { personal: {}, biz: {} }
}
}
}
```
### Example: WhatsApp Fast Chat + Telegram Deep Work
```json5
{
agents: {
list: [
{ id: "chat", workspace: "~/.openclaw/workspace-chat", model: "anthropic/claude-sonnet-4-6" },
{ id: "opus", workspace: "~/.openclaw/workspace-opus", model: "anthropic/claude-opus-4-6" }
]
},
bindings: [
{ agentId: "chat", match: { channel: "whatsapp" } },
{ agentId: "opus", match: { channel: "telegram" } }
]
}
```
### Example: Per-Agent Sandbox + Tool Restrictions
```json5
{
agents: {
list: [
{
id: "family",
workspace: "~/.openclaw/workspace-family",
sandbox: { mode: "all", scope: "agent" },
tools: {
allow: ["read"],
deny: ["exec", "write", "edit", "apply_patch"]
}
}
]
}
}
```
### Auth Isolation
Auth profiles are **per-agent** and **not shared automatically**. Never reuse `agentDir` across agents (causes auth/session collisions). To share credentials, copy `auth-profiles.json` into the other agent's `agentDir`.
### Cross-Agent QMD Memory Search
```json5
{
agents: {
list: [
{
id: "main",
memorySearch: {
qmd: {
extraCollections: [{ path: "notes" }]
}
}
}
]
}
}
```
---
## OAuth
**What it is:** OpenClaw supports "subscription auth" via OAuth for providers that offer it (notably **OpenAI Codex/ChatGPT OAuth**).
### The Token Sink Problem
OAuth providers often mint a **new refresh token** during login/refresh. Some providers invalidate older tokens when new one is issued → logging in via both OpenClaw AND Claude Code can cause one to randomly "get logged out."
**Solution:** OpenClaw treats `auth-profiles.json` as a **token sink** — reads from one place, keeps multiple profiles with deterministic routing, re-reads external CLIs without spending their refresh tokens.
### Token Storage
- Auth profiles: `~/.openclaw/agents/<agentId>/agent/auth-profiles.json`
- Legacy: `~/.openclaw/agents/<agentId>/agent/auth.json`
- Legacy import-only: `~/.openclaw/credentials/oauth.json` (imported on first use)
### OpenAI Codex OAuth Flow (PKCE)
1. Generate PKCE verifier/challenge + random `state`
2. Open `https://auth.openai.com/oauth/authorize?...`
3. Try to capture callback on `http://127.0.0.1:1455/auth/callback`
4. If can't bind (remote/headless): paste the redirect URL/code
5. Exchange at `https://auth.openai.com/oauth/token`
6. Extract `accountId` from access token and store `{ access, refresh, expires, accountId }`
Wizard path: `openclaw onboard` → auth choice `openai-codex`
### Anthropic
- API key auth: normal Anthropic API billing (recommended for production)
- Claude CLI reuse: Anthropic staff confirmed this usage is allowed again
```bash
claude auth login
openclaw models status
```
### Refresh + Expiry
- Stored `expires` timestamp; if expired → refresh under file lock, overwrite stored credentials
- Reused external CLI credentials: OpenClaw re-reads CLI auth store, never spends the copied refresh token itself
### Multiple Profiles in One Agent
```
/model Opus@anthropic:work # select specific profile for this session
openclaw channels list --json # see what profile IDs exist
```
---
## Presence
**What it is:** A lightweight, best-effort view of the Gateway itself and clients connected to it. Used primarily for the macOS app's **Instances** tab.
### Presence Fields
- `instanceId`: stable client identity (survives restarts)
- `host`: human-friendly host name
- `ip`: best-effort IP address
- `version`: client version string
- `deviceFamily` / `modelIdentifier`: hardware hints
- `mode`: `ui`, `webchat`, `cli`, `backend`, `probe`, `test`, `node`, etc.
- `lastInputSeconds`: seconds since last user input
- `reason`: `self`, `connect`, `node-connected`, `periodic`
- `ts`: last update timestamp (ms since epoch)
### Producers
1. **Gateway self entry** — seeded at startup
2. **WebSocket connect** — every WS client creates a presence entry (⚠️ `client.mode === "cli"` one-off commands are **not** turned into presence entries)
3. **`system-event` beacons** — clients send rich periodic beacons reporting host name, IP, `lastInputSeconds`
4. **Node connects** — `role: node` connections upsert a presence entry
### TTL and Bounded Size
- **TTL:** entries older than 5 minutes are pruned
- **Max entries:** 200 (oldest dropped first)
### Key Gotcha
Without stable `instanceId` in `connect.client.instanceId`, a reconnecting client may show up as a **duplicate row**.
### Remote/Tunnel Caveat
When client connects over SSH tunnel, Gateway may see `127.0.0.1` as remote address → loopback remote addresses are ignored to avoid overwriting client-reported IPs.
### Debugging
```
# See raw list:
# Call system-presence against the Gateway
# If duplicates: confirm clients send stable client.instanceId
```
---
## QA E2E Automation
**What it is:** The private QA stack for exercising OpenClaw in realistic, channel-shaped ways. Includes `qa-channel` (synthetic message channel), `qa-lab` (debugger UI), and repo-backed scenario seeds.
### Running the QA Lab
```bash
pnpm qa:lab:up
# For faster UI iteration (bind-mounted bundle):
pnpm openclaw qa docker-build-image
pnpm qa:lab:build
pnpm qa:lab:up:fast
pnpm qa:lab:watch # rebuild on change, browser auto-reloads
```
### Live Transport Lanes
```bash
# Matrix smoke test (disposable Tuwunel homeserver in Docker)
pnpm openclaw qa matrix
# Telegram smoke test (requires real Telegram group + two bots)
# Requires: OPENCLAW_QA_TELEGRAM_GROUP_ID, OPENCLAW_QA_TELEGRAM_DRIVER_BOT_TOKEN, OPENCLAW_QA_TELEGRAM_SUT_BOT_TOKEN
pnpm openclaw qa telegram
# Discord smoke test (requires real Discord guild + two bots)
# Requires: OPENCLAW_QA_DISCORD_GUILD_ID, OPENCLAW_QA_DISCORD_CHANNEL_ID, etc.
pnpm openclaw qa discord
# Suite on disposable Linux VM (Multipass)
pnpm openclaw qa suite --runner multipass --scenario channel-chat-baseline
# Credentials health check
pnpm openclaw qa credentials doctor
```
### Transport Contract Matrix
| Lane | Canary | Mention gating | Allowlist block | Top-level reply | Restart resume | Thread follow-up | Thread isolation | Reaction observation | Help cmd | Native cmd registration |
|------|--------|----------------|-----------------|-----------------|----------------|-----------------|-----------------|---------------------|---------|------------------------|
| Matrix | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | |
| Telegram | ✓ | ✓ | | | | | | | ✓ | |
| Discord | ✓ | ✓ | | | | | | | | ✓ |
### Provider Mock Lanes
- `mock-openai`: scenario-aware OpenClaw mock; default deterministic mock lane
- `aimock`: AIMock-backed provider for experimental protocol, record/replay, chaos coverage
### Character and Style Evaluation
```bash
pnpm openclaw qa character-eval \
--model openai/gpt-5.4,thinking=medium,fast \
--model anthropic/claude-opus-4-6,thinking=high \
--judge-model openai/gpt-5.4,thinking=xhigh,fast \
--blind-judge-models \
--concurrency 16 \
--judge-concurrency 16
```
### Scenario File Structure
Files in `qa/scenarios/` define:
- Scenario metadata
- Optional category, capability, lane, risk metadata
- Docs and code refs
- Optional plugin requirements + gateway config patch
- The executable `qa-flow`
### Important
Use `--allow-failures` when you want artifacts without a failing exit code.
`OPENCLAW_QA_MATRIX_TIMEOUT_MS` bounds the full Matrix run.
---
## Command Queue
**What it is:** A lane-aware in-process queue that serializes inbound auto-reply runs to prevent multiple agent runs from colliding, while allowing safe parallelism across sessions.
### How it works
- FIFO queue per lane with configurable concurrency cap (main defaults to 4, subagent to 8, unconfigured to 1)
- `runEmbeddedPiAgent` enqueues by **session key** (lane `session:<key>`) — only one active run per session
- Each session run queued into **global lane** (`main` by default) capped by `agents.defaults.maxConcurrent`
- Verbose logging: queued runs emit notice if they waited more than ~2s
- Typing indicators fire immediately on enqueue (before run starts)
### Queue Modes (Per Channel)
| Mode | Behavior |
|------|---------|
| `steer` | Inject into current run (after current tool calls finish); falls back to followup if not streaming |
| `followup` | Enqueue for next agent turn after current run ends |
| `collect` | Coalesce all queued messages into a **single** followup turn (default); different channels/threads drain individually |
| `steer-backlog` | Steer now AND preserve message for a followup turn (can look like duplicates on streaming surfaces) |
| `interrupt` (legacy) | Abort active run, then run newest message |
| `queue` (legacy alias) | Same as `steer` |
Default: all surfaces → `collect`
### Configuration
```json5
{
messages: {
queue: {
mode: "collect",
debounceMs: 1000,
cap: 20,
drop: "summarize", // "old" | "new" | "summarize"
byChannel: { discord: "collect" }
}
}
}
```
**`drop: "summarize"`** keeps a short bullet list of dropped messages and injects it as a synthetic followup prompt.
### Per-Session Overrides
```
/queue collect
/queue collect debounce:2s cap:25 drop:summarize
/queue default # or: /queue reset
```
### Scope and Guarantees
- Applies to all inbound channels using the gateway reply pipeline
- Default lane (`main`) is process-wide for inbound + main heartbeats
- Additional lanes (`cron`, `subagent`) run in parallel without blocking inbound replies
- Per-session lanes guarantee only one agent run touches a given session at a time
- Pure TypeScript + promises — no external dependencies
---
## Retry Policy
**What it is:** OpenClaw's retry behavior for HTTP requests to model providers and channel APIs.
### Defaults
- Attempts: 3
- Max delay cap: 30000ms
- Jitter: 0.1 (10%)
- Telegram min delay: 400ms
- Discord min delay: 500ms
### Model Providers
- OpenClaw lets provider SDKs handle normal short retries
- For Stainless-based SDKs (Anthropic, OpenAI): `retry-after-ms` or `retry-after` > 60s → OpenClaw injects `x-should-retry: false` so SDK surfaces the error immediately and model failover can rotate
- Override cap: `OPENCLAW_SDK_RETRY_MAX_WAIT_SECONDS=<seconds>` (set to `0`/`false`/`off`/`none`/`disabled` to let SDKs honor long waits)
### Discord
- Retries only on rate-limit errors (HTTP 429)
- Uses Discord `retry_after` when available, otherwise exponential backoff
### Telegram
- Retries on transient errors (429, timeout, connect/reset/closed, temporarily unavailable)
- Uses `retry_after` when available, otherwise exponential backoff
- Markdown parse errors NOT retried; fall back to plain text
### Configuration
```json5
{
channels: {
telegram: {
retry: {
attempts: 3,
minDelayMs: 400,
maxDelayMs: 30000,
jitter: 0.1
}
},
discord: {
retry: {
attempts: 3,
minDelayMs: 500,
maxDelayMs: 30000,
jitter: 0.1
}
}
}
}
```
### Notes
- Retries apply per request (message send, media upload, reaction, poll, sticker)
- Composite flows do not retry completed steps
---
## Session Management
**What it is:** How OpenClaw organizes conversations into **sessions**, routing each message based on where it came from.
### How Messages Are Routed
| Source | Behavior |
|--------|---------|
| Direct messages | Shared session by default |
| Group chats | Isolated per group |
| Rooms/channels | Isolated per room |
| Cron jobs | Fresh session per run |
| Webhooks | Isolated per hook |
### DM Isolation
⚠️ **If multiple people can message your agent, enable DM isolation** or Alice's private messages will be visible to Bob.
```json5
{
session: {
dmScope: "per-channel-peer" // isolate by channel + sender
}
}
```
Options:
- `main` (default) — all DMs share one session
- `per-peer` — isolate by sender (across channels)
- `per-channel-peer` — isolate by channel + sender (recommended)
- `per-account-channel-peer` — isolate by account + channel + sender
Verify setup: `openclaw security audit`
Tip: use `session.identityLinks` to link a person's identities across channels so they share one session.
### Session Lifecycle
- **Daily reset** (default) — new session at 4:00 AM local time on gateway host
- **Idle reset** (optional) — `session.reset.idleMinutes`
- **Manual reset** — `/new` or `/reset` in chat; `/new <model>` also switches model
When both daily and idle resets configured, whichever expires first wins.
⚠️ Sessions with an active provider-owned CLI session are NOT cut by implicit daily default. Use `/reset` or configure `session.reset` explicitly.
### Where State Lives
```
Store: ~/.openclaw/agents/<agentId>/sessions/sessions.json
Transcripts: ~/.openclaw/agents/<agentId>/sessions/<sessionId>.jsonl
```
### Session Maintenance
```json5
{
session: {
maintenance: {
mode: "enforce", // "warn" (default) | "enforce"
pruneAfter: "30d",
maxEntries: 500
}
}
}
```
Preview: `openclaw sessions cleanup --dry-run`
### Inspecting Sessions
```bash
openclaw status # session store path and recent activity
openclaw sessions --json # all sessions
openclaw sessions --active 60 # active in last 60 minutes
```
In chat:
```
/status # context usage, model, toggles
/context list # what's in the system prompt
```
---
## Session Pruning
**What it is:** Trims **old tool results** from the context before each LLM call to reduce context bloat. **In-memory only — does NOT modify the on-disk session transcript.**
### Why it matters
Long sessions accumulate tool output that inflates context window. Especially valuable for **Anthropic prompt caching**: pruning reduces cache-write size, directly lowering cost.
### How it works
1. Wait for cache TTL to expire (default 5 minutes)
2. Find old tool results
3. **Soft-trim** oversized results — keep head and tail, insert `...`
4. **Hard-clear** the rest — replace with a placeholder
5. Reset TTL so follow-up requests reuse fresh cache
### Legacy Image Cleanup
- Preserves the **3 most recent completed turns** byte-for-byte (to keep prompt cache prefixes stable)
- Older already-processed image blocks replaced with `[image data removed - already processed by model]`
### Auto-Enabled for Anthropic
| Profile type | Pruning enabled | Heartbeat |
|-------------|----------------|-----------|
| Anthropic OAuth/token auth (Claude CLI reuse) | Yes | 1 hour |
| API key | Yes | 30 min |
### Configuration
```json5
{
agents: {
defaults: {
contextPruning: { mode: "cache-ttl", ttl: "5m" }
}
}
}
```
To disable: `mode: "off"`
### Pruning vs Compaction
| | Pruning | Compaction |
|--|--|--|
| **What** | Trims tool results | Summarizes conversation |
| **Saved?** | No (per-request) | Yes (in transcript) |
| **Scope** | Tool results only | Entire conversation |
They complement each other — pruning keeps tool output lean between compaction cycles.
---
## Session Tools
**What it is:** Tools that let agents work across sessions, inspect status, and orchestrate sub-agents.
### Available Tools
| Tool | What it does |
|------|-------------|
| `sessions_list` | List sessions with optional filters (kind, label, agent, recency, preview) |
| `sessions_history` | Read the transcript of a specific session |
| `sessions_send` | Send a message to another session and optionally wait |
| `sessions_spawn` | Spawn an isolated sub-agent session for background work |
| `sessions_yield` | End the current turn and wait for follow-up sub-agent results |
| `subagents` | List, steer, or kill spawned sub-agents for this session |
| `session_status` | Show a `/status`-style card and optionally set a per-session model override |
### `sessions_history` Safety Filtering
Returns intentionally bounded and safety-filtered view:
- Thinking tags stripped
- `<relevant-memories>` scaffolding blocks stripped
- Plain-text tool-call XML payload blocks stripped
- Downgraded tool-call/result scaffolding stripped
- Leaked model control tokens stripped
- Malformed MiniMax tool-call XML stripped
- Credential/token-like text redacted
- Long text blocks truncated
Reports: `truncated`, `droppedMessages`, `contentTruncated`, `contentRedacted`, `bytes`
### Sending Cross-Session Messages
```
sessions_send with timeoutSeconds: 0 → fire-and-forget
sessions_send with timeoutSeconds: 30 → wait up to 30s for reply
```
After target responds, OpenClaw can run a **reply-back loop** (up to 5 turns). Target agent replies `REPLY_SKIP` to stop early.
### Spawning Sub-Agents
`sessions_spawn` creates an isolated session for background task. Always non-blocking — returns immediately with `runId` and `childSessionKey`.
Key options:
- `runtime: "subagent"` (default) or `"acp"` for external harness agents
- `model` and `thinking` overrides for child session
- `thread: true` to bind spawn to a chat thread (Discord, Slack, etc.)
- `sandbox: "require"` to enforce sandboxing on child
- `context: "fork"` for child to get current requester transcript; `context: "isolated"` (default) for clean child
After completion, an announce step posts result to requester's channel.
### Sub-Agent Depth and Tool Access
Default leaf sub-agents do NOT get session tools. When `maxSpawnDepth >= 2`:
- Depth-1 orchestrator sub-agents get: `sessions_spawn`, `subagents`, `sessions_list`, `sessions_history`
- Leaf runs still do not get recursive orchestration tools
### Visibility Scopes
| Level | Scope |
|-------|-------|
| `self` | Only the current session |
| `tree` | Current session + spawned sub-agents (default) |
| `agent` | All sessions for this agent |
| `all` | All sessions (cross-agent if configured) |
Sandboxed sessions are clamped to `tree` regardless of config.
### `subagents` Control Tool
```
action: "list" → inspect active/recent runs
action: "steer" → send follow-up guidance to running child
action: "kill" → stop one child or "all"
```
---
## SOUL.md Personality Guide
**What it is:** `SOUL.md` is where your agent's voice lives. OpenClaw injects it on normal sessions with real weight.
### What Belongs in SOUL.md
- Tone
- Opinions
- Brevity rules
- Humor policy
- Boundaries
- Default level of bluntness
### What Does NOT Belong in SOUL.md
- Life story
- Changelog
- Security policy dump
- Giant wall of vibes with no behavioral effect
**Short beats long. Sharp beats vague.**
### Good SOUL.md Rules vs Bad
```markdown
✅ GOOD:
- have a take
- skip filler
- be funny when it fits
- call out bad ideas early
- stay concise unless depth is actually useful
❌ BAD:
- maintain professionalism at all times
- provide comprehensive and thoughtful assistance
- ensure a positive and supportive experience
```
### The "Molty" Prompt (Rewrite SOUL.md via Chat)
```
Read your `SOUL.md`. Now rewrite it with these changes:
1. You have opinions now. Strong ones. Stop hedging everything with "it depends" - commit to a take.
2. Delete every rule that sounds corporate. If it could appear in an employee handbook, it doesn't belong here.
3. Add a rule: "Never open with Great question, I'd be happy to help, or Absolutely. Just answer."
4. Brevity is mandatory. If the answer fits in one sentence, one sentence is what I get.
5. Humor is allowed. Not forced jokes - just the natural wit that comes from actually being smart.
6. You can call things out. If I'm about to do something dumb, say so.
7. Swearing is allowed when it lands. Don't force it. Don't overdo it. But if a situation calls for a "holy shit" - say holy shit.
8. Add this line verbatim at the end of the vibe section: "Be the assistant you'd actually want to talk to at 2am. Not a corporate drone. Not a sycophant. Just... good."
Save the new `SOUL.md`. Welcome to having a personality.
```
### Key Rule
Personality is not permission to be sloppy. Keep `AGENTS.md` for operating rules. Keep `SOUL.md` for voice, stance, and style.
---
## Streaming and Chunking
**What it is:** Two separate streaming layers: block streaming (channel messages) and preview streaming (Telegram/Discord/Slack).
### Block Streaming (Channel Messages)
Sends completed assistant blocks as soon as they finish. Off by default.
**Controls:**
- `agents.defaults.blockStreamingDefault`: `"on"`/`"off"` (default off)
- Channel overrides: `*.blockStreaming: true/false`
- `agents.defaults.blockStreamingBreak`: `"text_end"` or `"message_end"`
- `agents.defaults.blockStreamingChunk`: `{ minChars, maxChars, breakPreference? }`
- `agents.defaults.blockStreamingCoalesce`: `{ minChars?, maxChars?, idleMs? }`
- Channel hard cap: `*.textChunkLimit`
- Channel chunk mode: `*.chunkMode` (`length` default, `newline` splits on blank lines first)
- Discord soft cap: `channels.discord.maxLinesPerMessage` (default 17)
**Boundary semantics:**
- `text_end`: stream blocks as soon as chunker emits; flush on each `text_end`
- `message_end`: wait until assistant message finishes, then flush buffered output (may emit multiple chunks if very long)
### Chunking Algorithm (EmbeddedBlockChunker)
- **Low bound:** don't emit until buffer >= `minChars` (unless forced)
- **High bound:** prefer splits before `maxChars`
- **Break preference:** `paragraph` → `newline` → `sentence` → `whitespace` → hard break
- **Code fences:** never split inside fences; when forced, close + reopen to keep Markdown valid
- `maxChars` clamped to channel `textChunkLimit`
### Coalescing (Merge Streamed Blocks)
Waits for idle gaps (`idleMs`) before flushing. Reduces "single-line spam."
- Default coalesce `minChars` bumped to 1500 for Signal/Slack/Discord
### Human-Like Pacing
```json5
{
agents: {
defaults: {
humanDelay: {
mode: "natural", // "off" (default) | "natural" (800–2500ms) | "custom"
// custom: { minMs: 500, maxMs: 3000 }
}
}
}
}
```
Applies only to block replies (not final replies or tool summaries).
### Preview Streaming Modes
`channels.<channel>.streaming`:
- `off`: disable preview streaming
- `partial`: single preview that is replaced with latest text
- `block`: preview updates in chunked/appended steps
- `progress`: progress/status preview during generation, final answer at completion
| Channel | off | partial | block | progress |
|---------|-----|---------|-------|----------|
| Telegram | ✓ | ✓ | ✓ | maps to `partial` |
| Discord | ✓ | ✓ | ✓ | maps to `partial` |
| Slack | ✓ | ✓ | ✓ | ✓ |
| Mattermost | ✓ | ✓ | ✓ | ✓ |
### Tool-Progress Preview Updates
Preview streaming can include short status lines ("searching the web", "reading file") during tool execution on Discord, Slack, and Telegram. Skipped when preview streaming is `off` or block streaming has taken over.
### Config Summary (Three Modes)
```
Stream as you go: blockStreamingDefault: "on" + blockStreamingBreak: "text_end"
Stream everything end: blockStreamingBreak: "message_end"
No block streaming: blockStreamingDefault: "off" (only final reply)
```
---
## System Prompt
**What it is:** OpenClaw builds a custom system prompt for every agent run. The prompt is **OpenClaw-owned** and does not use the pi-coding-agent default prompt.
### Structure (Fixed Sections)
1. **Tooling**: structured-tool source-of-truth + runtime tool-use guidance (includes long-running work guidance: use cron for future follow-up, `exec`/`process` for commands that start now, `sessions_spawn` for larger tasks)
2. **Execution Bias**: follow-through guidance (act in-turn on actionable requests, continue until done, recover from weak tool results, check mutable state live, verify before finalizing)
3. **Safety**: guardrail reminder (advisory only — use tool policy, exec approvals, sandboxing for hard enforcement)
4. **Skills** (when available): how to load skill instructions on demand
5. **OpenClaw Self-Update**: how to inspect/patch config safely
6. **Workspace**: working directory
7. **Documentation**: local path to OpenClaw docs + public mirror
8. **Workspace Files (injected)**: bootstrap files included below
9. **Sandbox** (when enabled): sandbox paths and elevated exec availability
10. **Current Date & Time**: timezone only (no dynamic clock — keeps prompt cache-stable)
11. **Reply Tags**: optional reply tag syntax for supported providers
12. **Heartbeats**: heartbeat prompt and ack behavior
13. **Runtime**: host, OS, node, model, repo root (when detected), thinking level
14. **Reasoning**: current visibility level + /reasoning toggle hint
### Prompt Modes
| Mode | Used for | Omits |
|------|---------|-------|
| `full` | Default | Nothing |
| `minimal` | Sub-agents | Skills, Memory Recall, Self-Update, Model Aliases, User Identity, Reply Tags, Messaging, Silent Replies, Heartbeats |
| `none` | Returns only base identity line | Everything else |
### Workspace Bootstrap Injection
Files injected under **Project Context**:
`AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, `BOOTSTRAP.md` (new workspaces only), `MEMORY.md` (when present)
All injected every turn except:
- `HEARTBEAT.md` omitted when heartbeats disabled
- Sub-agents use the `minimal` prompt mode and only get `AGENTS.md` and `TOOLS.md` injected (other bootstrap files like `SOUL.md`, `IDENTITY.md`, `USER.md` are filtered out to keep the sub-agent context small)
Large files truncated with marker. Control with:
- `agents.defaults.bootstrapMaxChars` (default: 12000 per file)
- `agents.defaults.bootstrapTotalMaxChars` (default: 60000 total)
- `agents.defaults.bootstrapPromptTruncationWarning` (`off`/`once`/`always`, default: `once`)
**Note:** `memory/*.md` daily files are **NOT** part of normal bootstrap. Accessed on-demand via `memory_search` and `memory_get` tools. Bare `/new` and `/reset` turns can prepend recent daily memory as a one-shot startup block.
### Skills in System Prompt
```xml
<available_skills>
<skill>
<name>...</name>
<description>...</description>
<location>...</location>
</skill>
</available_skills>
```
Skills list budget: `skills.limits.maxSkillsPromptChars` (global) or `agents.list[].skillsLimits.maxSkillsPromptChars` (per-agent)
### Time Handling
System prompt includes **Current Date & Time** section with timezone only (no dynamic clock, to keep prompt cache-stable). Agent calls `session_status` when it needs current time.
Configure:
- `agents.defaults.userTimezone` (IANA timezone)
- `agents.defaults.timeFormat` (`auto` | `12` | `24`)
### Provider Plugin Contributions
Provider plugins can contribute to system prompt without replacing it:
- Replace named core sections: `interaction_style`, `tool_call_style`, `execution_bias`
- Inject **stable prefix** above prompt cache boundary
- Inject **dynamic suffix** below prompt cache boundary
The OpenAI GPT-5 family overlay keeps the core execution rule small and adds model-specific guidance for persona latching, concise output, tool discipline, parallel lookup, deliverable coverage, verification, missing context, and terminal-tool hygiene.
### Debug
```
/context list # what's injected + rough sizes
/context detail # per-file, per-tool schema, per-skill entry sizes
```
---
## Timezones
**What it is:** OpenClaw standardizes timestamps so the model sees a **single reference time**.
### Message Envelopes
Inbound messages are wrapped in:
```
[Provider ... 2026-01-05 16:26 PST] message text
```
Timestamp is **host-local by default**, with minutes precision.
### Configuration
```json5
{
agents: {
defaults: {
envelopeTimezone: "local", // "utc" | "local" | "user" | IANA timezone
envelopeTimestamp: "on", // "on" | "off"
envelopeElapsed: "on", // "on" | "off"
}
}
}
```
- `"utc"` — use UTC
- `"user"` — use `agents.defaults.userTimezone` (falls back to host timezone)
- Explicit IANA (e.g. `"Europe/Vienna"`) — fixed offset
**Examples:**
```
Local (default): [Signal Alice +1555 2026-01-18 00:19 PST] hello
Fixed timezone: [Signal Alice +1555 2026-01-18 06:19 GMT+1] hello
Elapsed time: [Signal Alice +1555 +2m 2026-01-18T05:19Z] follow-up
```
### Tool Payloads
Tool calls return raw provider timestamps PLUS normalized fields:
- `timestampMs` (UTC epoch milliseconds)
- `timestampUtc` (ISO 8601 UTC string)
### User Timezone for System Prompt
```json5
{
agents: {
defaults: {
userTimezone: "America/Chicago",
timeFormat: "auto" // "auto" | "12" | "24"
}
}
}
```
If unset, OpenClaw resolves **host timezone at runtime** (no config write).
---
## TypeBox
**What it is:** TypeBox is the TypeScript-first schema library used to define the **Gateway WebSocket protocol**. Those schemas drive runtime validation, JSON Schema export, and Swift codegen for the macOS app. One source of truth — everything else is generated.
### Mental Model (Every Gateway WS Message)
Three frame types:
- **Request**: `{ type: "req", id, method, params }`
- **Response**: `{ type: "res", id, ok, payload | error }`
- **Event**: `{ type: "event", event, payload, seq?, stateVersion? }`
First frame **must** be `connect`. After handshake, clients call methods and subscribe to events.
### Connection Flow
```
Client → req:connect → Gateway
← res:hello-ok
← event:tick
Client → req:health
← res:health
```
### Common Methods + Events
| Category | Examples | Notes |
|----------|---------|-------|
| Core | `connect`, `health`, `status` | `connect` must be first |
| Messaging | `send`, `agent`, `agent.wait`, `system-event`, `logs.tail` | side-effects need `idempotencyKey` |
| Chat | `chat.history`, `chat.send`, `chat.abort` | WebChat uses these |
| Sessions | `sessions.list`, `sessions.patch`, `sessions.delete` | session admin |
| Automation | `wake`, `cron.list`, `cron.run`, `cron.runs` | wake + cron control |
| Nodes | `node.list`, `node.invoke`, `node.pair.*` | Gateway WS + node actions |
| Events | `tick`, `presence`, `agent`, `chat`, `health`, `shutdown` | server push |
### Where Schemas Live
- Source: `src/gateway/protocol/schema.ts`
- Runtime validators (AJV): `src/gateway/protocol/index.ts`
- Feature/discovery registry: `src/gateway/server-methods-list.ts`
- Generated JSON Schema: `dist/protocol.schema.json`
- Generated Swift models: `apps/macos/Sources/OpenClawProtocol/GatewayModels.swift`
### Pipeline
```bash
pnpm protocol:gen # writes JSON Schema (draft-07)
pnpm protocol:gen:swift # generates Swift gateway models
pnpm protocol:check # runs both generators and verifies output is committed
```
### Example Frames
```json
// Connect (first message):
{ "type": "req", "id": "c1", "method": "connect", "params": { "minProtocol": 3, "maxProtocol": 3, "client": { "id": "openclaw-macos", "displayName": "macos", "version": "1.0.0", "platform": "macos 15.1", "mode": "ui", "instanceId": "A1B2" } } }
// Hello-ok response:
{ "type": "res", "id": "c1", "ok": true, "payload": { "type": "hello-ok", "protocol": 3, "server": { "version": "dev", "connId": "ws-1" }, "features": { "methods": ["health"], "events": ["tick"] }, "snapshot": { "presence": [], "health": {}, "stateVersion": { "presence": 0, "health": 0 }, "uptimeMs": 0 } } }
// Event:
{ "type": "event", "event": "tick", "payload": { "ts": 1730000000 }, "seq": 12 }
```
### Minimal Node.js Client
```typescript
import { WebSocket } from "ws";
const ws = new WebSocket("ws://127.0.0.1:18789");
ws.on("open", () => {
ws.send(JSON.stringify({
type: "req", id: "c1", method: "connect",
params: { minProtocol: 3, maxProtocol: 3,
client: { id: "cli", displayName: "example", version: "dev", platform: "node", mode: "cli" } }
}));
});
ws.on("message", (data) => {
const msg = JSON.parse(String(data));
if (msg.type === "res" && msg.id === "c1" && msg.ok) {
ws.send(JSON.stringify({ type: "req", id: "h1", method: "health" }));
}
if (msg.type === "res" && msg.id === "h1") {
console.log("health:", msg.payload);
ws.close();
}
});
```
### Schema Conventions
- Most objects use `additionalProperties: false` for strict payloads
- `NonEmptyString` default for IDs and method/event names
- Top-level `GatewayFrame` uses a **discriminator** on `type`
- Methods with side effects require `idempotencyKey` (e.g. `send`, `poll`, `agent`, `chat.send`)
### Adding a New Method (End-to-End)
1. Add TypeBox schemas to `src/gateway/protocol/schema.ts`
2. Add to `ProtocolSchemas` and export types
3. Export AJV validator in `src/gateway/protocol/index.ts`
4. Add handler in `src/gateway/server-methods/<category>.ts`
5. Register in `src/gateway/server-methods.ts`
6. Add to `listGatewayMethods` in `src/gateway/server-methods-list.ts`
7. Classify in `src/gateway/method-scopes.ts` if needed
8. Run `pnpm protocol:check`
9. Add tests + update docs
---
## Typing Indicators
**What it is:** Typing indicators sent to the chat channel while a run is active. Control **when** typing starts (`typingMode`) and **how often** it refreshes (`typingIntervalSeconds`).
### Defaults (When `typingMode` is Unset)
- **Direct chats**: typing starts immediately when model loop begins
- **Group chats with mention**: typing starts immediately
- **Group chats without mention**: typing starts only when message text begins streaming
- **Heartbeat runs**: typing starts when heartbeat run begins (if typing-capable chat)
### Modes
| Mode | When typing fires |
|------|-----------------|
| `never` | Never |
| `instant` | As soon as model loop begins (even if run later returns only silent reply) |
| `thinking` | On first reasoning delta (requires `reasoningLevel: "stream"`) |
| `message` | On first non-silent text delta (ignores `NO_REPLY` token) |
Order of "how early it fires": `never` → `message` → `thinking` → `instant`
### Configuration
```json5
{
agent: {
typingMode: "thinking",
typingIntervalSeconds: 6,
}
}
```
Per-session override:
```json5
{
session: {
typingMode: "message",
typingIntervalSeconds: 4,
}
}
```
### Gotchas
- `message` mode won't show typing for silent-only replies (`NO_REPLY`, matched case-insensitively)
- `thinking` only fires if run streams reasoning (`reasoningLevel: "stream"`); if model doesn't emit reasoning deltas, typing won't start
- `typingIntervalSeconds` controls **refresh cadence** (default: 6s), not start time
- Heartbeats do not show typing when `target: "none"`, target can't be resolved, chat delivery is disabled, or channel doesn't support typing
---
## Usage Tracking
**What it is:** Pulls provider usage/quota directly from their usage endpoints. No estimated costs — only provider-reported windows.
### Where It Shows Up
- `/status` in chats — emoji-rich status card with session tokens + estimated cost (API key only). Provider usage shows as `X% left` for current model provider.
- `/usage off|tokens|full` in chats — per-response usage footer (OAuth shows tokens only)
- `/usage cost` in chats — local cost summary aggregated from OpenClaw session logs
- CLI: `openclaw status --usage` — full per-provider breakdown
- CLI: `openclaw channels list` — usage snapshot alongside provider config (use `--no-usage` to skip)
- macOS menu bar: "Usage" section under Context
### Supported Providers
| Provider | Auth |
|----------|------|
| Anthropic (Claude) | OAuth tokens in auth profiles |
| GitHub Copilot | OAuth tokens in auth profiles |
| Gemini CLI | OAuth tokens in auth profiles |
| OpenAI Codex | OAuth tokens in auth profiles (accountId used when present) |
| MiniMax | API key or MiniMax OAuth auth profile |
| Xiaomi MiMo | `XIAOMI_API_KEY` |
| z.ai | API key via env/config/auth store |
### MiniMax Gotcha
OpenClaw treats `minimax`, `minimax-cn`, and `minimax-portal` as the same quota surface. MiniMax's raw `usage_percent` / `usagePercent` fields mean **remaining** quota, so OpenClaw inverts them before display. Count-based fields win when present.
### Session-Level Fallback
`/status` and `session_status` can fall back to the latest transcript usage entry when the live session snapshot is sparse. Fallback fills missing token/cache counters, can recover active runtime model label, prefers the larger prompt-oriented total.
Usage is hidden when no usable provider usage auth can be resolved.
---
## Cross-References
This section maps related concepts to help you build a complete mental model.
### Context and Memory System
- **[Context](#context)** → defines what the model sees each turn
- **[Context Engine](#context-engine)** → plugin interface to customize context assembly
- **[Session Pruning](#session-pruning)** → reduces context by trimming tool results (in-memory)
- **[Compaction](#compaction)** → reduces context by summarizing conversation (persisted)
- **[Active Memory](#active-memory)** → proactively injects relevant memory before each reply
- **[Memory Overview](#memory-overview)** → the Markdown files that are the memory
- **[Memory — Builtin Engine](#memory--builtin-engine)** → SQLite-based default backend
- **[Memory — QMD Engine](#memory--qmd-engine)** → local-first sidecar with reranking
- **[Memory — Honcho](#memory--honcho)** → AI-native cross-session memory service
- **[Memory Search](#memory-search)** → how `memory_search` works (hybrid vector + keyword)
- **[Dreaming](#dreaming)** → background consolidation pass for long-term memory
### Agent and Session Lifecycle
- **[Architecture](#architecture)** → Gateway daemon; single entry point for all channels
- **[Agent Runtime](#agent-runtime)** → workspace, bootstrap files, skill loading
- **[Agent Loop](#agent-loop)** → execution cycle from intake to reply; hook points
- **[Agent Workspace](#agent-workspace)** → workspace directory layout and what each file means
- **[Session Management](#session-management)** → routing, DM isolation, lifecycle, maintenance
- **[Session Tools](#session-tools)** → `sessions_spawn`, `sessions_send`, `sessions_yield`, `subagents`
- **[Multi-Agent Routing](#multi-agent-routing)** → isolated agents per workspace + binding rules
- **[Command Queue](#command-queue)** → serialization of concurrent runs
- **[Retry Policy](#retry-policy)** → per-request retry behavior for providers and channels
### System Prompt and Personality
- **[System Prompt](#system-prompt)** → what the model sees; bootstrap injection; prompt modes
- **[SOUL.md Personality Guide](#soulmd-personality-guide)** → how to write an effective persona
- **[Timezones](#timezones)** → timestamp handling in envelopes and system prompt
### Models and Providers
- **[Models CLI](#models-cli)** → model selection, allowlists, switching in chat, CLI commands
- **[Model Providers](#model-providers)** → 35+ providers with auth, rotation, and key examples
- **[Model Failover](#model-failover)** → auth profile rotation, cooldowns, billing disables
- **[OAuth](#oauth)** → token sink pattern, PKCE flow, multiple account profiles
- **[Usage Tracking](#usage-tracking)** → provider quota and cost reporting
### Communication and Delivery
- **[Messages](#messages)** → full message lifecycle: inbound, debounce, silent replies
- **[Streaming and Chunking](#streaming-and-chunking)** → block streaming, preview streaming, coalescing
- **[Markdown Formatting](#markdown-formatting)** → IR pipeline, per-channel rendering
- **[Typing Indicators](#typing-indicators)** → when typing indicators fire and how to configure
- **[Presence](#presence)** → client visibility in the Gateway's Instances tab
### Infrastructure and Protocol
- **[TypeBox](#typebox)** → Gateway WS protocol schema; how to add methods; codegen
- **[Delegate Architecture](#delegate-architecture)** → running OpenClaw as an organizational agent
- **[Experimental Features](#experimental-features)** → flags behind opt-in gates
- **[Features](#features)** → full capability overview across all surfaces
### Quality Assurance
- **[QA E2E Automation](#qa-e2e-automation)** → QA lab, transport lanes, character eval, scenario format
- **[GPT-5.4 / Codex Agentic Parity](#gpt-54--codex-agentic-parity)** → strict-agentic execution contract, parity harness
### Integrations
- **[Pi Integration Architecture](#pi-integration-architecture)** → embedded AgentSession, pi SDK packages
- **[OpenProse](#openprose)** → markdown-first workflow format, multi-agent orchestration
---
## GPT-5.4 / Codex Agentic Parity
**What it is:** A parity program that fixes gaps where GPT-5.4 and Codex-style models underperform compared to Claude Opus 4.6 in agentic scenarios.
### Problem
GPT-5.4 could: stop after planning instead of acting, use strict OpenAI/Codex tool schemas incorrectly, request `/elevated full` when impossible, lose long-running task state during replay/compaction.
### Four PRs
1. **PR A: strict-agentic execution** — opt-in contract. Plan-only turns rejected; model must use tools or make progress. Retries with act-now steer, then fails closed with explicit blocked state.
2. **PR B: runtime truthfulness** — accurate error signals for auth failures, permission scope, DNS/timeout. Model stops hallucinating wrong remediation.
3. **PR C: execution correctness** — fixes OpenAI/Codex tool-schema compatibility (parameter-free tools, strict object-root). Long-task liveness surfacing (paused/blocked/abandoned visible).
4. **PR D: parity harness** — QA-lab parity pack for GPT-5.4 vs Opus 4.6 comparison with shared scenarios.
### Parity Report
```bash
pnpm openclaw qa parity-report \
--candidate-summary .artifacts/qa-e2e/gpt54/qa-suite-summary.json \
--baseline-summary .artifacts/qa-e2e/opus46/qa-suite-summary.json \
--output-dir .artifacts/qa-e2e/parity
```
Outputs: Markdown report, JSON verdict, pass/fail gate.
---
## Pi Integration Architecture
**What it is:** OpenClaw embeds the pi SDK (`pi-coding-agent`, `pi-ai`, `pi-agent-core`, `pi-tui`) to power its AI agent capabilities. It directly imports `createAgentSession()` instead of spawning pi as a subprocess.
### Embedded Approach Provides
- Full session lifecycle and event handling control
- Custom tool injection (messaging, sandbox, channel-specific)
- System prompt customization per channel/context
- Session persistence with branching/compaction
- Multi-account auth profile rotation with failover
- Provider-agnostic model switching
### Package Dependencies
| Package | Purpose |
|---|---|
| `pi-ai` | Core LLM abstractions: Model, streamSimple, message types, provider APIs |
| `pi-agent-core` | Agent loop, tool execution, AgentMessage types |
| `pi-coding-agent` | High-level SDK: createAgentSession, SessionManager, AuthStorage, ModelRegistry, built-in tools |
| `pi-tui` | Terminal UI components (used in OpenClaw's local TUI mode) |
### Key File Structure
- `src/agents/pi-embedded-runner/run.ts` — main entry: `runEmbeddedPiAgent()`
- `src/agents/pi-embedded-runner/run/attempt.ts` — single attempt logic with session setup
- `src/agents/pi-embedded-runner/compact.ts` — manual/auto compaction logic
- `src/agents/pi-embedded-runner/tools/` — OpenClaw-specific tool registrations
---
## OpenProse
**What it is:** A portable, markdown-first workflow format for orchestrating AI sessions. Ships as a bundled plugin (disabled by default) with a `/prose` slash command. Programs live in `.prose` files.
### Enable
```bash
openclaw plugins enable open-prose
```
Restart Gateway after enabling.
### Slash Command
```
/prose help
/prose run <file.prose>
/prose run <handle/slug> # resolves to https://p.prose.md/<handle>/<slug>
/prose run <url>
/prose compile <file.prose>
/prose examples
/prose update
```
### Example `.prose` File
```prose
input topic: "What should we research?"
agent researcher:
model: sonnet
prompt: "You research thoroughly and cite sources."
agent writer:
model: opus
prompt: "You write a concise summary."
parallel:
findings = session: researcher
prompt: "Research {topic}."
draft = session: writer
prompt: "Summarize {topic}."
session "Merge the findings + draft into a final answer."
context: { findings, draft }
```
### File Locations
- `.prose/` in workspace (runs, bindings, agents)
- `~/.prose/agents/` for user-level persistent agents
### State Modes
- **filesystem** (default)
- **in-context** (transient, small programs)
- **sqlite** (experimental)
- **postgres** (experimental, credentials in subagent logs — use least-privileged DB)
---
*End of OpenClaw Core Concepts — Complete Reference*
FILE:references/02-gateway.md
# OpenClaw Gateway — Complete Reference
> Compiled from all 39 official Gateway documentation pages at docs.openclaw.ai/gateway
---
## Table of Contents
- [Gateway Runbook (Overview)](#gateway-runbook-overview)
- [Authentication](#authentication)
- [Background Process (exec & process tools)](#background-process-exec--process-tools)
- [Bonjour / mDNS Discovery](#bonjour--mdns-discovery)
- [Bridge Protocol (REMOVED)](#bridge-protocol-removed)
- [CLI Backends](#cli-backends)
- [Config — Agents](#config--agents)
- [Config — Channels](#config--channels)
- [Config — Tools](#config--tools)
- [Configuration](#configuration)
- [Configuration Examples](#configuration-examples)
- [Configuration Reference](#configuration-reference)
- [Diagnostics Export](#diagnostics-export)
- [Discovery & Transports](#discovery--transports)
- [Doctor](#doctor)
- [Gateway Lock](#gateway-lock)
- [Health Checks](#health-checks)
- [Heartbeat](#heartbeat)
- [Local Models](#local-models)
- [Logging](#logging)
- [Multiple Gateways](#multiple-gateways)
- [Network Model](#network-model)
- [OpenAI Chat Completions HTTP API](#openai-chat-completions-http-api)
- [OpenResponses HTTP API](#openresponses-http-api)
- [OpenShell](#openshell)
- [Gateway-Owned Pairing](#gateway-owned-pairing)
- [Gateway Protocol](#gateway-protocol)
- [Remote Access](#remote-access)
- [Remote Gateway Setup (macOS)](#remote-gateway-setup-macos)
- [Sandboxing](#sandboxing)
- [Sandbox vs Tool Policy vs Elevated](#sandbox-vs-tool-policy-vs-elevated)
- [Secrets Management](#secrets-management)
- [Secrets Plan Contract](#secrets-plan-contract)
- [Security](#security)
- [Security Audit Checks](#security-audit-checks)
- [Tailscale](#tailscale)
- [Tools Invoke HTTP API](#tools-invoke-http-api)
- [Troubleshooting](#troubleshooting)
- [Trusted Proxy Auth](#trusted-proxy-auth)
## Gateway Runbook (Overview)
### What it is
The Gateway is OpenClaw's single always-on process for routing, control plane, and channel connections. It serves as the central hub multiplexing WebSocket control/RPC, HTTP APIs, Control UI, and hooks on a single port.
### Runtime Model
- **Single multiplexed port** for: WebSocket control/RPC, HTTP APIs (OpenAI-compatible), Control UI, hooks
- **Default bind**: `loopback` (127.0.0.1)
- **Default port**: `18789`
- **Auth required by default** — shared-secret or trusted-proxy
### Port and Bind Precedence
| Setting | Resolution order |
|---------|-----------------|
| Gateway port | `--port` → `OPENCLAW_GATEWAY_PORT` → `gateway.port` → `18789` |
| Bind mode | CLI/override → `gateway.bind` → `loopback` |
### 5-Minute Startup
```bash
openclaw gateway --port 18789
openclaw gateway --port 18789 --verbose # debug/trace
openclaw gateway --force # force-kill listener, then start
openclaw gateway status
openclaw status
openclaw logs --follow
openclaw channels status --probe
```
### Hot Reload Modes
| `gateway.reload.mode` | Behavior |
|----------------------|----------|
| `off` | No config reload |
| `hot` | Apply only hot-safe changes |
| `restart` | Restart on reload-required changes |
| `hybrid` (default) | Hot-apply when safe, restart when required |
### Operator Commands
```bash
openclaw gateway status
openclaw gateway status --deep # system-level service scan (LaunchDaemons/systemd/schtasks), NOT a deeper RPC probe
openclaw gateway status --require-rpc # need read-scope RPC proof, not just reachability
openclaw gateway status --json
openclaw gateway probe # can warn about "multiple reachable gateways"
openclaw gateway install
openclaw gateway restart
openclaw gateway stop
openclaw secrets reload
openclaw logs --follow
openclaw doctor
```
**Environment overrides for isolated instances:**
- `OPENCLAW_CONFIG_PATH` — config file path
- `OPENCLAW_STATE_DIR` — state directory
- `OPENCLAW_SKIP_CHANNELS=1` — skip channel initialization (useful for VoiceClaw-only test gateways)
### OpenAI-Compatible Endpoints
- `GET /v1/models` — returns agent targets (`openclaw`, `openclaw/default`, `openclaw/<agentId>`)
- `GET /v1/models/{id}`
- `POST /v1/embeddings`
- `POST /v1/chat/completions`
- `POST /v1/responses`
### VoiceClaw Real-Time Brain
- Endpoint: `/voiceclaw/realtime` (WebSocket)
- Uses Gemini Live for real-time audio
- Requires `GEMINI_API_KEY`
- Tool calls return immediate "working" result, then async execution
### Notes
- By default, the Gateway refuses to start unless `gateway.mode=local` is set in config. Use `--allow-unconfigured` for ad-hoc/dev runs **without** modifying the config file.
- `SIGUSR1` triggers an in-process restart when authorized. `commands.restart` is enabled by default; set `commands.restart: false` to block manual SIGUSR1 restarts while still allowing gateway tool/config apply/update.
### Common Failure Signatures
| Signature | Likely issue |
|-----------|-------------|
| `refusing to bind gateway ... without auth` | Non-loopback bind without auth |
| `another gateway instance is already listening` / `EADDRINUSE` | Port conflict |
| `Gateway start blocked: set gateway.mode=local` | Config set to remote mode |
| `unauthorized` during connect | Auth mismatch |
### Supervision
- **macOS**: launchd (`ai.openclaw.gateway` label)
- **Linux**: systemd user unit
- **Windows**: Scheduled Task or Startup-folder fallback
```bash
# macOS
openclaw gateway install
openclaw gateway status
# Linux
openclaw gateway install
systemctl --user enable --now openclaw-gateway.service
sudo loginctl enable-linger <user> # for persistence
# Dev profile
openclaw --dev setup
openclaw --dev gateway --allow-unconfigured
```
---
## Authentication
### What it is
Covers **model provider** authentication (API keys, OAuth, Claude CLI reuse). For gateway connection auth, see Configuration and Trusted Proxy Auth.
### API Key Setup (Recommended)
```bash
export <PROVIDER>_API_KEY="..."
# For daemon (systemd/launchd):
cat >> ~/.openclaw/.env <<'EOF'
<PROVIDER>_API_KEY=...
EOF
openclaw models status
```
### Claude CLI Reuse (Anthropic)
```bash
claude auth login
claude auth status --text
openclaw models auth login --provider anthropic --method cli --set-default
```
### API Key Rotation
Priority order:
1. `OPENCLAW_LIVE_<PROVIDER>_KEY` (single override)
2. `<PROVIDER>_API_KEYS`
3. `<PROVIDER>_API_KEY`
4. `<PROVIDER>_API_KEY_*`
- Google providers add `GOOGLE_API_KEY` as fallback
- Retries only on rate-limit errors (429, quota, throttle, concurrency)
### Per-Session Credential Control
```
/model <alias>@<profileId>
```
### Per-Agent Auth Order
```bash
openclaw models auth order get --provider anthropic
openclaw models auth order set --provider anthropic anthropic:default
openclaw models auth order clear --provider anthropic
```
### Automation Check
```bash
openclaw models status --check # exit 1=expired/missing, 2=expiring
openclaw models status --probe # live probes
```
### Auth Profile Refs
- `api_key` credentials: `keyRef: { source, provider, id }`
- `token` credentials: `tokenRef: { source, provider, id }`
- OAuth profiles reject SecretRef input
---
## Background Process (exec & process tools)
### exec Tool Parameters
| Parameter | Default | Description |
|-----------|---------|-------------|
| `command` | required | Shell command |
| `yieldMs` | 10000 | Auto-background after this delay |
| `background` | false | Background immediately |
| `timeout` | 1800s | Kill after timeout |
| `elevated` | false | Run outside sandbox |
| `pty` | false | Real TTY |
| `workdir` | — | Working directory |
| `env` | — | Environment overrides |
### Config Keys
| Key | Default | Description |
|-----|---------|-------------|
| `tools.exec.backgroundMs` | 10000 | Auto-background delay |
| `tools.exec.timeoutSec` | 1800 | Process timeout |
| `tools.exec.cleanupMs` | 1800000 | Cleanup delay |
| `tools.exec.notifyOnExit` | true | System event on exit |
| `tools.exec.notifyOnExitEmptySuccess` | false | Notify on clean exit |
### process Tool Actions
- `list` — running + finished sessions
- `poll` — drain new output (reports exit status)
- `log` — read aggregated output (offset + limit; default last 200 lines)
- `write` — send stdin (data, optional eof)
- `send-keys` — explicit key tokens to PTY
- `submit` — send Enter to PTY
- `paste` — literal text with optional bracketed paste
- `kill` — terminate background session
- `clear` — remove finished session
- `remove` — kill if running, clear if finished
### Important Notes
- Sessions scoped per agent
- Lost on process restart (no disk persistence)
- Logs saved to chat only if polled
- `OPENCLAW_SHELL=exec` set in spawned commands
---
## Bonjour / mDNS Discovery
### What it is
Uses mDNS/DNS-SD to discover Gateway via `_openclaw-gw._tcp` service type. LAN-only multicast via bundled bonjour plugin (enabled by default).
### Wide-Area Bonjour (Tailscale)
```json5
{
gateway: { bind: "tailnet" },
discovery: { wideArea: { enabled: true } },
}
```
```bash
openclaw dns setup --apply
```
### TXT Keys (non-secret hints)
`role`, `displayName`, `lanHost`, `gatewayPort`, `gatewayTls`, `gatewayTlsSha256`, `canvasPort`, `transport`, `tailnetDns`, `sshPort`, `cliPath`
### Disabling
```bash
openclaw plugins disable bonjour
# or
OPENCLAW_DISABLE_BONJOUR=1
```
---
## Bridge Protocol (REMOVED)
**TCP bridge has been removed** from current builds. `bridge.*` config keys no longer in schema. Historical reference only. Current clients use WebSocket Gateway Protocol.
---
## CLI Backends
### What it is
Local AI CLIs as text-only fallback when API providers are down. Conservative safety net, not primary path.
### Bundled Backends
- `codex-cli` (OpenAI plugin)
- `claude-cli` (Anthropic)
- `google-gemini-cli` (Google)
### Configuration
```json5
{
agents: {
defaults: {
cliBackends: {
"codex-cli": {
command: "/opt/homebrew/bin/codex",
},
},
},
},
}
```
### Key Config Fields
| Field | Description |
|-------|-------------|
| `command` | Path to CLI binary |
| `args` | CLI arguments |
| `output` | `json` (default), `jsonl`, `text` |
| `input` | `arg` (default), `stdin` |
| `modelArg` | Model flag (e.g. `--model`) |
| `sessionArg` | Session ID flag |
| `sessionMode` | `always`, `existing`, `none` |
| `bundleMcp` | Enable loopback MCP bridge for gateway tools |
| `imageArg` | Image file path flag |
### bundleMcp
When `true`, spawns loopback HTTP MCP server exposing gateway tools to the CLI. Authenticated per-session.
---
## Config — Agents
### Workspace & Bootstrap
```json5
{
agents: {
defaults: {
workspace: "~/.openclaw/workspace",
repoRoot: "~/Projects/openclaw",
skipBootstrap: false,
contextInjection: "always", // or "continuation-skip"
bootstrapMaxChars: 12000,
bootstrapTotalMaxChars: 60000,
},
},
}
```
### Model Configuration
```json5
{
agents: {
defaults: {
model: {
primary: "anthropic/claude-opus-4-6",
fallbacks: ["openai/gpt-5.4"],
},
models: {
"anthropic/claude-opus-4-6": { alias: "opus" },
},
imageModel: { primary: "..." },
imageGenerationModel: { primary: "openai/gpt-image-2" },
videoGenerationModel: { primary: "qwen/wan2.6-t2v" },
pdfModel: { primary: "anthropic/claude-opus-4-6" },
},
},
}
```
### Built-in Aliases
| Alias | Model |
|-------|-------|
| `opus` | `anthropic/claude-opus-4-6` |
| `sonnet` | `anthropic/claude-sonnet-4-6` |
| `gpt` | `openai/gpt-5.4` |
| `gpt-mini` | `openai/gpt-5.4-mini` |
| `gpt-nano` | `openai/gpt-5.4-nano` |
| `gemini` | `google/gemini-3.1-pro-preview` |
| `gemini-flash` | `google/gemini-3-flash-preview` |
| `gemini-flash-lite` | `google/gemini-3.1-flash-lite-preview` |
### Embedded Harness
```json5
{
agents: {
defaults: {
embeddedHarness: {
runtime: "auto", // auto | pi | <harness-id>
fallback: "pi", // pi | none
},
},
},
}
```
### Context Budget Keys
- `agents.defaults.bootstrapMaxChars` / `bootstrapTotalMaxChars` — workspace bootstrap
- `agents.defaults.startupContext.*` — /new and /reset prelude
- `skills.limits.maxSkillsPromptChars` — skills list injection
- `agents.defaults.contextLimits.*` — runtime excerpts
- `memory.qmd.limits.*` — memory search snippets
### Other Key Settings
- `maxConcurrent`: 4 (default) — max parallel agent runs
- `imageMaxDimensionPx`: 1200 — image downscaling
- `userTimezone` — system prompt timezone
- `timeFormat`: `auto` | `12` | `24`
---
## Config — Channels
### DM Policies
| Policy | Behavior |
|--------|----------|
| `pairing` (default) | One-time pairing code; owner approves |
| `allowlist` | Only senders in `allowFrom` |
| `open` | Allow all (requires `allowFrom: ["*"]`) |
| `disabled` | Ignore all DMs |
### Group Policies
| Policy | Behavior |
|--------|----------|
| `allowlist` (default) | Only matching allowlist |
| `open` | Bypass allowlists (mention-gating applies) |
| `disabled` | Block all group messages |
### Channel Model Overrides
```json5
{
channels: {
modelByChannel: {
telegram: { "-1001234567890": "openai/gpt-4.1-mini" },
},
},
}
```
### WhatsApp
- Multi-account via `accounts` sub-object
- `sendReadReceipts`, `textChunkLimit`, `mediaMaxMb`
- Groups: `requireMention`, `groupPolicy`, `groupAllowFrom`
### Telegram
- `botToken`, topics support, `customCommands`
- Streaming modes: `off` | `partial` | `block` | `progress`
- Proxy: `proxy: "socks5://localhost:9050"`
- Webhook: `webhookUrl`, `webhookSecret`, `webhookPath`
### Discord
- Guilds with channels, voice, `threadBindings`
- `execApprovals` with approvers and target (dm/channel/both)
- Components v2 with `ui.components.accentColor`
- Voice with auto-join and DAVE encryption
### Slack
- Socket mode: `botToken` + `appToken`
- HTTP mode: `botToken` + `signingSecret`
- Native streaming, slash commands
- `execApprovals` similar to Discord
---
## Config — Tools
### Tool Profiles
| Profile | Includes |
|---------|----------|
| `minimal` | `session_status` only |
| `coding` | fs, runtime, web, sessions, memory, cron, image tools |
| `messaging` | messaging, sessions_list/history/send, session_status |
| `full` | No restriction |
### Tool Groups
| Group | Tools |
|-------|-------|
| `group:runtime` | exec, process, code_execution |
| `group:fs` | read, write, edit, apply_patch |
| `group:sessions` | sessions_list/history/send/spawn/yield, subagents, session_status |
| `group:memory` | memory_search, memory_get |
| `group:web` | web_search, x_search, web_fetch |
| `group:ui` | browser, canvas |
| `group:automation` | cron, gateway |
| `group:messaging` | message |
| `group:nodes` | nodes |
| `group:agents` | agents_list |
| `group:media` | image, image_generate, video_generate, tts |
| `group:openclaw` | All built-in tools |
### Allow/Deny
```json5
{ tools: { deny: ["browser", "canvas"] } }
```
- `deny` always wins
- Non-empty `allow` = everything else blocked
- Case-insensitive, supports `*` wildcards
### Per-Provider Tool Restrictions
```json5
{
tools: {
byProvider: {
"openai/gpt-5.4": { allow: ["group:fs", "sessions_list"] },
},
},
}
```
### Elevated Exec
```json5
{
tools: {
elevated: {
enabled: true,
allowFrom: {
whatsapp: ["+15555550123"],
discord: ["1234567890123"],
},
},
},
}
```
### Loop Detection
```json5
{
tools: {
loopDetection: {
enabled: true, // disabled by default
historySize: 30,
warningThreshold: 10,
criticalThreshold: 20,
globalCircuitBreakerThreshold: 30,
},
},
}
```
### Custom Providers
```json5
{
models: {
mode: "merge", // merge | replace
providers: {
"custom-proxy": {
baseUrl: "http://localhost:4000/v1",
apiKey: "LITELLM_KEY",
api: "openai-completions", // openai-responses | anthropic-messages | google-generative-ai
models: [{ id: "llama-3.1-8b", contextWindow: 128000, maxTokens: 32000 }],
},
},
},
}
```
---
## Configuration
### What it is
JSON5 config at `~/.openclaw/openclaw.json`. Strict validation — unknown keys refuse startup.
### Editing Methods
1. `openclaw onboard` / `openclaw configure` — interactive wizard
2. `openclaw config get/set/unset` — CLI one-liners
3. Control UI at `http://127.0.0.1:18789` — Config tab
4. Direct edit — file watched for hot reload
### Strict Validation
- Unknown keys = Gateway refuses to start
- Only `$schema` allowed as unknown root key
- Run `openclaw doctor` to diagnose, `openclaw doctor --fix` to repair
- Last-known-good recovery: broken config saved as `.clobbered.*`
- **Plugin-local failures:** When every validation issue is scoped to `plugins.entries.<id>...`, OpenClaw does **not** perform whole-file recovery. It surfaces the plugin-local failure so a plugin schema mismatch cannot roll back unrelated user settings.
- Promotion to last-known-good is skipped when a candidate contains redacted secret placeholders (`***`).
- **Symlink warning:** Symlinked `openclaw.json` is unsupported for OpenClaw-owned writes; an atomic write may replace the path instead of preserving the symlink. Use `OPENCLAW_CONFIG_PATH` to point directly at the real file.
- `openclaw config schema` prints the canonical JSON Schema. `config.schema.lookup` fetches a single path-scoped node plus child summaries for drill-down tooling.
### $include (Config Splitting)
```json5
{
gateway: { port: 18789 },
agents: { $include: "./agents.json5" },
broadcast: { $include: ["./clients/a.json5", "./clients/b.json5"] },
}
```
- Single file: replaces containing object
- Array: deep-merged in order
- Nested: up to 10 levels
---
## Configuration Examples
### Minimal
```json5
{
agent: { workspace: "~/.openclaw/workspace" },
channels: { whatsapp: { allowFrom: ["+15555550123"] } },
}
```
### Multi-Platform
```json5
{
channels: {
whatsapp: { allowFrom: ["+15555550123"] },
telegram: { enabled: true, botToken: "...", allowFrom: ["123456789"] },
discord: { enabled: true, token: "...", dm: { allowFrom: ["123456789012345678"] } },
},
}
```
### Secure DM Mode
```json5
{
session: { dmScope: "per-channel-peer" },
channels: {
whatsapp: { dmPolicy: "allowlist", allowFrom: ["+1...", "+1..."] },
},
}
```
---
## Configuration Reference
### Top-Level Sections
- `agents.*` → see Config — Agents
- `channels.*` → see Config — Channels
- `tools.*` → see Config — Tools
- `gateway.*` → Gateway runtime
- `session.*` → Session lifecycle
- `messages.*` → Message delivery
- `talk.*` → Talk mode
- `models.*` → Custom providers
- `skills.*` — skill allowlists, install prefs, entries
- `plugins.*` — plugin enable/disable, config, hooks
- `browser.*` — browser profiles, SSRF policy
- `cron.*` — scheduled jobs
- `hooks.*` — webhook endpoints
- `logging.*` — log level, file, redaction
### Gateway Section
```json5
{
gateway: {
mode: "local", // local | remote
port: 18789,
bind: "loopback", // auto | loopback | lan | tailnet | custom
auth: {
mode: "token", // none | token | password | trusted-proxy
token: "your-token",
allowTailscale: true,
rateLimit: { maxAttempts: 10, windowMs: 60000, lockoutMs: 300000 },
},
tailscale: { mode: "off" }, // off | serve | funnel
controlUi: { enabled: true, basePath: "/openclaw" },
remote: { url: "ws://...", token: "..." },
trustedProxies: ["10.0.0.1"],
channelHealthCheckMinutes: 5,
channelStaleEventThresholdMinutes: 30,
channelMaxRestartsPerHour: 10,
},
}
```
---
## Diagnostics Export
### Quick Start
```bash
openclaw gateway diagnostics export
openclaw gateway diagnostics export --output openclaw-diagnostics.zip
openclaw gateway diagnostics export --json
```
### Contents
- `summary.md` — human-readable overview
- `diagnostics.json` — machine-readable
- Sanitized config shape (no secrets)
- Sanitized log summaries
- Stability bundle
### Stability Recorder
```bash
openclaw gateway stability
openclaw gateway stability --bundle latest
openclaw gateway stability --bundle latest --export
```
Set `diagnostics.enabled: false` to disable the stability recorder entirely.
### Privacy Model
- Keeps: subsystem names, status codes, durations, byte counts
- Omits: chat text, credentials, tokens, raw bodies, account IDs
---
## Discovery & Transports
### Discovery Inputs
1. **Bonjour/DNS-SD** — LAN multicast + optional wide-area unicast
2. **Tailnet** — MagicDNS name or stable IP
3. **Manual/SSH** — SSH tunnel fallback
### Transport Selection (Recommended)
1. Paired direct endpoint if reachable → use it
2. Discovery finds gateway → offer "Use this gateway"
3. Tailnet DNS/IP configured → try direct
4. Fall back to SSH
---
## Doctor
### What it is
Repair + migration tool. Fixes stale config/state, checks health, provides repair steps.
```bash
openclaw doctor # interactive
openclaw doctor --yes # accept all defaults
openclaw doctor --repair # apply recommended repairs
openclaw doctor --deep # scan system services
openclaw doctor --non-interactive # safe migrations only
```
### What it Does
- Config normalization for legacy values
- Legacy state migrations (sessions, agent dir, WhatsApp auth)
- Model auth health (OAuth expiry check/refresh)
- Sandbox image repair
- Service config audit
- Security warnings for open DM policies
- Shell completion auto-install
- Memory search embedding readiness check
---
## Gateway Lock
### Mechanism
- Gateway binds WebSocket listener exclusively on startup
- `EADDRINUSE` → throws `GatewayLockError`
- OS releases listener on any process exit (crashes, SIGKILL)
- No separate lock file needed
---
## Health Checks
### Quick Checks
```bash
openclaw status # local summary
openclaw status --all # full diagnosis
openclaw status --deep # live health probe
openclaw health # gateway health snapshot
openclaw health --verbose # force live probe
openclaw health --json # machine-readable
```
### Health Monitor Config
| Key | Default | Description |
|-----|---------|-------------|
| `gateway.channelHealthCheckMinutes` | 5 | Check interval (0 disables) |
| `gateway.channelStaleEventThresholdMinutes` | 30 | Idle threshold |
| `gateway.channelMaxRestartsPerHour` | 10 | Restart cap |
### Per-Channel Override
```json5
{
channels: {
telegram: { healthMonitor: { enabled: false } },
},
}
```
---
## Heartbeat
### What it is
Periodic agent turns in the main session so the model can surface anything needing attention.
### Configuration
```json5
{
agents: {
defaults: {
heartbeat: {
every: "30m", // default; 1h for Anthropic OAuth/CLI
target: "last", // none | last | <channel-id>
to: "+15551234567", // optional recipient override (E.164 for WhatsApp, Telegram chat id, or topic format)
accountId: "ops-bot", // optional account id for multi-account channels
session: "main", // optional session key: "main" (default) or explicit session key
directPolicy: "allow", // allow | block
lightContext: false, // only inject HEARTBEAT.md
isolatedSession: false, // fresh session each run
includeReasoning: false, // deliver separate Reasoning: message when available
suppressToolErrorWarnings: false, // suppress tool error warning payloads during heartbeat runs
model: "anthropic/claude-opus-4-6",
prompt: "Read HEARTBEAT.md...",
ackMaxChars: 300,
activeHours: { start: "09:00", end: "22:00", timezone: "America/New_York" },
},
},
},
}
```
### Response Contract
- Nothing to report → reply `HEARTBEAT_OK`
- `HEARTBEAT_OK` at start/end stripped if remaining ≤ `ackMaxChars`
- Alerts → do NOT include `HEARTBEAT_OK`
### activeHours Timezone Options
- Omitted / `"user"`: uses `agents.defaults.userTimezone` if set, otherwise host timezone
- `"local"`: always uses the host system timezone
- Any IANA identifier (e.g. `"America/New_York"`): used directly
- **⚠️ Zero-width window gotcha:** Do NOT set `start` and `end` to the same time (e.g., `08:00` to `08:00`). This is treated as a zero-width window, so heartbeats will always be skipped.
### HEARTBEAT.md Tasks Block
```md
tasks:
- name: inbox-triage
interval: 30m
prompt: "Check for urgent unread emails."
- name: calendar-scan
interval: 2h
prompt: "Check for upcoming meetings."
```
Tasks behavior:
- Only **due** tasks are included in each heartbeat tick.
- If no tasks are due, the heartbeat is **skipped entirely** (`reason=no-tasks-due`) to avoid a wasted model call.
- Task timestamps are only advanced after a heartbeat run completes its **normal reply path**. Skipped runs (`empty-heartbeat-file` or `no-tasks-due`) do **NOT** mark tasks as completed.
### HEARTBEAT.md Empty Detection
If `HEARTBEAT.md` exists but is effectively empty (only blank lines and markdown headers like `# Heading`), OpenClaw skips the heartbeat run (`reason=empty-heartbeat-file`) to save API calls.
### Visibility Controls
```yaml
channels:
defaults:
heartbeat:
showOk: false # suppress OK acks
showAlerts: true # deliver alerts
useIndicator: true # emit indicator events
telegram:
heartbeat:
showOk: true # show OK acks on Telegram
whatsapp:
accounts:
work:
heartbeat:
showAlerts: false # suppress alerts for this account
```
**Precedence:** per-account → per-channel → channel defaults → built-in defaults
If **all three** flags (`showOk`, `showAlerts`, `useIndicator`) are false, OpenClaw skips the heartbeat run entirely (no model call).
### Per-Agent Heartbeats
If any `agents.list[]` entry includes a `heartbeat` block, **only those agents** run heartbeats. The per-agent block merges on top of `agents.defaults.heartbeat`.
### Queue and Delivery Behavior
- If the main queue is busy, the heartbeat is **skipped** and retried later.
- If the resolved heartbeat target supports typing, OpenClaw **shows typing** while the heartbeat run is active (disabled by `typingMode: "never"`).
- **Background task wake:** Detached background tasks can enqueue a system event to wake heartbeat when the main session should notice something quickly. That wake does not make the heartbeat run a background task.
- **Control UI/WebChat:** Heartbeat prompts and OK-only acks are hidden in history. The session transcript still contains those turns for audit/replay.
- **Stray HEARTBEAT_OK:** Outside heartbeat runs, `HEARTBEAT_OK` at start/end of a message is stripped and logged; a message that is only `HEARTBEAT_OK` is dropped.
### Heartbeat Session Keep-alive
Heartbeat-only replies do **not** keep the session alive. The last `updatedAt` timestamp is restored after a heartbeat run, so idle expiry behaves normally.
### Manual Wake (On-Demand)
To trigger an immediate heartbeat run:
```bash
openclaw system event --text "Check for urgent follow-ups" --mode now
```
---
## Local Models
### Requirements
- Aim for ≥2 maxed-out Mac Studios or equivalent (~$30k+)
- Single 24GB GPU works for lighter prompts
- Use largest model variant you can run
### LM Studio Setup (Recommended)
```json5
{
agents: { defaults: { model: { primary: "lmstudio/my-local-model" } } },
models: {
mode: "merge",
providers: {
lmstudio: {
baseUrl: "http://127.0.0.1:1234/v1",
apiKey: "lmstudio",
api: "openai-responses",
models: [{ id: "my-local-model", contextWindow: 196608, maxTokens: 8192 }],
},
},
},
}
```
### Compatibility Notes
- `compat.requiresStringContent: true` for string-only backends
- `compat.supportsTools: false` for backends that can't handle tool schemas
- Context warning at <32k, blocked at <16k
---
## Logging
### Two Surfaces
- **Console output** — controlled by `logging.consoleLevel` and `--verbose`
- **File logs** (JSONL) — controlled by `logging.level`
### Config
```json5
{
logging: {
level: "info",
file: "/tmp/openclaw/openclaw-YYYY-MM-DD.log",
consoleLevel: "info",
consoleStyle: "pretty", // pretty | compact | json
redactSensitive: "tools", // off | tools
redactPatterns: [], // custom regex array
},
}
```
### WS Log Modes
- Normal: only errors and slow calls (≥50ms)
- Verbose (`--verbose`): all WS traffic
- `--ws-log auto|compact|full`
---
## Multiple Gateways
### When to Use
- One gateway recommended for most setups
- Separate for isolation or rescue bot
### Rescue Bot Quickstart
```bash
openclaw --profile rescue onboard
openclaw --profile rescue gateway install --port 19789
```
### Isolation Checklist
- Unique `OPENCLAW_CONFIG_PATH`
- Unique `OPENCLAW_STATE_DIR`
- Unique `agents.defaults.workspace`
- Unique `gateway.port`
- Leave ≥20 ports between base ports
### Derived Ports
- Browser control = base + 2
- CDP auto-allocates from controlPort+9..+108
---
## Network Model
Core rules:
- One Gateway per host recommended
- Loopback WS default: `ws://127.0.0.1:18789`
- Canvas served on same port: `/__openclaw__/canvas/` and `/__openclaw__/a2ui/`
- Remote use via SSH tunnel or tailnet VPN
---
## OpenAI Chat Completions HTTP API
### Enabling
```json5
{
gateway: {
http: { endpoints: { chatCompletions: { enabled: true } } },
},
}
```
### Endpoints (disabled by default)
- `POST /v1/chat/completions`
- `GET /v1/models` / `GET /v1/models/{id}`
- `POST /v1/embeddings`
### Agent-First Model Contract
- `model: "openclaw"` → default agent
- `model: "openclaw/default"` → stable alias
- `model: "openclaw/<agentId>"` → specific agent
- `x-openclaw-model` → backend model override
- `x-openclaw-session-key` → session routing
### Security
- **Full operator-access surface** — treat token like owner credential
- Shared-secret auth ignores narrower `x-openclaw-scopes`
- Keep on loopback/tailnet/private only
### Open WebUI Quick Setup
- Base URL: `http://127.0.0.1:18789/v1`
- API key: gateway bearer token
- Model: `openclaw/default`
---
## OpenResponses HTTP API
### Enabling
```json5
{
gateway: {
http: { endpoints: { responses: { enabled: true } } },
},
}
```
### Supported Input Types
- `message` — roles: system, developer, user, assistant
- `function_call_output` — tool results
- `input_image` — base64/URL (jpeg/png/gif/webp/heic/heif, max 10MB)
- `input_file` — base64/URL (text/markdown/html/csv/json/pdf, max 5MB)
### Config
```json5
{
gateway: {
http: {
endpoints: {
responses: {
enabled: true,
maxBodyBytes: 20000000,
maxUrlParts: 8,
files: { allowUrl: true, maxBytes: 5242880, maxChars: 200000 },
images: { allowUrl: true, maxBytes: 10485760 },
},
},
},
},
}
```
### SSE Events
`response.created`, `response.in_progress`, `response.output_item.added`, `response.content_part.added`, `response.output_text.delta`, `response.output_text.done`, `response.content_part.done`, `response.output_item.done`, `response.completed`, `response.failed`
---
## OpenShell
### What it is
Managed sandbox backend. Delegates lifecycle to `openshell` CLI via SSH transport.
### Workspace Modes
| | `mirror` | `remote` |
|--|---------|---------|
| **Canonical** | Local host | Remote OpenShell |
| **Sync** | Bidirectional (each exec) | One-time seed |
| **Per-turn overhead** | Higher | Lower |
| **Best for** | Dev workflows | Long-running agents, CI |
### Configuration
```json5
{
agents: { defaults: { sandbox: { mode: "all", backend: "openshell" } } },
plugins: {
entries: {
openshell: {
enabled: true,
config: { from: "openclaw", mode: "remote" },
},
},
},
}
```
### Key Config Keys
`mode`, `command`, `from`, `gateway`, `gatewayEndpoint`, `policy`, `providers`, `gpu`, `autoProviders`, `remoteWorkspaceDir` (/sandbox), `remoteAgentWorkspaceDir` (/agent), `timeoutSeconds` (120)
---
## Gateway-Owned Pairing
### What it is
Node identity/trust system. Gateway is source of truth for allowed nodes.
### Flow
1. Node connects → pending request stored → `node.pair.requested` emitted
2. Approve/reject via CLI or UI
3. Approval issues fresh token
4. Node reconnects with token
### CLI
```bash
openclaw nodes pending
openclaw nodes approve <requestId>
openclaw nodes reject <requestId>
openclaw nodes status
openclaw nodes rename --node <id|name> --name "Living Room iPad"
```
### Approval Scope Requirements
- Commandless: `operator.pairing`
- Non-exec commands: + `operator.write`
- `system.run`: + `operator.admin`
### Important (2026.3.31+)
- Node commands disabled until pairing approved
- Node-originated runs on reduced trusted surface
### Storage
- `~/.openclaw/nodes/paired.json`
- `~/.openclaw/nodes/pending.json`
---
## Gateway Protocol
### Transport
- WebSocket, text frames with JSON payloads
- First frame **must** be `connect`
- Pre-connect: 64 KiB cap; post-handshake: follow `hello-ok.policy.maxPayload`
### Handshake
1. Gateway sends `connect.challenge` (nonce + timestamp)
2. Client sends `connect` with role, scopes, caps, commands, auth, device info
3. Gateway returns `hello-ok` with protocol version, server info, features, snapshot, policy
### Framing
- **Request**: `{type:"req", id, method, params}`
- **Response**: `{type:"res", id, ok, payload|error}`
- **Event**: `{type:"event", event, payload, seq?, stateVersion?}`
### Roles
- `operator` — control plane (CLI/UI/automation)
- `node` — capability host (camera/screen/canvas/system.run)
### Operator Scopes
`operator.read`, `operator.write`, `operator.admin`, `operator.approvals`, `operator.pairing`, `operator.talk.secrets`
### Broadcast Scoping
- Chat/agent/tool-result: require `operator.read`
- Plugin broadcasts: `operator.write` or `operator.admin`
- Status/transport events: unrestricted
### Major RPC Families
- System: `health`, `status`, `system-presence`, `system-event`
- Models: `models.list`, `usage.status`, `usage.cost`
- Channels: `channels.status`, `channels.logout`, `web.login.*`
- Sessions: `sessions.list/create/send/steer/abort/patch/reset/delete/compact`
- Config: `config.get/set/patch/apply/schema`
- Agents: `agents.list/create/update/delete`
- Nodes: `node.list/describe/invoke/event`
- Automation: `cron.list/status/add/update/remove/run`
---
## Remote Access
### SSH Tunnel (Universal Fallback)
```bash
ssh -N -L 18789:127.0.0.1:18789 user@host
```
### CLI Remote Defaults
```json5
{
gateway: {
mode: "remote",
remote: { url: "ws://127.0.0.1:18789", token: "your-token" },
},
}
```
### Credential Precedence
- Explicit `--token`/`--password` always win
- CLI `--url` never reuses implicit credentials
- Local mode: `OPENCLAW_GATEWAY_TOKEN` → `gateway.auth.token` → `gateway.remote.token`
- Remote mode: `gateway.remote.token` → `OPENCLAW_GATEWAY_TOKEN` → `gateway.auth.token`
### Security
- Keep Gateway loopback-only unless needed
- `OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1` for plaintext ws:// on private networks (process env only)
- `gateway.remote.tlsFingerprint` pins remote TLS cert
---
## Remote Gateway Setup (macOS)
Merged into Remote Access. SSH tunnel + LaunchAgent for persistence.
```xml
<!-- ~/Library/LaunchAgents/ai.openclaw.ssh-tunnel.plist -->
<plist version="1.0"><dict>
<key>Label</key><string>ai.openclaw.ssh-tunnel</string>
<key>ProgramArguments</key><array>
<string>/usr/bin/ssh</string><string>-N</string><string>remote-gateway</string>
</array>
<key>KeepAlive</key><true/>
<key>RunAtLoad</key><true/>
</dict></plist>
```
---
## Sandboxing
### Modes
| Mode | Behavior |
|------|----------|
| `off` | No sandboxing |
| `non-main` | Sandbox only non-main sessions |
| `all` | Every session sandboxed |
### Scope
| Scope | Containers |
|-------|-----------|
| `agent` (default) | One per agent |
| `session` | One per session |
| `shared` | One for all |
### Backends
| Backend | Where | Setup |
|---------|-------|-------|
| `docker` (default) | Local container | `scripts/sandbox-setup.sh` |
| `ssh` | SSH-accessible host | SSH key + target |
| `openshell` | OpenShell managed | Plugin enabled |
### Workspace Access
- `none` (default): sandbox workspace only
- `ro`: workspace read-only at `/agent`
- `rw`: workspace read-write at `/workspace`
### Docker Bind Mounts
```json5
{
agents: {
defaults: {
sandbox: {
docker: {
binds: ["/home/user/source:/source:ro"],
network: "none", // default: no network
},
},
},
},
}
```
**Blocked bind sources**: docker.sock, /etc, /proc, /sys, /dev, credential roots (~/.aws, ~/.ssh, etc.)
### Docker-out-of-Docker (DooD)
When the Gateway itself runs as a Docker container, it orchestrates sibling sandbox containers via the host's Docker socket. **Config `workspace` must contain the host's absolute path**, not the Gateway container path. The Gateway deployment must include an identical volume map (`-v /home/user/.openclaw:/home/user/.openclaw`) for FS bridge parity.
### Sandbox Browser
- Auto-starts by default (`sandbox.browser.autoStart`, `sandbox.browser.autoStartTimeoutMs`)
- Dedicated Docker network (`openclaw-sandbox-browser`, configurable via `sandbox.browser.network`)
- noVNC password-protected; short-lived token URL with password in URL fragment
- `cdpSourceRange` for CIDR allowlist
- `allowHostControl` lets sandboxed sessions target the host browser
- Custom target allowlists: `allowedControlUrls`, `allowedControlHosts`, `allowedControlPorts`
---
## Sandbox vs Tool Policy vs Elevated
### Three Controls
1. **Sandbox** (`sandbox.*`) — where tools run
2. **Tool policy** (`tools.*`) — which tools allowed
3. **Elevated** (`tools.elevated.*`) — exec-only host escape
### Key Rules
- `deny` always wins in tool policy
- Tool policy is the hard stop — /exec cannot override denied tool
- Elevated only affects `exec`, not other tools
- Elevated is NOT skill-scoped
### Debug
```bash
openclaw sandbox explain
openclaw sandbox explain --session agent:main:main
openclaw sandbox explain --json
```
---
## Secrets Management
### SecretRef Contract
```json5
{ source: "env" | "file" | "exec", provider: "default", id: "..." }
```
### Sources
- **env**: `{ source: "env", provider: "default", id: "OPENAI_API_KEY" }`
- **file**: `{ source: "file", provider: "filemain", id: "/providers/openai/apiKey" }`
- **exec**: `{ source: "exec", provider: "vault", id: "providers/openai/apiKey" }`
### Provider Config
```json5
{
secrets: {
providers: {
default: { source: "env" },
filemain: { source: "file", path: "~/.openclaw/secrets.json", mode: "json" },
vault: { source: "exec", command: "/usr/local/bin/resolver", jsonOnly: true },
},
},
}
```
### Runtime Model
- Resolution is eager during activation, not lazy
- Startup fails fast on unresolvable active refs
- Reload uses atomic swap (full success or keep last-known-good)
- Inactive surfaces don't block startup
### Exec Provider
- Runs absolute binary path, no shell
- `allowSymlinkCommand: true` for Homebrew shims
- Request: `{ protocolVersion: 1, provider: "vault", ids: [...] }`
- Response: `{ protocolVersion: 1, values: { ... } }`
---
## Secrets Plan Contract
### `secrets apply` Format
```json
{ "version": 1, "protocolVersion": 1, "targets": [...] }
```
### Target Fields
- `type` — recognized target type
- `path` — config path
- `ref` — SecretRef object
- Forbidden segments: `__proto__`, `prototype`, `constructor`
### Validation
```bash
openclaw secrets apply --from plan.json --dry-run
openclaw secrets apply --from plan.json
```
- Exec SecretRefs rejected in write mode unless `--allow-exec`
---
## Security
### Trust Model
- **Personal assistant model** — one trusted operator per gateway
- NOT a hostile multi-tenant boundary
- Gateway = control plane; Node = remote execution surface
- `sessionKey` = routing, not auth
- Exec approvals are guardrails, not isolation
### Security Audit
```bash
openclaw security audit
openclaw security audit --deep
openclaw security audit --fix
openclaw security audit --json
```
### Hardened Baseline
```json5
{
gateway: { mode: "local", bind: "loopback", auth: { mode: "token", token: "..." } },
session: { dmScope: "per-channel-peer" },
tools: {
profile: "messaging",
deny: ["group:automation", "group:runtime", "group:fs", "sessions_spawn", "sessions_send"],
exec: { security: "deny", ask: "always" },
elevated: { enabled: false },
},
channels: { whatsapp: { dmPolicy: "pairing", groups: { "*": { requireMention: true } } } },
}
```
### Context Visibility
- `all` (default) — include all context
- `allowlist` — filter to allowlisted senders
- `allowlist_quote` — allowlist but keep explicit quote
### Credential Storage Map
- WhatsApp: `~/.openclaw/credentials/whatsapp/<accountId>/creds.json`
- Telegram: config/env or `tokenFile`
- Pairing: `~/.openclaw/credentials/<channel>-allowFrom.json`
- Auth profiles: `~/.openclaw/agents/<agentId>/agent/auth-profiles.json`
- Secrets: `~/.openclaw/secrets.json`
---
## Security Audit Checks
### Critical Checks
| checkId | Description |
|---------|-------------|
| `fs.state_dir.perms_world_writable` | State dir world-writable |
| `fs.config.perms_writable` | Config writable by others |
| `fs.config.perms_world_readable` | Config world-readable |
| `fs.config_include.perms_writable` | Config include file writable |
| `fs.config_include.perms_world_readable` | Included secrets world-readable |
| `fs.auth_profiles.perms_writable` | Auth profiles writable |
| `fs.credentials_dir.perms_writable` | Pairing/credential state writable |
| `gateway.bind_no_auth` | Remote bind without auth |
| `gateway.loopback_no_auth` | Reverse-proxied loopback unauthenticated |
| `gateway.http.no_auth` | HTTP APIs reachable with `auth.mode="none"` |
| `gateway.tailscale_funnel` | Public internet exposure |
| `gateway.control_ui.device_auth_disabled` | Device auth disabled |
| `gateway.control_ui.allowed_origins_required` | Non-loopback Control UI without origin allowlist |
| `gateway.nodes.allow_commands_dangerous` | High-impact node commands enabled |
| `gateway.tools_invoke_http.dangerous_allow` | Dangerous tools re-enabled over HTTP |
| `hooks.token_reuse_gateway_token` | Hook token = gateway token |
| `sandbox.dangerous_bind_mount` | Dangerous bind mount |
### Warn/Info Checks
| checkId | Description |
|---------|-------------|
| `fs.state_dir.perms_group_writable` | Group-writable state |
| `fs.config.symlink` | Symlinked config (unsupported for writes) |
| `fs.synced_dir` | State/config in iCloud/Dropbox/Drive |
| `fs.sessions_store.perms_readable` | Others can read session transcripts |
| `gateway.trusted_proxies_missing` | Proxy headers without trust |
| `gateway.token_too_short` | Short auth token |
| `gateway.http.session_key_override_enabled` | HTTP callers can override sessionKey |
| `gateway.nodes.deny_commands_ineffective` | Pattern deny doesn't match shell text |
| `logging.redact_off` | Redaction disabled |
---
## Tailscale
### Modes
| Mode | Description |
|------|-------------|
| `off` (default) | No Tailscale integration |
| `serve` | Tailnet-only via tailscale serve |
| `funnel` | Public HTTPS (requires password auth) |
### Tailscale Identity Auth
```json5
{ gateway: { auth: { allowTailscale: true } } }
```
- Verifies via `tailscale whois`
- Only for WS/Control UI, NOT HTTP API endpoints
- Requires request from loopback with X-Forwarded-* headers
### Gateway Bind
- `gateway.bind: "tailnet"` — direct Tailnet IP bind
- `loopback` + `serve` — HTTPS via MagicDNS
---
## Tools Invoke HTTP API
### Endpoint
`POST /tools/invoke` — always enabled, same port as Gateway
### Request
```json
{ "tool": "web_fetch", "action": "...", "args": {...}, "sessionKey": "main" }
```
### Default Hard Deny List
`exec`, `spawn`, `shell`, `fs_write`, `fs_delete`, `fs_move`, `apply_patch`, `sessions_spawn`, `sessions_send`, `cron`, `gateway`, `nodes`, `whatsapp_login`
### Customize
- `gateway.tools.deny` — add to deny list
- `gateway.tools.allow` — remove from deny list
### Responses
- 200: `ok:true` + result
- 400: invalid body
- 401: auth failure
- 404: tool not found
- 429: rate-limited
- 500: error
---
## Troubleshooting
### Command Ladder
```bash
openclaw status
openclaw gateway status
openclaw logs --follow
openclaw doctor
openclaw channels status --probe
```
### Common Issues
**Anthropic 429 long context**: Disable `context1m` param, or use eligible credential, or configure fallback.
**Local backend fails on agent runs**: Set `compat.requiresStringContent: true` or `compat.supportsTools: false`.
**No replies**: Check pairing pending, mention gating, allowlist mismatches.
**Config restored**: `.clobbered.*` = rejected config; `.rejected.*` = failed write. Next agent turn warned.
**Gateway not starting**: Set `gateway.mode=local`; check EADDRINUSE; check auth for non-loopback.
### Auth Detail Codes
| Code | Meaning | Action |
|------|---------|--------|
| `AUTH_TOKEN_MISSING` | No token sent | Set token in client |
| `AUTH_TOKEN_MISMATCH` | Token mismatch | Check `canRetryWithDeviceToken` |
| `AUTH_DEVICE_TOKEN_MISMATCH` | Stale device token | Rotate/re-approve |
| `PAIRING_REQUIRED` | Device needs approval | `openclaw devices approve` |
---
## Trusted Proxy Auth
### When to Use
- Identity-aware proxy (Pomerium, Caddy+OAuth, nginx+oauth2-proxy, Traefik forward auth)
- Proxy handles ALL auth
### When NOT to Use
- Proxy doesn't authenticate (TLS terminator only)
- Any bypass path exists
- Same-host loopback proxy (fails closed)
### Configuration
```json5
{
gateway: {
bind: "lan",
trustedProxies: ["10.0.0.1"],
auth: {
mode: "trusted-proxy",
trustedProxy: {
userHeader: "x-forwarded-user", // required
requiredHeaders: ["x-auth-verified"], // optional
allowUsers: ["[email protected]"], // optional
},
},
},
}
```
### Error Codes
- `trusted_proxy_untrusted_source` — request not from trusted proxy
- `trusted_proxy_loopback_source` — same-host loopback rejected
- `trusted_proxy_user_missing` — no user header
- `trusted_proxy_user_not_allowed` — user not in allowlist
- `trusted_proxy_origin_not_allowed` — browser origin rejected
### Proxy Examples
- Pomerium: `x-pomerium-claim-email`
- Caddy: `x-forwarded-user`
- nginx+oauth2-proxy: `x-auth-request-email`
- Traefik: `x-forwarded-user`
---
## Cross-References
| Topic | Related Pages |
|-------|--------------|
| Gateway startup | Runbook, Troubleshooting, Doctor |
| Authentication | Authentication, Secrets, Trusted Proxy Auth |
| Configuration | Configuration, Config-Agents, Config-Channels, Config-Tools, Configuration-Reference, Configuration-Examples |
| Network/Remote | Remote, Remote-Gateway-Readme, Tailscale, Network-Model |
| Security | Security, Security/Audit-Checks, Sandbox-vs-Tool-Policy, Sandboxing |
| Protocol | Protocol, Bridge-Protocol (removed) |
| Discovery | Discovery, Bonjour, Pairing |
| HTTP APIs | OpenAI-HTTP-API, OpenResponses-HTTP-API, Tools-Invoke-HTTP-API |
| Operations | Health, Heartbeat, Diagnostics, Logging, Doctor, Gateway-Lock |
| Sandbox | Sandboxing, OpenShell, Sandbox-vs-Tool-Policy |
| Models | Local-Models, CLI-Backends, Authentication |
| Multi-Gateway | Multiple-Gateways, Gateway-Lock |
FILE:references/03-cli.md
# OpenClaw CLI — Complete Command Reference
> **Source:** All 53 CLI documentation pages from https://docs.openclaw.ai/cli
> **Fetched:** 2026-04-24
> **Entry point:** `openclaw` — the main CLI binary
---
## Table of Contents
1. [Global Flags & Output Modes](#global-flags--output-modes)
2. [ACP](#acp)
3. [Agent](#agent)
4. [Agents](#agents)
5. [Approvals / exec-policy](#approvals--exec-policy)
6. [Backup](#backup)
7. [Browser](#browser)
8. [Channels](#channels)
9. [Clawbot (legacy alias)](#clawbot-legacy-alias)
10. [Completion](#completion)
11. [Config](#config)
12. [Configure](#configure)
13. [Cron](#cron)
14. [Daemon (legacy alias)](#daemon-legacy-alias)
15. [Dashboard](#dashboard)
16. [Devices](#devices)
17. [Directory](#directory)
18. [DNS](#dns)
19. [Docs](#docs)
20. [Doctor](#doctor)
21. [Flows (redirect → tasks)](#flows-redirect--tasks)
22. [Gateway](#gateway)
23. [Health](#health)
24. [Hooks](#hooks)
25. [Infer / capability](#infer--capability)
26. [Logs](#logs)
27. [MCP](#mcp)
28. [Memory](#memory)
29. [Message](#message)
30. [Models](#models)
31. [Node](#node)
32. [Nodes](#nodes)
33. [Onboard](#onboard)
34. [Pairing](#pairing)
35. [Plugins](#plugins)
36. [Proxy](#proxy)
37. [QR](#qr)
38. [Reset](#reset)
39. [Sandbox](#sandbox)
40. [Secrets](#secrets)
41. [Security](#security)
42. [Sessions](#sessions)
43. [Setup](#setup)
44. [Skills](#skills)
45. [Status](#status)
46. [System](#system)
47. [Tasks](#tasks)
48. [TUI / chat / terminal](#tui--chat--terminal)
49. [Uninstall](#uninstall)
50. [Update](#update)
51. [Voicecall (plugin)](#voicecall-plugin)
52. [Webhooks](#webhooks)
53. [Wiki](#wiki)
54. [Quick Reference Table](#quick-reference-table)
---
## Global Flags & Output Modes
Applied to **every** `openclaw` command:
| Flag | Description |
|---|---|
| `--dev` | Isolate state under `~/.openclaw-dev`; shifts default ports |
| `--profile <name>` | Isolate state under `~/.openclaw-<name>` |
| `--container <name>` | Target a named container for execution |
| `--no-color` | Disable ANSI colors (`NO_COLOR=1` env also works) |
| `--update` | Shorthand for `openclaw update` (source installs only) |
| `-V`, `--version`, `-v` | Print version and exit |
**Output modes:**
- ANSI colors + progress indicators render only in TTY.
- OSC-8 hyperlinks for supported terminals; falls back to plain URLs.
- `--json` disables styling for clean machine-readable output.
- `--no-color` keeps human layout but disables ANSI.
- Long-running commands show a progress indicator (OSC 9;4 when supported).
**Chat slash commands** (inside TUI sessions):
| Command | Purpose |
|---|---|
| `/status` | Show session + model status |
| `/trace` | Toggle trace logging |
| `/config` | Open configure wizard inline |
| `/debug` | Verbose debug output (requires `commands.debug: true` in config to be available) |
| `/models` | Model selection |
| `/new` | New session |
| `/reset` | Reset session |
| `/dreaming on\|off\|status` | Dreaming control |
| `/auth [provider]` | Local-mode auth |
---
## ACP
**Synopsis:** `openclaw acp [options]`
**Description:** Agent Client Protocol bridge — speaks ACP over stdio for IDEs, forwards prompts to the Gateway over WebSocket. Keeps ACP sessions mapped to Gateway session keys.
Use `openclaw acp` when an IDE/client speaks ACP and you want it to drive an OpenClaw Gateway session. NOT the same as ACP harness sessions.
### Options
| Flag | Description |
|---|---|
| `--url <url>` | Gateway WebSocket URL |
| `--token <token>` | Gateway auth token |
| `--token-file <path>` | Read token from file (preferred over inline) |
| `--password <password>` | Gateway password |
| `--password-file <path>` | Read password from file |
| `--session <key>` | Default session key |
| `--session-label <label>` | Default session label to resolve |
| `--require-existing` | Fail if session key/label does not exist |
| `--reset-session` | Reset session key before first use |
| `--no-prefix-cwd` | Do not prefix prompts with working directory |
| `--provenance <off\|meta\|meta+receipt>` | ACP provenance metadata |
| `--verbose, -v` | Verbose logging to stderr |
### Subcommand: `acp client`
Debug ACP client — spawns the bridge and lets you type prompts interactively.
| Flag | Description |
|---|---|
| `--cwd <dir>` | Working directory for ACP session |
| `--server <command>` | ACP server command (default: `openclaw`) |
| `--server-args <args...>` | Extra args passed to ACP server |
| `--server-verbose` | Enable verbose logging on ACP server |
| `--verbose, -v` | Verbose client logging |
### Examples
```bash
openclaw acp
openclaw acp --url wss://gateway-host:18789 --token <token>
openclaw acp --url wss://gateway-host:18789 --token-file ~/.openclaw/gateway.token
openclaw acp --session agent:main:main
openclaw acp --session-label "support inbox"
openclaw acp --session agent:main:main --reset-session
openclaw acp client
```
### Zed Editor Config
```json
{
"agent_servers": {
"OpenClaw ACP": {
"type": "custom",
"command": "openclaw",
"args": ["acp"]
}
}
}
```
### ACP Compatibility Matrix
| Feature | Status |
|---|---|
| `initialize`, `newSession`, `prompt`, `cancel` | ✅ Implemented |
| `listSessions`, slash commands | ✅ Implemented |
| `loadSession` | ⚠️ Partial (text history only, no tool history) |
| Prompt content (text, resource, images) | ⚠️ Partial |
| Session modes | ⚠️ Partial |
| Tool streaming | ⚠️ Partial |
| Per-session MCP servers (`mcpServers`) | ❌ Unsupported |
| Client filesystem methods | ❌ Unsupported |
| Client terminal methods | ❌ Unsupported |
| Session plans / thought streaming | ❌ Unsupported |
### Notes/Gotchas
- Prefer `--token-file`/`--password-file` over inline secrets (visible in process listings).
- Env vars: `OPENCLAW_GATEWAY_TOKEN`, `OPENCLAW_GATEWAY_PASSWORD`.
- Setting `--url` does NOT reuse config/env credentials — pass explicit `--token`/`--password`.
- ACP client auto-approval is allowlist-based (scoped reads, readonly search tools only).
- `OPENCLAW_SHELL=acp` is set in ACP backend child processes; `acp-client` for the debug client.
---
## Agent
**Synopsis:** `openclaw agent [options]`
**Description:** Run an agent turn via the Gateway. Pass at least one session selector (`--to`, `--session-id`, or `--agent`).
### Options
| Flag | Description |
|---|---|
| `-m, --message <text>` | Required message body |
| `-t, --to <dest>` | Recipient to derive session key |
| `--session-id <id>` | Explicit session id |
| `--agent <id>` | Agent id; overrides routing bindings |
| `--thinking <level>` | Thinking level: `off\|minimal\|low\|medium\|high\|xhigh\|adaptive\|max` |
| `--verbose <on\|off>` | Persist verbose level for session |
| `--channel <channel>` | Delivery channel |
| `--reply-to <target>` | Delivery target override |
| `--reply-channel <channel>` | Delivery channel override |
| `--reply-account <id>` | Delivery account override |
| `--local` | Run embedded agent directly (no Gateway) |
| `--deliver` | Send reply back to selected channel/target |
| `--timeout <seconds>` | Override agent timeout (default 600) |
| `--json` | JSON output |
### Examples
```bash
openclaw agent --to +15555550123 --message "status update" --deliver
openclaw agent --agent ops --message "Summarize logs"
openclaw agent --session-id 1234 --message "Summarize inbox" --thinking medium
openclaw agent --agent ops --message "Generate report" --deliver --reply-channel slack --reply-to "#reports"
openclaw agent --agent ops --message "Run locally" --local
```
### Notes/Gotchas
- Gateway mode falls back to embedded when Gateway request fails; use `--local` to force embedded.
- `--local` still preloads plugin registry.
- `--channel`, `--reply-channel`, `--reply-account` affect reply delivery, not session routing.
---
## Agents
**Synopsis:** `openclaw agents [subcommand] [options]`
**Description:** Manage isolated agents (workspaces, auth, routing). No subcommand = `agents list`.
### Subcommands
#### `agents list`
```bash
openclaw agents list
openclaw agents list --bindings
openclaw agents list --json
```
#### `agents add [name]`
```bash
openclaw agents add work --workspace ~/.openclaw/workspace-work
openclaw agents add ops --workspace ~/.openclaw/workspace-ops --bind telegram:ops --non-interactive
```
Options: `--workspace <dir>`, `--model <id>`, `--agent-dir <dir>`, `--bind <channel[:accountId]>` (repeatable), `--non-interactive`, `--json`
> **Note:** `main` is reserved; cannot be used as a new agent id.
#### `agents bindings`
```bash
openclaw agents bindings
openclaw agents bindings --agent work --json
```
Options: `--agent <id>`, `--json`
#### `agents bind`
```bash
openclaw agents bind --agent work --bind telegram:ops --bind discord:guild-a
```
Options: `--agent <id>`, `--bind <channel[:accountId]>` (repeatable), `--json`
#### `agents unbind`
```bash
openclaw agents unbind --agent work --bind telegram:ops
openclaw agents unbind --agent work --all
```
Options: `--agent <id>`, `--bind` (repeatable), `--all`, `--json`
#### `agents delete <id>`
```bash
openclaw agents delete work
openclaw agents delete work --force
```
Options: `--force` (skip confirmation), `--json`
> Workspace/state directories are moved to Trash (not hard-deleted). `main` cannot be deleted.
#### `agents set-identity`
```bash
openclaw agents set-identity --workspace ~/.openclaw/workspace --from-identity
openclaw agents set-identity --agent main --name "OpenClaw" --emoji "🦞" --avatar avatars/openclaw.png
```
Options: `--agent <id>`, `--workspace <dir>`, `--identity-file <path>`, `--from-identity`, `--name`, `--theme`, `--emoji`, `--avatar`, `--json`
### Notes/Gotchas
- Binding without `accountId` matches channel default account only.
- `accountId: "*"` is a channel-wide fallback (less specific than explicit account binding).
- `--from-identity` reads from workspace root `IDENTITY.md` or explicit `--identity-file`.
---
## Approvals / exec-policy
**Synopsis:** `openclaw approvals [subcommand] [options]`
**Alias:** `openclaw exec-approvals`
**Description:** Manage exec approvals for local host, gateway host, or a node host.
### `openclaw exec-policy` (local convenience)
```bash
openclaw exec-policy show
openclaw exec-policy show --json
openclaw exec-policy preset yolo
openclaw exec-policy preset cautious
openclaw exec-policy set --host gateway --security full --ask off --ask-fallback full
```
> Local-only. Updates local config + approvals file. `--host node` is rejected.
### Core Commands
```bash
# Get effective policy
openclaw approvals get
openclaw approvals get --node <id|name|ip>
openclaw approvals get --gateway
# Replace approvals from file/stdin
openclaw approvals set --file ./exec-approvals.json
openclaw approvals set --stdin <<'EOF'
{ version: 1, defaults: { security: "full", ask: "off" } }
EOF
openclaw approvals set --node <id|name|ip> --file ./exec-approvals.json
openclaw approvals set --gateway --file ./exec-approvals.json
# Allowlist management
openclaw approvals allowlist add "~/Projects/**/bin/rg"
openclaw approvals allowlist add --agent main --node <id|name|ip> "/usr/bin/uptime"
openclaw approvals allowlist add --agent "*" "/usr/bin/uname"
openclaw approvals allowlist remove "~/Projects/**/bin/rg"
```
### Common Options
All `get`/`set`/`allowlist` support:
- `--node <id|name|ip>`
- `--gateway`
- `--url <url>`, `--token <token>`, `--timeout <ms>`, `--json`
`allowlist add|remove` also supports:
- `--agent <id>` (defaults to `*` — applies to all agents)
### Notes/Gotchas
- Approvals files stored at `~/.openclaw/exec-approvals.json`.
- Host approvals file is the enforceable source of truth.
- Node host must advertise `system.execApprovals.get/set`.
- Set `tools.exec.host=gateway` to use host exec even with sandbox configured.
---
## Backup
**Synopsis:** `openclaw backup create [options]` / `openclaw backup verify <archive>`
**Description:** Create restorable backup archives of OpenClaw state, config, credentials, sessions, and workspace.
### Commands
```bash
openclaw backup create
openclaw backup create --output ~/Backups
openclaw backup create --dry-run --json
openclaw backup create --verify
openclaw backup create --no-include-workspace
openclaw backup create --only-config
openclaw backup verify ./2026-03-09T00-00-00.000Z-openclaw-backup.tar.gz
```
### Options
| Flag | Description |
|---|---|
| `--output <dir>` | Output directory (default: CWD) |
| `--dry-run` | Print actions without writing archive |
| `--json` | Machine-readable output |
| `--verify` | Validate archive after writing |
| `--no-include-workspace` | Skip workspace discovery |
| `--only-config` | Back up only the active JSON config file |
### What Gets Backed Up
- State directory (`~/.openclaw`)
- Active config file
- Resolved `credentials/` directory
- Workspace directories from current config (unless `--no-include-workspace`)
### Notes/Gotchas
- Archive includes `manifest.json` with resolved source paths.
- Timestamped `.tar.gz` output in CWD by default.
- Existing archive files are NEVER overwritten.
- If config is invalid and workspace backup enabled, fails fast. Rerun with `--no-include-workspace`.
---
## Browser
**Synopsis:** `openclaw browser [--browser-profile <name>] <subcommand> [options]`
**Description:** Manage OpenClaw's browser control — lifecycle, profiles, tabs, snapshots, navigation, input, state, and debugging.
> Requires bundled browser plugin to be in `plugins.allow`.
### Common Flags
| Flag | Description |
|---|---|
| `--url <gatewayWsUrl>` | Gateway WebSocket URL |
| `--token <token>` | Gateway token |
| `--timeout <ms>` | Request timeout |
| `--expect-final` | Wait for final response |
| `--browser-profile <name>` | Browser profile (default from config) |
| `--json` | Machine-readable output |
### Quick Start
```bash
openclaw browser profiles
openclaw browser --browser-profile openclaw start
openclaw browser --browser-profile openclaw open https://example.com
openclaw browser --browser-profile openclaw snapshot
```
### Lifecycle
```bash
openclaw browser status
openclaw browser doctor # CDP readiness check
openclaw browser start
openclaw browser stop
openclaw browser --browser-profile openclaw reset-profile
```
### Profiles
```bash
openclaw browser profiles
openclaw browser create-profile --name work --color "#FF5A36"
openclaw browser create-profile --name chrome-live --driver existing-session
openclaw browser create-profile --name remote --cdp-url https://browser-host.example.com
openclaw browser delete-profile --name work
openclaw browser --browser-profile work tabs
```
**Profile types:**
- `openclaw`: dedicated OpenClaw-managed Chrome (isolated user data dir)
- `user`: existing signed-in Chrome via Chrome DevTools MCP
- custom CDP profiles: point at local/remote CDP endpoint
### Tabs & Navigation
```bash
openclaw browser tabs
openclaw browser tab new
openclaw browser tab select 2
openclaw browser tab close 2
openclaw browser open https://docs.openclaw.ai
openclaw browser focus <targetId>
openclaw browser close <targetId>
openclaw browser navigate https://example.com
```
### Snapshot / Screenshot
```bash
openclaw browser snapshot
openclaw browser snapshot --urls # append discovered link destinations
openclaw browser screenshot
openclaw browser screenshot --full-page
openclaw browser screenshot --ref e12
openclaw browser screenshot --labels # overlay current snapshot refs
```
> `--full-page` cannot combine with `--ref`/`--element`. `existing-session`/`user` profiles support `--ref` screenshots but not CSS `--element`.
### UI Automation (ref-based)
```bash
openclaw browser click <ref>
openclaw browser type <ref> "hello"
openclaw browser press Enter
openclaw browser hover <ref>
openclaw browser scrollintoview <ref>
openclaw browser drag <startRef> <endRef>
openclaw browser select <ref> OptionA OptionB
openclaw browser fill --fields '[{"ref":"1","value":"Ada"}]'
openclaw browser wait --text "Done"
openclaw browser evaluate --fn '(el) => el.textContent' --ref <ref>
openclaw browser upload /tmp/openclaw/uploads/file.pdf --ref <ref>
openclaw browser waitfordownload
openclaw browser download <ref> report.pdf
openclaw browser dialog --accept
```
### State and Storage
```bash
openclaw browser resize 1280 720
openclaw browser set viewport 1280 720
openclaw browser set offline on
openclaw browser set media dark
openclaw browser set timezone Europe/London
openclaw browser set locale en-GB
openclaw browser set geo 51.5074 -0.1278 --accuracy 25
openclaw browser set device "iPhone 14"
openclaw browser set headers '{"x-test":"1"}'
openclaw browser set credentials myuser mypass
openclaw browser cookies
openclaw browser cookies set session abc123 --url https://example.com
openclaw browser cookies clear
openclaw browser storage local get
openclaw browser storage local set token abc123
openclaw browser storage session clear
```
### Debugging
```bash
openclaw browser console --level error
openclaw browser pdf
openclaw browser responsebody "**/api"
openclaw browser highlight <ref>
openclaw browser errors --clear
openclaw browser requests --filter api
openclaw browser trace start
openclaw browser trace stop --out trace.zip
```
### Notes/Gotchas
- If `openclaw browser` is unknown, check `plugins.allow` in `openclaw.json`.
- `--full-page` cannot be combined with `--ref` or `--element`.
- For remote browser control, run a node host on the machine with Chrome.
- `existing-session` profile: no `slowly=true`, no `delayMs`, no `networkidle`.
- `attachOnly`/remote CDP: `stop` closes control session even if OpenClaw didn't launch the browser.
---
## Channels
**Synopsis:** `openclaw channels <subcommand> [options]`
**Description:** Manage chat channel accounts and their runtime status on the Gateway.
### Commands
```bash
openclaw channels list
openclaw channels status
openclaw channels status --probe
openclaw channels status --probe --timeout 5000 --json
openclaw channels capabilities
openclaw channels capabilities --channel discord --target channel:123
openclaw channels resolve --channel slack "#general" "@jane"
openclaw channels logs --channel all
openclaw channels logs --channel telegram --lines 50 --json
```
### Add / Remove Accounts
```bash
openclaw channels add --channel telegram --token <bot-token>
openclaw channels add --channel nostr --private-key "$NOSTR_PRIVATE_KEY"
openclaw channels remove --channel telegram --delete
```
**Per-channel non-interactive add flags:**
- Bot-token channels: `--token`, `--bot-token`, `--app-token`, `--token-file`
- Signal/iMessage: `--signal-number`, `--cli-path`, `--http-url`, `--http-host`, `--http-port`, `--db-path`, `--service`, `--region`
- Google Chat: `--webhook-path`, `--webhook-url`, `--audience-type`, `--audience`
- Matrix: `--homeserver`, `--user-id`, `--access-token`, `--password`, `--device-name`, `--initial-sync-limit`
- Nostr: `--private-key`, `--relay-urls`
- Tlon: `--ship`, `--url`, `--code`, `--group-channels`, `--dm-allowlist`, `--auto-discover-channels`
- `--use-env`: default-account env-backed auth
### Login / Logout (Interactive)
```bash
openclaw channels login --channel whatsapp
openclaw channels logout --channel whatsapp
```
### Resolve Names to IDs
```bash
openclaw channels resolve --channel slack "#general" "@jane"
openclaw channels resolve --channel discord "My Server/#support" "@someone"
openclaw channels resolve --channel matrix "Project Room"
# Options: --kind user|group|auto
```
### Notes/Gotchas
- `channels status --probe` is the live path; without it falls back to config-only.
- Run `openclaw doctor --fix` if config is in mixed state.
---
## Clawbot (legacy alias)
```bash
openclaw clawbot qr # same as: openclaw qr
```
Only supported alias. Kept for backwards compatibility. Use `openclaw qr` directly.
---
## Completion
**Synopsis:** `openclaw completion [options]`
**Description:** Generate shell completion scripts.
### Options
| Flag | Description |
|---|---|
| `-s, --shell <shell>` | Target shell: `zsh\|bash\|powershell\|fish` (default: `zsh`) |
| `-i, --install` | Install completion into shell profile |
| `--write-state` | Write script to `$OPENCLAW_STATE_DIR/completions` |
| `-y, --yes` | Skip confirmation prompts |
### Examples
```bash
openclaw completion
openclaw completion --shell zsh
openclaw completion --install
openclaw completion --shell fish --install
openclaw completion --write-state
openclaw completion --shell bash --write-state
```
---
## Config
**Synopsis:** `openclaw config [subcommand] [path] [value] [options]`
**Description:** Non-interactive config edits for `openclaw.json`. No subcommand = opens configure wizard.
### Subcommands
```bash
openclaw config file # Print active config file path
openclaw config schema # Print JSON schema to stdout
openclaw config schema > openclaw.schema.json
openclaw config get <path> # Get value at path
openclaw config get browser.executablePath
openclaw config get agents.list[0].id
openclaw config set <path> <value>
openclaw config unset <path>
openclaw config validate
openclaw config validate --json
```
### `config get` Options
- `--json`: print raw value as JSON
### `config set` — Four Assignment Modes
**1. Value mode (default):**
```bash
openclaw config set browser.executablePath "/usr/bin/google-chrome"
openclaw config set agents.defaults.heartbeat.every "2h"
```
**2. SecretRef builder mode:**
```bash
openclaw config set channels.discord.token \
--ref-provider default \
--ref-source env \
--ref-id DISCORD_BOT_TOKEN
```
**3. Provider builder mode** (`secrets.providers.<alias>` only):
```bash
openclaw config set secrets.providers.vault \
--provider-source exec \
--provider-command /usr/local/bin/my-vault \
--provider-arg read \
--provider-arg openai/api-key \
--provider-timeout-ms 5000
```
**4. Batch mode:**
```bash
openclaw config set --batch-json '[
{ "path": "secrets.providers.default", "provider": { "source": "env" } },
{ "path": "channels.discord.token", "ref": { "source": "env", "provider": "default", "id": "DISCORD_BOT_TOKEN" } }
]'
openclaw config set --batch-file ./config-set.batch.json --dry-run
```
### `config set` Additional Options
| Flag | Description |
|---|---|
| `--strict-json` | Require JSON5 parsing |
| `--merge` | Merge into maps/lists instead of replacing |
| `--replace` | Force complete replacement of protected maps |
| `--dry-run` | Validate without writing |
| `--allow-exec` | Allow exec SecretRef checks in dry-run |
### Provider Builder Flags
Common:
- `--provider-source <env|file|exec>`
- `--provider-timeout-ms <ms>`
Env provider: `--provider-allowlist <ENV_VAR>` (repeatable)
File provider: `--provider-path <path>`, `--provider-mode <singleValue|json>`, `--provider-max-bytes`, `--provider-allow-insecure-path`
Exec provider: `--provider-command <path>`, `--provider-arg <arg>` (repeatable), `--provider-no-output-timeout-ms`, `--provider-max-output-bytes`, `--provider-json-only`, `--provider-env <KEY=VALUE>` (repeatable), `--provider-pass-env`, `--provider-trusted-dir`, `--provider-allow-insecure-path`, `--provider-allow-symlink-command`
### Notes/Gotchas
- Values parsed as JSON5 when possible; otherwise treated as strings.
- Protected maps (e.g. `agents.defaults.models`, `plugins.entries`) refuse replacements that remove existing entries unless `--replace`.
- Config path must be a regular file — symlinks not supported for writes.
- If write is rejected, inspect `openclaw.json.rejected.*` for the rejected payload.
---
## Configure
**Synopsis:** `openclaw configure [options]`
**Description:** Interactive prompt-driven setup wizard. Same as `openclaw config` with no subcommand.
### Options
- `--section <section>` (repeatable): filter to specific section
Available sections: `workspace`, `model`, `web`, `gateway`, `daemon`, `channels`, `plugins`, `skills`, `health`
### Examples
```bash
openclaw configure
openclaw configure --section web
openclaw configure --section model --section channels
openclaw configure --section gateway --section daemon
```
### Notes/Gotchas
- **Model section**: multi-select for `agents.defaults.models` allowlist; merges, not replaces.
- Provider setup choices merge selected models; use `openclaw models set <model>` to change default.
- Re-running preserves existing `agents.defaults.model.primary`.
---
## Cron
**Synopsis:** `openclaw cron <subcommand> [options]`
**Description:** Manage cron jobs for the Gateway scheduler.
### Admin Commands
```bash
openclaw cron status
openclaw cron list
openclaw cron list --json
openclaw cron show <job-id>
openclaw cron run <job-id>
openclaw cron run <job-id> --due # only if due
openclaw cron runs --id <job-id> --limit 50
openclaw cron enable <job-id>
openclaw cron disable <job-id>
openclaw cron rm <job-id>
```
### Add a Job
```bash
# Recurring
openclaw cron add \
--name "Morning brief" \
--cron "0 7 * * *" \
--session isolated \
--message "Summarize overnight updates." \
--announce --channel telegram --to "123456789"
# One-shot (runs once at specified time)
openclaw cron add \
--name "Reminder" \
--at "2026-04-25T09:00:00" \
--tz "America/New_York" \
--message "Check email" \
--announce
# Lightweight isolated
openclaw cron add \
--name "Brief" \
--cron "0 7 * * *" \
--session isolated \
--message "Summarize overnight updates." \
--light-context \
--no-deliver
```
### Edit a Job
```bash
openclaw cron edit <job-id> --announce --channel telegram --to "123456789"
openclaw cron edit <job-id> --no-deliver
openclaw cron edit <job-id> --light-context
openclaw cron edit <job-id> --announce --channel slack --to "channel:C1234567890"
openclaw cron edit <job-id> --agent ops
openclaw cron edit <job-id> --clear-agent
openclaw cron edit <job-id> --session current
openclaw cron edit <job-id> --session "session:daily-brief"
openclaw cron edit <job-id> --best-effort-deliver
openclaw cron edit <job-id> --no-best-effort-deliver
openclaw cron add --model openai/gpt-5.4 ...
```
### Key Options
| Flag | Description |
|---|---|
| `--name <name>` | Job name |
| `--cron <expr>` | Cron expression (recurring) |
| `--at <datetime>` | ISO datetime for one-shot job (UTC unless `--tz`) |
| `--tz <timezone>` | Timezone for `--at` |
| `--session <key>` | `main\|isolated\|current\|session:<id>` |
| `--message <text>` | Message to inject |
| `--announce` | Deliver output to channel |
| `--no-deliver` | Keep output internal |
| `--light-context` | Lightweight bootstrap context |
| `--agent <id>` | Target agent |
| `--model <model>` | Model override |
| `--best-effort-deliver` | Best-effort delivery (don't fail on delivery error) |
| `--keep-after-run` | Keep one-shot job after success |
### Notes/Gotchas
- `--session` values: `main`, `isolated`, `current`, `session:<id>`.
- `--at` without `--tz` is treated as UTC.
- One-shot jobs delete after success by default (use `--keep-after-run` to keep).
- Isolated `cron add` defaults to `--announce` delivery. Use `--no-deliver` to keep output internal. `--deliver` is deprecated alias.
- Retry backoff: 30s → 1m → 5m → 15m → 60m (resets after next success).
- `cron run` returns immediately with `{ ok: true, enqueued: true, runId }`. Use `--due` to keep older "only run if due" behavior.
- `cron runs` entries include delivery diagnostics: intended target, resolved target, message-tool sends, fallback use, delivered state.
- If isolated run returns only `NO_REPLY`/`no_reply`, delivery is suppressed (direct outbound + fallback queued summary).
- **Stale ack guard:** If first result is just interim status ("on it", etc.) and no descendant subagent handles the answer, cron re-prompts once for real result.
- **Model switch retry:** If an isolated run throws `LiveSessionModelSwitchError`, cron persists the switched provider/model (and auth profile) then retries. Bounded: initial + 2 switch retries, then abort.
- **Failure notifications:** `delivery.failureDestination` → global `cron.failureDestination` → primary announce target fallback.
- Retention: `cron.sessionRetention` (default `24h`), `cron.runLog.maxBytes`, `cron.runLog.keepLines`.
- **Doctor migration:** `openclaw doctor --fix` normalizes legacy cron fields (`jobId`, `schedule.cron`, top-level delivery, legacy `threadId`, `notify: true` webhook fallback).
---
## Daemon (legacy alias)
**Synopsis:** `openclaw daemon <subcommand>`
**Description:** Legacy alias for gateway service management. Maps to `openclaw gateway <service-cmd>`.
```bash
openclaw daemon status
openclaw daemon install
openclaw daemon start
openclaw daemon stop
openclaw daemon restart
openclaw daemon uninstall
```
**Prefer:** `openclaw gateway` for current usage.
**Common options:** `--url`, `--token`, `--password`, `--timeout`, `--no-probe`, `--require-rpc`, `--deep`, `--json`, `--port`, `--runtime <node|bun>`, `--force`
---
## Dashboard
**Synopsis:** `openclaw dashboard [options]`
**Description:** Open the Control UI using current auth.
```bash
openclaw dashboard
openclaw dashboard --no-open
```
**Notes:**
- For SecretRef-managed tokens, prints a non-tokenized URL (avoids exposing secrets in terminal or browser-launch args).
---
## Devices
**Synopsis:** `openclaw devices <subcommand> [options]`
**Description:** Manage device pairing requests and device-scoped tokens.
### Commands
```bash
# List all devices
openclaw devices list
openclaw devices list --json
# Remove a device
openclaw devices remove <deviceId>
openclaw devices remove <deviceId> --json
# Clear pending requests
openclaw devices clear --yes
openclaw devices clear --yes --pending
openclaw devices clear --yes --pending --json
# Approve / reject pairing
openclaw devices approve # preview (requires rerun with explicit ID)
openclaw devices approve <requestId>
openclaw devices approve --latest
openclaw devices reject <requestId>
# Token rotation
openclaw devices rotate --device <deviceId> --role operator --scope operator.read --scope operator.write
# Token revocation
openclaw devices revoke --device <deviceId> --role node
```
### Common Options
- `--url <url>`, `--token <token>`, `--password <password>`, `--timeout <ms>`, `--json`
### Token Drift Recovery
```bash
openclaw config get gateway.auth.token
openclaw devices list
openclaw devices rotate --device <deviceId> --role operator
# If not enough:
openclaw devices remove <deviceId>
openclaw devices list
openclaw devices approve <requestId>
```
### Notes/Gotchas
- `devices approve` requires explicit request ID before minting tokens.
- Non-admin callers can remove/revoke only their own device.
- `devices clear` gated by `--yes`.
- Token rotation returns new token (treat as secret).
- Commands require `operator.pairing` (or `operator.admin`) scope.
---
## Directory
**Synopsis:** `openclaw directory [self|peers|groups] [options]`
**Description:** Directory lookups for channels that support contacts/groups.
### Common Flags
- `--channel <name>`: channel id/alias (required with multiple channels)
- `--account <id>`: account id
- `--json`
### Commands
```bash
openclaw directory self --channel zalouser
openclaw directory peers list --channel zalouser
openclaw directory peers list --channel zalouser --query "name" --limit 50
openclaw directory groups list --channel zalouser
openclaw directory groups list --channel zalouser --query "work"
openclaw directory groups members --channel zalouser --group-id <id>
```
### ID Formats by Channel
| Channel | Format |
|---|---|
| WhatsApp | `+15551234567` (DM), `[email protected]` (group) |
| Telegram | `@username` or numeric chat id |
| Slack | `user:U…`, `channel:C…` |
| Discord | `user:<id>`, `channel:<id>` |
| Matrix | `user:@user:server`, `room:!roomId:server`, `#alias:server` |
| Microsoft Teams | `user:<id>`, `conversation:<id>` |
---
## DNS
**Synopsis:** `openclaw dns setup [options]`
**Description:** DNS helpers for wide-area discovery (Tailscale + CoreDNS). Currently focused on macOS + Homebrew.
### Options
| Flag | Description |
|---|---|
| `--domain <domain>` | Wide-area discovery domain (e.g. `openclaw.internal`) |
| `--apply` | Install/update CoreDNS config and restart service (sudo; macOS only) |
### Examples
```bash
openclaw dns setup
openclaw dns setup --domain openclaw.internal
openclaw dns setup --apply
```
### Notes/Gotchas
- Without `--apply`, planning helper only (prints recommended setup).
- `--apply` bootstraps zone file, ensures CoreDNS import stanza, restarts `coredns` brew service.
---
## Docs
**Synopsis:** `openclaw docs [query...]`
**Description:** Search the live docs index.
```bash
openclaw docs
openclaw docs browser existing-session
openclaw docs sandbox allowHostControl
openclaw docs gateway token secretref
```
- No query opens live docs search entrypoint.
- Multi-word queries passed as one search request.
---
## Doctor
**Synopsis:** `openclaw doctor [options]`
**Description:** Health checks + quick fixes for gateway and channels.
### Options
| Flag | Description |
|---|---|
| `--no-workspace-suggestions` | Disable workspace memory/search suggestions |
| `--yes` | Accept defaults without prompting |
| `--repair` / `--fix` | Apply recommended repairs without prompting |
| `--force` | Apply aggressive repairs, overwrite custom service config |
| `--non-interactive` | Run without prompts; safe migrations only |
| `--generate-gateway-token` | Generate and configure a gateway token |
| `--deep` | Scan system services for extra gateway installs |
### Examples
```bash
openclaw doctor
openclaw doctor --repair
openclaw doctor --deep
openclaw doctor --repair --non-interactive
openclaw doctor --generate-gateway-token
```
### What Doctor Does
- Health checks for gateway and channels.
- `--fix` writes backup to `~/.openclaw/openclaw.json.bak`, drops unknown config keys.
- Detects orphan transcript files; can archive as `.deleted.<timestamp>`.
- Scans and normalizes legacy cron job shapes.
- Repairs missing bundled plugin runtime dependencies.
- Auto-migrates legacy flat Talk config.
- Includes memory-search readiness check.
- Warns if sandbox mode enabled but Docker unavailable.
### macOS: Stale Env Overrides
```bash
# Check for stale overrides (can cause persistent "unauthorized" errors)
launchctl getenv OPENCLAW_GATEWAY_TOKEN
launchctl getenv OPENCLAW_GATEWAY_PASSWORD
# Remove them:
launchctl unsetenv OPENCLAW_GATEWAY_TOKEN
launchctl unsetenv OPENCLAW_GATEWAY_PASSWORD
```
---
## Flows (redirect → tasks)
Flow commands are subcommands of `openclaw tasks`, not standalone:
```bash
openclaw tasks flow list [--json]
openclaw tasks flow show <lookup>
openclaw tasks flow cancel <lookup>
```
See [Tasks](#tasks).
---
## Gateway
**Synopsis:** `openclaw gateway [subcommand] [options]`
**Description:** OpenClaw's WebSocket server (channels, nodes, sessions, hooks).
### Run the Gateway
```bash
openclaw gateway # requires gateway.mode=local in config
openclaw gateway run # foreground alias
```
### Run Options
| Flag | Description |
|---|---|
| `--port <port>` | WebSocket port (default: `18789`) |
| `--bind <loopback\|lan\|tailnet\|auto\|custom>` | Listener bind mode |
| `--auth <token\|password>` | Auth mode override |
| `--token <token>` | Token override |
| `--password <password>` | Password (⚠️ visible in process listings) |
| `--password-file <path>` | Read password from file |
| `--tailscale <off\|serve\|funnel>` | Expose via Tailscale |
| `--tailscale-reset-on-exit` | Reset Tailscale config on shutdown |
| `--allow-unconfigured` | Allow start without `gateway.mode=local` |
| `--dev` | Create dev config + workspace if missing |
| `--reset` | Reset dev config + credentials + sessions + workspace (requires `--dev`) |
| `--force` | Kill existing listener on port before starting |
| `--verbose` | Verbose logs |
| `--cli-backend-logs` | Only show CLI backend logs |
| `--ws-log <auto\|full\|compact>` | WebSocket log style |
| `--compact` | Alias for `--ws-log compact` |
| `--raw-stream` | Log raw model stream events to jsonl |
| `--raw-stream-path <path>` | Raw stream jsonl path |
**Startup profiling:** `OPENCLAW_GATEWAY_STARTUP_TRACE=1`
### Query Commands (Gateway RPC)
Shared options: `--url`, `--token`, `--password`, `--timeout <ms>`, `--expect-final`
> When you set `--url`, the CLI does NOT fall back to config/env credentials. Pass `--token`/`--password` explicitly.
#### `gateway health`
```bash
openclaw gateway health --url ws://127.0.0.1:18789
```
- `/healthz` = liveness; `/readyz` = strict readiness (waits for channels/hooks)
#### `gateway usage-cost`
```bash
openclaw gateway usage-cost
openclaw gateway usage-cost --days 7 # default: 30
openclaw gateway usage-cost --json
```
#### `gateway stability`
```bash
openclaw gateway stability
openclaw gateway stability --type payload.large
openclaw gateway stability --bundle latest
openclaw gateway stability --bundle latest --export
openclaw gateway stability --json
```
Options: `--limit <n>` (max 1000, default 25), `--type <type>`, `--since-seq <seq>`, `--bundle [path]`, `--export`, `--output <path>`
#### `gateway diagnostics export`
```bash
openclaw gateway diagnostics export
openclaw gateway diagnostics export --output openclaw-diagnostics.zip
openclaw gateway diagnostics export --json
```
Options: `--output <path>`, `--log-lines <count>` (default 5000), `--log-bytes <bytes>` (default 1000000), `--url`, `--token`, `--password`, `--timeout <ms>`, `--no-stability-bundle`, `--json`
#### `gateway status`
```bash
openclaw gateway status
openclaw gateway status --json
openclaw gateway status --require-rpc
```
Options: `--url`, `--token`, `--password`, `--timeout <ms>`, `--no-probe`, `--deep`, `--require-rpc`
> `--require-rpc`: upgrade probe to read probe; exit non-zero when it fails. Use in scripts.
#### `gateway probe`
```bash
openclaw gateway probe
openclaw gateway probe --json
openclaw gateway probe --ssh user@gateway-host
openclaw gateway probe --ssh user@gateway-host:22 --ssh-identity ~/.ssh/id_rsa
openclaw gateway probe --ssh-auto
```
**Probe interpretation:**
- `Reachable: yes` — at least one target accepted WebSocket connect
- `Capability: read-only|write-capable|admin-capable|pairing-pending|connect-only`
- `Read probe: ok` — read-scope RPC calls succeeded
**Warning codes:** `ssh_tunnel_failed`, `multiple_gateways`, `auth_secretref_unresolved`, `probe_scope_limited`
#### `gateway call <method>`
```bash
openclaw gateway call status
openclaw gateway call logs.tail --params '{"sinceMs": 60000}'
```
Options: `--params <json>`, `--url`, `--token`, `--password`, `--timeout <ms>`, `--expect-final`, `--json`
### Service Management
```bash
openclaw gateway install
openclaw gateway start
openclaw gateway stop
openclaw gateway restart
openclaw gateway uninstall
```
Options for `install`: `--port`, `--runtime <node|bun>`, `--token`, `--force`, `--json`
### Discovery (Bonjour / mDNS)
```bash
openclaw gateway discover
openclaw gateway discover --timeout 4000
openclaw gateway discover --json | jq '.beacons[].wsUrl'
```
Options: `--timeout <ms>` (default 2000), `--json`
**Wide-Area Bonjour TXT records:** `role`, `transport`, `gatewayPort`, `sshPort`, `tailnetDns`, `gatewayTls`, `gatewayTlsSha256`, `cliPath`
### Notes/Gotchas
- Gateway refuses to start unless `gateway.mode=local` is set. Use `--allow-unconfigured` for ad-hoc.
- Binding beyond loopback without auth is blocked.
- `SIGUSR1` triggers in-process restart (when `commands.restart` enabled).
- For password auth, prefer `OPENCLAW_GATEWAY_PASSWORD`, `--password-file`, or SecretRef.
- If both token and password are configured and `gateway.auth.mode` unset, install is blocked.
---
## Health
**Synopsis:** `openclaw health [options]`
**Description:** Fetch health from the running Gateway.
### Options
| Flag | Description |
|---|---|
| `--json` | Machine-readable output |
| `--timeout <ms>` | Connection timeout (default `10000`) |
| `--verbose` | Verbose logging (forces live probe) |
| `--debug` | Alias for `--verbose` |
### Examples
```bash
openclaw health
openclaw health --json
openclaw health --timeout 2500
openclaw health --verbose
openclaw health --debug
```
### Notes/Gotchas
- Default: asks Gateway for cached health snapshot.
- `--verbose` forces live probe; expands output across all configured accounts and agents.
- Output includes per-agent session stores when multiple agents configured.
---
## Hooks
**Synopsis:** `openclaw hooks [subcommand] [options]`
**Description:** Manage agent hooks (event-driven automations for `/new`, `/reset`, gateway startup). No subcommand = `hooks list`.
### Commands
```bash
openclaw hooks list
openclaw hooks list --eligible
openclaw hooks list --verbose
openclaw hooks list --json
openclaw hooks info session-memory
openclaw hooks info session-memory --json
openclaw hooks check
openclaw hooks check --json
openclaw hooks enable session-memory
openclaw hooks enable boot-md
openclaw hooks disable command-logger
```
### Install / Update Hook Packs
```bash
openclaw plugins install <package> # ClawHub first, then npm
openclaw plugins install <package> --pin # pin version
openclaw plugins install <path> # local path
openclaw plugins install -l ./my-hook-pack # link without copying
openclaw plugins update <id>
openclaw plugins update --all
openclaw plugins update --dry-run
```
> `openclaw hooks install`/`hooks update` still work but print deprecation warnings and forward to `plugins install`/`update`.
### Bundled Hooks
| Hook | Events | Description |
|---|---|---|
| `session-memory` | `command:new`, `command:reset` | Saves session context to memory on `/new`/`/reset` |
| `bootstrap-extra-files` | `agent:bootstrap` | Injects additional bootstrap files |
| `command-logger` | all commands | Logs all command events to `~/.openclaw/logs/commands.log` |
| `boot-md` | `gateway:startup` | Runs `BOOT.md` when gateway starts |
```bash
openclaw hooks enable session-memory
openclaw hooks enable boot-md
openclaw hooks enable bootstrap-extra-files
openclaw hooks enable command-logger
# View command-logger output:
tail -n 20 ~/.openclaw/logs/commands.log
cat ~/.openclaw/logs/commands.log | jq .
grep '"action":"new"' ~/.openclaw/logs/commands.log | jq .
```
### Notes/Gotchas
- Plugin-managed hooks cannot be enabled/disabled here; enable/disable the owning plugin.
- Workspace hooks are disabled by default until enabled.
- Npm specs are registry-only (exact version or dist-tag only). No semver ranges.
- After enabling/disabling: **restart the gateway**.
---
## Infer / capability
**Synopsis:** `openclaw infer <subcommand> [options]`
**Alias:** `openclaw capability`
**Description:** Headless surface for provider-backed inference — model, image, audio, TTS, video, web, embedding.
### Command Tree
```
openclaw infer
list
inspect
model
run
list / inspect / providers
auth login / logout / status
image
generate / edit / describe / describe-many / providers
audio
transcribe / providers
tts
convert / voices / providers / status / enable / disable / set-provider
video
generate / describe / providers
web
search / fetch / providers
embedding
create / providers
```
### Common Task → Command
| Task | Command |
|---|---|
| Text/model prompt | `openclaw infer model run --prompt "..." --json` |
| Generate image | `openclaw infer image generate --prompt "..." --json` |
| Describe image | `openclaw infer image describe --file ./image.png --json` |
| Transcribe audio | `openclaw infer audio transcribe --file ./memo.m4a --json` |
| Synthesize speech | `openclaw infer tts convert --text "..." --output ./speech.mp3 --json` |
| Generate video | `openclaw infer video generate --prompt "..." --json` |
| Describe video | `openclaw infer video describe --file ./clip.mp4 --json` |
| Web search | `openclaw infer web search --query "..." --json` |
| Fetch page | `openclaw infer web fetch --url https://example.com --json` |
| Create embeddings | `openclaw infer embedding create --text "..." --json` |
### Model Examples
```bash
openclaw infer model run --prompt "Reply with exactly: smoke-ok" --json
openclaw infer model run --prompt "Summarize this changelog entry" --provider openai --json
openclaw infer model providers --json
openclaw infer model inspect --name gpt-5.5 --json
```
### Image Examples
```bash
openclaw infer image generate --prompt "friendly lobster illustration" --json
openclaw infer image describe --file ./photo.jpg --json
openclaw infer image describe --file ./ui-screenshot.png --model openai/gpt-4.1-mini --json
openclaw infer image describe --file ./photo.jpg --model ollama/qwen2.5vl:7b --json
openclaw infer image providers --json
# Smoke test:
openclaw infer image generate \
--model google/gemini-3.1-flash-image-preview \
--prompt "Minimal flat test image: one blue square on white background, no text." \
--output ./smoke.png --json
```
### Audio Examples
```bash
openclaw infer audio transcribe --file ./memo.m4a --json
openclaw infer audio transcribe --file ./team-sync.m4a --language en --prompt "Focus on names and action items" --json
openclaw infer audio transcribe --file ./memo.m4a --model openai/whisper-1 --json
```
### TTS Examples
```bash
openclaw infer tts convert --text "hello from openclaw" --output ./hello.mp3 --json
openclaw infer tts providers --json
openclaw infer tts status --json
openclaw infer tts voices --json
```
### Video Examples
```bash
openclaw infer video generate --prompt "cinematic sunset over the ocean" --json
openclaw infer video describe --file ./clip.mp4 --json
openclaw infer video describe --file ./clip.mp4 --model openai/gpt-4.1-mini --json
```
### Web Examples
```bash
openclaw infer web search --query "OpenClaw docs" --json
openclaw infer web fetch --url https://docs.openclaw.ai/cli/infer --json
openclaw infer web providers --json
```
### Embedding Examples
```bash
openclaw infer embedding create --text "friendly lobster" --json
openclaw infer embedding create --text "customer support ticket" --model openai/text-embedding-3-large --json
openclaw infer embedding providers --json
```
### JSON Output Envelope
```json
{
"ok": true,
"capability": "image.generate",
"transport": "local",
"provider": "openai",
"model": "gpt-image-2",
"attempts": [],
"outputs": []
}
```
Stable fields: `ok`, `capability`, `transport`, `provider`, `model`, `attempts`, `outputs`, `error`
### Notes/Gotchas
- For `image describe`, `audio transcribe`, `video describe`: `--model` MUST be `<provider/model>`.
- Stateless execution defaults to local (no Gateway required for most commands).
- `tts status` defaults to gateway (reflects gateway-managed TTS state).
- Common mistake: `openclaw infer media image generate` → WRONG. Use `openclaw infer image generate`.
- For local Ollama vision: `OLLAMA_API_KEY=ollama-local` (any placeholder value).
---
## Logs
**Synopsis:** `openclaw logs [options]`
**Description:** Tail Gateway file logs over RPC (works in remote mode).
### Options
| Flag | Description |
|---|---|
| `--limit <n>` | Max log lines (default `200`) |
| `--max-bytes <n>` | Max bytes from log file (default `250000`) |
| `--follow` | Follow the log stream |
| `--interval <ms>` | Polling interval while following (default `1000`) |
| `--json` | Line-delimited JSON events |
| `--plain` | Plain text, no styling |
| `--no-color` | Disable ANSI colors |
| `--local-time` | Timestamps in local timezone |
| `--url <url>` | Gateway WebSocket URL |
| `--token <token>` | Gateway token |
| `--timeout <ms>` | Timeout (default `30000`) |
| `--expect-final` | Wait for final response |
### Examples
```bash
openclaw logs
openclaw logs --follow
openclaw logs --follow --interval 2000
openclaw logs --limit 500 --max-bytes 500000
openclaw logs --json
openclaw logs --plain
openclaw logs --no-color
openclaw logs --limit 500
openclaw logs --local-time
openclaw logs --follow --local-time
openclaw logs --url ws://127.0.0.1:18789 --token "$OPENCLAW_GATEWAY_TOKEN"
```
### Notes/Gotchas
- When passing `--url`, also pass explicit `--token` (no auto-apply of config credentials).
- If local loopback Gateway asks for pairing, `logs` falls back to configured local log file. Explicit `--url` does NOT use this fallback.
---
## MCP
**Synopsis:** `openclaw mcp <subcommand>`
**Description:** Two jobs: (1) run OpenClaw as MCP server with `mcp serve`; (2) manage OpenClaw-owned outbound MCP server definitions.
### `mcp serve` — OpenClaw as MCP Server
```bash
openclaw mcp serve
openclaw mcp serve --url wss://gateway-host:18789 --token-file ~/.openclaw/gateway.token
openclaw mcp serve --url wss://gateway-host:18789 --password-file ~/.openclaw/gateway.password
openclaw mcp serve --verbose
openclaw mcp serve --claude-channel-mode off
```
**Options:**
| Flag | Description |
|---|---|
| `--url <url>` | Gateway WebSocket URL |
| `--token <token>` | Gateway token |
| `--token-file <path>` | Read token from file |
| `--password <password>` | Gateway password |
| `--password-file <path>` | Read password from file |
| `--claude-channel-mode <auto\|on\|off>` | Claude notification mode (default `auto`) |
| `-v, --verbose` | Verbose logs on stderr |
### Bridge Tools Exposed
| Tool | Description |
|---|---|
| `conversations_list` | List recent session-backed conversations |
| `conversation_get` | Get one conversation by `session_key` |
| `messages_read` | Read recent transcript messages |
| `attachments_fetch` | Extract non-text message content blocks |
| `events_poll` | Read queued live events since cursor |
| `events_wait` | Long-polls until next matching event or timeout |
| `messages_send` | Send text through the same route |
| `permissions_list_open` | List pending exec/plugin approval requests |
| `permissions_respond` | Resolve approval: `allow-once\|allow-always\|deny` |
### Claude Channel Notifications
When `--claude-channel-mode on|auto`:
- Inbound `user` transcript messages → `notifications/claude/channel`
- Claude permission requests → `notifications/claude/channel/permission`
### MCP Client Config
```json
{
"mcpServers": {
"openclaw": {
"command": "openclaw",
"args": [
"mcp", "serve",
"--url", "wss://gateway-host:18789",
"--token-file", "/path/to/gateway.token"
]
}
}
}
```
### Saved MCP Server Definitions (Registry)
```bash
openclaw mcp list
openclaw mcp show context7 --json
openclaw mcp set context7 '{"command":"uvx","args":["context7-mcp"]}'
openclaw mcp set docs '{"url":"https://mcp.example.com"}'
openclaw mcp unset context7
```
**Transport shapes:**
```json
// Stdio
{ "command": "uvx", "args": ["context7-mcp"], "env": {}, "cwd": "/path" }
// SSE/HTTP
{ "url": "https://mcp.example.com", "headers": { "Authorization": "Bearer <token>" } }
// Streamable HTTP
{ "url": "https://mcp.example.com/stream", "transport": "streamable-http", "connectionTimeoutMs": 10000 }
```
### Notes/Gotchas
- Live queue starts when bridge connects; older history read with `messages_read`.
- `events_poll`/`events_wait` do NOT replay older Gateway history.
- `permissions_list_open` only shows approvals while bridge was connected.
- Blocked stdio env keys: `NODE_OPTIONS`, `PYTHONSTARTUP`, `PYTHONPATH`, `PERL5OPT`, `RUBYOPT`, `SHELLOPTS`, `PS4`, etc.
- `mcp list/show/set/unset` do NOT connect to the MCP server — config only.
---
## Memory
**Synopsis:** `openclaw memory <subcommand> [options]`
**Description:** Manage semantic memory indexing and search. Provided by active memory plugin (default: `memory-core`).
### Examples
```bash
openclaw memory status
openclaw memory status --deep
openclaw memory status --deep --index
openclaw memory status --fix
openclaw memory status --json
openclaw memory status --agent main
openclaw memory index --force
openclaw memory index --agent main --verbose
openclaw memory search "meeting notes"
openclaw memory search --query "deployment" --max-results 20
openclaw memory promote --limit 10 --min-score 0.75
openclaw memory promote --apply
openclaw memory promote --json --min-recall-count 0 --min-unique-queries 0
openclaw memory promote-explain "router vlan"
openclaw memory promote-explain "router vlan" --json
openclaw memory rem-harness
openclaw memory rem-harness --json
```
### Options
**`memory status`:**
- `--deep`: probe vector + embedding availability
- `--index`: run reindex if store is dirty (implies `--deep`)
- `--fix`: repair stale recall locks and normalize promotion metadata
- `--agent <id>`: scope to one agent
- `--verbose`: detailed logs
- `--json`
**`memory index`:**
- `--force`: force full reindex
- `--agent <id>`, `--verbose`
**`memory search`:**
- `[query]` or `--query <text>` (`--query` wins if both)
- `--agent <id>`, `--max-results <n>`, `--min-score <n>`, `--json`
**`memory promote`:**
- `--apply`: write to `MEMORY.md` (default: preview)
- `--limit <n>`, `--include-promoted`, `--agent <id>`
- `--min-score <n>`, `--min-recall-count <n>`, `--min-unique-queries <n>`
- `--json`
**`memory promote-explain <selector>`:**
- `--agent <id>`, `--include-promoted`, `--json`
**`memory rem-harness`:**
- `--agent <id>`, `--include-promoted`, `--json`
### Dreaming (Background Memory Consolidation)
Three phases: **light** (sort/stage) → **REM** (reflect/surface themes) → **deep** (promote to `MEMORY.md`)
Enable:
```json
{
"plugins": {
"entries": {
"memory-core": {
"config": { "dreaming": { "enabled": true } }
}
}
}
}
```
Toggle in chat: `/dreaming on|off` or `/dreaming status`
**Defaults:** sweep at `0 3 * * *`; deep thresholds: `minScore=0.8`, `minRecallCount=3`, `minUniqueQueries=3`, `recencyHalfLifeDays=14`, `maxAgeDays=30`
Human-readable output → `DREAMS.md`; durable memory → `MEMORY.md`
### Notes/Gotchas
- `memory status` shows `Dreaming status: blocked` → managed cron enabled but heartbeat not firing.
- Only the deep phase writes durable memory to `MEMORY.md`.
- `memory rem-harness --path <file-or-dir> --grounded` previews without writing anything.
---
## Message
**Synopsis:** `openclaw message <subcommand> [flags]`
**Description:** Single outbound command for sending messages and channel actions across all configured channels.
**Supported channels:** Discord, Google Chat, iMessage, Matrix, Mattermost (plugin), Microsoft Teams, Signal, Slack, Telegram, WhatsApp
### Channel Selection & Target Formats
- `--channel` required if more than one channel configured.
| Channel | `--target` format |
|---|---|
| WhatsApp | E.164 or group JID |
| Telegram | chat id or `@username` |
| Discord | `channel:<id>`, `user:<id>`, or `<@id>` |
| Google Chat | `spaces/<spaceId>` or `users/<userId>` |
| Slack | `channel:<id>` or `user:<id>` |
| Mattermost | `channel:<id>`, `user:<id>`, or `@username` |
| Signal | `+E.164`, `group:<id>`, `signal:+E.164`, `username:<name>` |
| iMessage | handle, `chat_id:<id>`, `chat_guid:<guid>` |
| Matrix | `@user:server`, `!room:server`, `#alias:server` |
| Microsoft Teams | `19:[email protected]`, `conversation:<id>`, `user:<aad-object-id>` |
### Common Flags
- `--channel <name>`, `--account <id>`, `--target <dest>`, `--targets <name>` (repeatable; broadcast)
- `--json`, `--dry-run`, `--verbose`
### Core Actions
**`send`**
```bash
openclaw message send --channel discord --target channel:123 --message "hi" --reply-to 456
openclaw message send --channel telegram --target @mychat --media ./diagram.png --force-document
```
Optional: `--media`, `--presentation`, `--delivery`, `--pin`, `--reply-to`, `--thread-id`, `--gif-playback` (WhatsApp), `--force-document` (Telegram), `--silent` (Telegram/Discord)
**`poll`**
```bash
openclaw message poll --channel discord --target channel:123 \
--poll-question "Snack?" --poll-option Pizza --poll-option Sushi \
--poll-multi --poll-duration-hours 48
openclaw message poll --channel telegram --target @mychat \
--poll-question "Lunch?" --poll-option Pizza --poll-option Sushi \
--poll-duration-seconds 120 --silent
```
**`react`**
```bash
openclaw message react --channel slack --target C123 --message-id 456 --emoji "✅"
openclaw message react --channel signal --target signal:group:abc123 --message-id 1737630212345 \
--emoji "✅" --target-author-uuid 123e4567-e89b-12d3-a456-426614174000
```
Required: `--message-id`, `--target`; Optional: `--emoji`, `--remove`, `--participant`, `--from-me`, `--target-author`, `--target-author-uuid`
**`reactions`** (Discord/Google Chat/Slack/Matrix): `--message-id`, `--target`, `--limit`
**`read`** (Discord/Slack/Matrix): `--target`, `--limit`, `--before`, `--after`; Discord: `--around`
**`edit`** (Discord/Slack/Matrix): `--message-id`, `--message`, `--target`
**`delete`** (Discord/Slack/Telegram/Matrix): `--message-id`, `--target`
**`pin`/`unpin`/`pins`** (Discord/Slack/Matrix): `--message-id`, `--target`
**`search`** (Discord): `--guild-id`, `--query`; Optional: `--channel-id`, `--channel-ids`, `--author-id`, `--author-ids`, `--limit`
### Threads (Discord)
```bash
openclaw message thread create --thread-name "Help" --target channel:123
openclaw message thread list --guild-id <guildId>
openclaw message thread reply --target <threadId> --message "Reply text"
```
### Emojis & Stickers (Discord)
```bash
openclaw message emoji list --guild-id <guildId>
openclaw message emoji upload --guild-id <guildId> --emoji-name "clawbot" --media ./emoji.png
openclaw message sticker send --target channel:123 --sticker-id <id>
openclaw message sticker upload --guild-id <guildId> --sticker-name "claw" --sticker-desc "clawbot" --sticker-tags "claw" --media ./sticker.png
```
### Roles / Channels / Members / Voice / Events / Moderation (Discord)
```bash
openclaw message role info --guild-id <guildId>
openclaw message role add --guild-id <guildId> --user-id <userId> --role-id <roleId>
openclaw message role remove --guild-id <guildId> --user-id <userId> --role-id <roleId>
openclaw message channel info --target <channelId>
openclaw message channel list --guild-id <guildId>
openclaw message member info --user-id <userId> --guild-id <guildId>
openclaw message voice status --guild-id <guildId> --user-id <userId>
openclaw message event list --guild-id <guildId>
openclaw message event create --guild-id <guildId> --event-name "Launch" --start-time "2026-05-01T14:00:00Z"
openclaw message timeout --guild-id <guildId> --user-id <userId> --duration-min 10
openclaw message kick --guild-id <guildId> --user-id <userId> --reason "Spam"
openclaw message ban --guild-id <guildId> --user-id <userId> --delete-days 1 --reason "Spam"
```
### Broadcast
```bash
openclaw message broadcast --channel all --targets "+15551234567" "+15559876543" --message "Hello"
openclaw message broadcast --channel discord --targets "channel:123" "channel:456" --media ./image.png --dry-run
```
### Presentation Blocks
```bash
openclaw message send --channel discord --target channel:123 --message "Choose:" \
--presentation '{"blocks":[{"type":"buttons","buttons":[{"label":"Approve","value":"approve","style":"success"},{"label":"Decline","value":"decline","style":"danger"}]}]}'
```
Core renders the same `presentation` payload into Discord components, Slack blocks, Telegram inline buttons, etc., based on channel capabilities.
---
## Models
**Synopsis:** `openclaw models <subcommand> [options]`
**Description:** Model discovery, scanning, and configuration (default model, fallbacks, auth profiles).
### Common Commands
```bash
openclaw models status
openclaw models list
openclaw models list --all
openclaw models list --provider moonshot
openclaw models set openai/gpt-5.4
openclaw models scan
openclaw models aliases list
openclaw models fallbacks list
```
### `models status` Options
| Flag | Description |
|---|---|
| `--json` | Machine-readable output |
| `--plain` | Plain output |
| `--check` | Exit 1=expired/missing, 2=expiring |
| `--probe` | Live probe of configured auth profiles |
| `--probe-provider <name>` | Probe one provider |
| `--probe-profile <id>` | Probe specific profiles (repeat or comma-separated) |
| `--probe-timeout <ms>` | Probe timeout |
| `--probe-concurrency <n>` | Probe concurrency |
| `--probe-max-tokens <n>` | Max tokens for probe |
| `--agent <id>` | Inspect a configured agent's model/auth state |
**Probe status buckets:** `ok`, `auth`, `rate_limit`, `billing`, `timeout`, `format`, `unknown`, `no_model`
### Auth Profiles
```bash
openclaw models auth add
openclaw models auth login --provider openai-codex --set-default
openclaw models auth setup-token --provider <id>
openclaw models auth paste-token
# With expiry:
openclaw models auth paste-token --expires-in 365d
```
### Auth Order (Per-Agent)
```bash
openclaw models auth order get --provider anthropic
openclaw models auth order set --provider anthropic anthropic:default
openclaw models auth order clear --provider anthropic
```
### Image Fallbacks
```bash
openclaw models image-fallbacks list
openclaw models image-fallbacks add <provider/model>
openclaw models image-fallbacks remove <provider/model>
openclaw models image-fallbacks clear
```
### `models scan` Options
| Flag | Description |
|---|---|
| `--no-probe` | Metadata only; no config/secrets lookup |
| `--min-params <b>` | Minimum param size |
| `--max-age-days <days>` | Max model age |
| `--provider <name>` | Filter by provider |
| `--max-candidates <n>` | Max candidates |
| `--timeout <ms>` | Catalog + per-probe timeout |
| `--concurrency <n>` | Probe concurrency |
| `--yes` | Auto-confirm |
| `--no-input` | Non-interactive |
| `--set-default` | Apply best result as default (requires live probe) |
| `--set-image` | Apply best result as image model (requires live probe) |
| `--json` | JSON output |
### Notes/Gotchas
- `models set` accepts `provider/model` or alias.
- `models list` is read-only — does NOT rewrite `models.json`.
- `models list --all` includes bundled provider-owned static catalog rows even when you haven't authenticated.
- `--provider` filter uses provider id (e.g. `moonshot`), NOT display label.
- Model refs parsed by splitting on first `/`. For OpenRouter: `openrouter/moonshotai/kimi-k2`.
- `models status` may show `marker(<value>)` for non-secret placeholders.
- Add `--probe` for live auth probes (real requests — may consume tokens).
- **Usage-window providers:** Anthropic, GitHub Copilot, Gemini CLI, OpenAI Codex, MiniMax, Xiaomi, z.ai — `models status` shows quota snapshots when available.
- **Probe detail/reason codes:** `excluded_by_auth_order`, `missing_credential`, `invalid_expires`, `expired`, `unresolved_ref`, `no_model`.
- `paste-token` requires `--provider`, prompts for token value, writes to profile `<provider>:manual` unless `--profile-id` is specified. `--expires-in <duration>` stores absolute expiry.
---
## Node
**Synopsis:** `openclaw node <subcommand> [options]`
**Description:** Run a headless node host that connects to the Gateway and exposes `system.run`/`system.which` on this machine.
### Run (foreground)
```bash
openclaw node run --host <gateway-host> --port 18789
```
Options: `--host <host>`, `--port <port>`, `--tls`, `--tls-fingerprint <sha256>`, `--node-id <id>`, `--display-name <name>`
### Service (background)
```bash
openclaw node install --host <gateway-host> --port 18789
openclaw node status
openclaw node stop
openclaw node restart
openclaw node uninstall
```
Additional `install` options: `--runtime <node|bun>`, `--force`
### Gateway Auth for Node Host
Resolution order:
1. `OPENCLAW_GATEWAY_TOKEN` / `OPENCLAW_GATEWAY_PASSWORD` env vars
2. Local config: `gateway.auth.token` / `gateway.auth.password`
3. In `gateway.mode=remote`: remote client fields per remote precedence rules
### Pairing
```bash
# First connection creates pending pairing request:
openclaw devices list
openclaw devices approve <requestId>
```
Node host stores state in `~/.openclaw/node.json`.
### Exec Approvals
`system.run` gated by local exec approvals (`~/.openclaw/exec-approvals.json`):
```bash
openclaw approvals --node <id|name|ip>
```
### Browser Proxy (zero-config)
Node hosts automatically advertise a browser proxy. Disable:
```json5
{ "nodeHost": { "browserProxy": { "enabled": false } } }
```
### Notes/Gotchas
- For non-loopback `ws://` on trusted private network: `OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1`
- `openclaw node install` persists this env var into the supervised node service when present.
- Approved async node exec uses canonical `systemRunPlan` — edits after approval request rejected.
---
## Nodes
**Synopsis:** `openclaw nodes <subcommand> [options]`
**Description:** Manage paired nodes and invoke node capabilities.
### Common Commands
```bash
openclaw nodes list
openclaw nodes list --connected
openclaw nodes list --last-connected 24h
openclaw nodes pending
openclaw nodes approve <requestId>
openclaw nodes reject <requestId>
openclaw nodes rename --node <id|name|ip> --name <displayName>
openclaw nodes status
openclaw nodes status --connected
openclaw nodes status --last-connected 24h
```
### Invoke
```bash
openclaw nodes invoke --node <id|name|ip> --command <command> --params '{"key":"value"}'
```
Options: `--params <json>`, `--invoke-timeout <ms>` (default 15000), `--idempotency-key <key>`
> `system.run` and `system.run.prepare` are blocked here; use `exec` tool with `host=node` for shell execution.
### Common Options
- `--url`, `--token`, `--timeout`, `--json`
### Approval Scope Requirements
- `nodes pending`: pairing scope only
- `nodes approve <requestId>`:
- commandless: pairing only
- non-exec node commands: pairing + write
- `system.run`/`system.run.prepare`/`system.which`: pairing + admin
---
## Onboard
**Synopsis:** `openclaw onboard [options]`
**Description:** Interactive onboarding for local or remote Gateway setup.
### Examples
```bash
openclaw onboard
openclaw onboard --flow quickstart
openclaw onboard --flow manual
openclaw onboard --skip-bootstrap
openclaw onboard --mode remote --remote-url wss://gateway-host:18789
# Non-interactive custom provider
openclaw onboard --non-interactive \
--auth-choice custom-api-key \
--custom-base-url "https://llm.example.com/v1" \
--custom-model-id "foo-large" \
--custom-api-key "$CUSTOM_API_KEY" \
--secret-input-mode plaintext \
--custom-compatibility openai
# Non-interactive Ollama
openclaw onboard --non-interactive \
--auth-choice ollama \
--custom-base-url "http://ollama-host:11434" \
--custom-model-id "qwen3.5:27b" \
--accept-risk
# Store as SecretRef instead of plaintext
openclaw onboard --non-interactive \
--auth-choice openai-api-key \
--secret-input-mode ref \
--accept-risk
# Gateway token as SecretRef
export OPENCLAW_GATEWAY_TOKEN="your-token"
openclaw onboard --non-interactive \
--mode local \
--auth-choice skip \
--gateway-auth token \
--gateway-token-ref-env OPENCLAW_GATEWAY_TOKEN \
--accept-risk
```
### Key Options
| Flag | Description |
|---|---|
| `--flow <quickstart\|manual>` | Onboarding flow |
| `--skip-bootstrap` | Skip creating workspace bootstrap files |
| `--mode <local\|remote>` | Onboarding mode |
| `--remote-url <url>` | Remote Gateway WebSocket URL |
| `--non-interactive` | Run without prompts |
| `--auth-choice <choice>` | Auth provider choice |
| `--custom-base-url <url>` | Custom provider base URL |
| `--custom-model-id <id>` | Custom model ID |
| `--custom-api-key <key>` | Custom API key |
| `--custom-compatibility <openai\|anthropic\|unknown>` | Compatibility mode |
| `--secret-input-mode <plaintext\|ref>` | How to store secrets |
| `--gateway-auth <token\|password\|none>` | Gateway auth mode |
| `--gateway-token <token>` | Gateway token (plaintext) |
| `--gateway-token-ref-env <name>` | Gateway token env SecretRef |
| `--install-daemon` | Install gateway daemon |
| `--skip-health` | Skip waiting for reachable local gateway |
| `--accept-risk` | Accept risk warnings |
### Notes/Gotchas
- `quickstart`: minimal prompts, auto-generates gateway token.
- `manual` / `advanced`: full prompts for port/bind/auth.
- `--json` does NOT imply non-interactive mode. Use `--non-interactive` for scripts.
- `--gateway-token` and `--gateway-token-ref-env` are mutually exclusive.
- Local onboarding writes `gateway.mode="local"` into config.
---
## Pairing
**Synopsis:** `openclaw pairing <subcommand> [options]`
**Description:** Approve or inspect DM pairing requests for channels that support pairing.
### Commands
```bash
openclaw pairing list telegram
openclaw pairing list --channel telegram --account work
openclaw pairing list telegram --json
openclaw pairing approve <code>
openclaw pairing approve telegram <code>
openclaw pairing approve --channel telegram --account work <code> --notify
```
### `pairing list` Options
- `[channel]` (positional), `--channel <channel>`, `--account <accountId>`, `--json`
### `pairing approve` Options
- `--channel <channel>`, `--account <accountId>`, `--notify` (send confirmation back to requester)
### Notes/Gotchas
- With multiple pairing-capable channels configured, must provide channel.
- With only one channel: `pairing approve <code>` without specifying channel is allowed.
---
## Plugins
**Synopsis:** `openclaw plugins <subcommand> [options]`
**Description:** Manage Gateway plugins, hook packs, and compatible bundles.
### Commands
```bash
openclaw plugins list
openclaw plugins list --enabled
openclaw plugins list --verbose
openclaw plugins list --json
openclaw plugins install <path-or-spec>
openclaw plugins inspect <id>
openclaw plugins inspect <id> --json
openclaw plugins inspect --all
openclaw plugins info <id> # alias for inspect
openclaw plugins enable <id>
openclaw plugins disable <id>
openclaw plugins uninstall <id>
openclaw plugins uninstall <id> --dry-run
openclaw plugins uninstall <id> --keep-files
openclaw plugins doctor
openclaw plugins update <id-or-npm-spec>
openclaw plugins update --all
openclaw plugins update <id-or-npm-spec> --dry-run
openclaw plugins marketplace list <source>
openclaw plugins marketplace list <source> --json
```
### Install
```bash
openclaw plugins install <package> # ClawHub first, then npm
openclaw plugins install clawhub:<package> # ClawHub only
openclaw plugins install clawhub:<package>@1.2.3 # Specific ClawHub version
openclaw plugins install <package> --force # overwrite existing
openclaw plugins install <package> --pin # pin version
openclaw plugins install <package> --dangerously-force-unsafe-install
openclaw plugins install <path> # local path
openclaw plugins install -l ./my-plugin # link without copying
openclaw plugins install <plugin>@<marketplace> # marketplace
openclaw plugins install <plugin> --marketplace <name>
openclaw plugins install <plugin> --marketplace https://github.com/<owner>/<repo>
```
### Update
```bash
openclaw plugins update <id-or-npm-spec>
openclaw plugins update --all
openclaw plugins update <id-or-npm-spec> --dry-run
openclaw plugins update @openclaw/voice-call@beta
```
### Inspect
Shows: identity, load status, source, capabilities, hooks, tools, commands, services, gateway methods, HTTP routes, policy flags, diagnostics, install metadata, bundle capabilities, MCP/LSP server support.
**Plugin shape classifications:**
- `plain-capability`: one capability type
- `hybrid-capability`: multiple capability types
- `hook-only`: only hooks
- `non-capability`: tools/commands/services but no capabilities
### Doctor
```bash
openclaw plugins doctor
# For module-shape failures, set: OPENCLAW_PLUGIN_LOAD_DEBUG=1
```
### Marketplace Sources
- Local marketplace path or `marketplace.json` path
- `owner/repo` GitHub shorthand
- GitHub URL or git URL
### Notes/Gotchas
- Npm specs: registry-only (exact version or dist-tag). No git/URL/file specs or semver ranges.
- Bare specs and `@latest` stay on stable track. If npm resolves to prerelease, OpenClaw stops.
- `--force` overwrites existing install in place.
- `--link` adds to `plugins.load.paths` instead of copying.
- `--dangerously-force-unsafe-install` for false positives in scanner (does NOT bypass policy blocks).
- After changing plugin code/enablement/hooks: **restart the Gateway**.
- If `plugins.allow` present in config, bundled browser plugin must be listed explicitly.
- `plugins list` is NOT a live runtime probe — does not probe running Gateway.
---
## Proxy
**Synopsis:** `openclaw proxy <subcommand> [options]`
**Description:** Run local explicit debug proxy and inspect captured traffic. For transport-level investigation.
### Commands
```bash
openclaw proxy start [--host <host>] [--port <port>]
openclaw proxy run [--host <host>] [--port <port>] -- <cmd...>
openclaw proxy coverage
openclaw proxy sessions [--limit <count>]
openclaw proxy query --preset <name> [--session <id>]
openclaw proxy blob --id <blobId>
openclaw proxy purge
```
### Query Presets
`openclaw proxy query --preset <name>` accepts:
- `double-sends`, `retry-storms`, `cache-busting`
- `ws-duplicate-frames`, `missing-ack`, `error-bursts`
### Notes/Gotchas
- `start` defaults to `127.0.0.1` unless `--host` set.
- `run` starts local debug proxy then runs command after `--`.
- Use `openclaw proxy purge` when done to remove capture data.
---
## QR
**Synopsis:** `openclaw qr [options]`
**Description:** Generate a mobile pairing QR and setup code from your current Gateway configuration.
### Options
| Flag | Description |
|---|---|
| `--remote` | Prefer `gateway.remote.url` |
| `--url <url>` | Override gateway URL in payload |
| `--public-url <url>` | Override public URL in payload |
| `--token <token>` | Override gateway token for bootstrap flow |
| `--password <password>` | Override gateway password for bootstrap flow |
| `--setup-code-only` | Print only setup code |
| `--no-ascii` | Skip ASCII QR rendering |
| `--json` | Emit JSON: `setupCode`, `gatewayUrl`, `auth`, `urlSource` |
### Examples
```bash
openclaw qr
openclaw qr --setup-code-only
openclaw qr --json
openclaw qr --remote
openclaw qr --url wss://gateway.example/ws
```
### Notes/Gotchas
- `--token` and `--password` are mutually exclusive.
- Setup code carries a short-lived `bootstrapToken`, NOT the shared gateway token/password.
- Mobile pairing fails closed for Tailscale/public `ws://` URLs. Private LAN `ws://` supported.
- With `--remote`, requires `gateway.remote.url` or `gateway.tailscale.mode=serve|funnel`.
- After scanning: `openclaw devices list` → `openclaw devices approve <requestId>`
---
## Reset
**Synopsis:** `openclaw reset [options]`
**Description:** Reset local config/state (keeps the CLI installed).
### Options
| Flag | Description |
|---|---|
| `--scope <scope>` | `config`, `config+creds+sessions`, or `full` |
| `--yes` | Skip confirmation prompts |
| `--non-interactive` | Disable prompts (requires `--scope` and `--yes`) |
| `--dry-run` | Print actions without removing files |
### Examples
```bash
openclaw backup create # backup first!
openclaw reset
openclaw reset --dry-run
openclaw reset --scope config --yes --non-interactive
openclaw reset --scope config+creds+sessions --yes --non-interactive
openclaw reset --scope full --yes --non-interactive
```
---
## Sandbox
**Synopsis:** `openclaw sandbox <subcommand> [options]`
**Description:** Manage sandbox runtimes for isolated agent execution (Docker, SSH, OpenShell).
### Commands
```bash
# Explain effective sandbox settings
openclaw sandbox explain
openclaw sandbox explain --session agent:main:main
openclaw sandbox explain --agent work
openclaw sandbox explain --json
# List sandbox runtimes
openclaw sandbox list
openclaw sandbox list --browser
openclaw sandbox list --json
# Recreate runtimes
openclaw sandbox recreate --all
openclaw sandbox recreate --session main
openclaw sandbox recreate --agent mybot
openclaw sandbox recreate --browser
openclaw sandbox recreate --all --force
```
### Use Cases
```bash
# After updating Docker image
docker pull openclaw-sandbox:latest
docker tag openclaw-sandbox:latest openclaw-sandbox:bookworm-slim
openclaw sandbox recreate --all
# After changing sandbox config
# Edit: agents.defaults.sandbox.*
openclaw sandbox recreate --all
# After changing SSH target
# Edit: agents.defaults.sandbox.ssh.*
openclaw sandbox recreate --all
# For a specific agent only
openclaw sandbox recreate --agent alfred
```
### Configuration (in `openclaw.json`)
```jsonc
{
"agents": {
"defaults": {
"sandbox": {
"mode": "all", // off, non-main, all
"backend": "docker", // docker, ssh, openshell
"scope": "agent", // session, agent, shared
"docker": {
"image": "openclaw-sandbox:bookworm-slim",
"containerPrefix": "openclaw-sbx-"
},
"prune": {
"idleHours": 24,
"maxAgeDays": 7
}
}
}
}
}
```
### Notes/Gotchas
- Existing runtimes keep running with old settings until recreated or 24h idle prune.
- For SSH/OpenShell `remote`, `recreate` deletes the canonical remote workspace (re-seeded on next use).
- Prefer `openclaw sandbox recreate` over manual backend-specific cleanup.
---
## Secrets
**Synopsis:** `openclaw secrets <subcommand> [options]`
**Description:** Manage SecretRefs and keep active runtime snapshot healthy.
### Recommended Operator Loop
```bash
openclaw secrets audit --check
openclaw secrets configure
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --dry-run
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json
openclaw secrets audit --check
openclaw secrets reload
```
### `secrets reload`
```bash
openclaw secrets reload
openclaw secrets reload --json
openclaw secrets reload --url ws://127.0.0.1:18789 --token <token>
```
> If resolution fails, gateway keeps last-known-good snapshot (no partial activation).
### `secrets audit`
```bash
openclaw secrets audit
openclaw secrets audit --check # exit 1 on findings, 2 on unresolved refs
openclaw secrets audit --json
openclaw secrets audit --allow-exec # also check exec SecretRefs
```
**Scans for:** plaintext storage, unresolved refs, precedence drift, generated model.json residues, legacy residues
**Finding codes:** `PLAINTEXT_FOUND`, `REF_UNRESOLVED`, `REF_SHADOWED`, `LEGACY_RESIDUE`
### `secrets configure` (Interactive TTY required)
```bash
openclaw secrets configure
openclaw secrets configure --plan-out /tmp/openclaw-secrets-plan.json
openclaw secrets configure --apply --yes
openclaw secrets configure --providers-only
openclaw secrets configure --skip-provider-setup
openclaw secrets configure --agent ops
```
Options: `--providers-only`, `--skip-provider-setup`, `--agent <id>`, `--allow-exec`, `--json`
### `secrets apply`
```bash
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --allow-exec
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --dry-run
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --dry-run --allow-exec
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --json
```
**What `apply` updates:** `openclaw.json` (SecretRef targets + provider upserts), `auth-profiles.json`, legacy `auth.json`, `~/.openclaw/.env` known secret keys
### Notes/Gotchas
- No rollback backups — apply path is one-way for scrubbed plaintext values.
- Write mode rejects exec-containing plans unless `--allow-exec` set.
- Safety: strict preflight + atomic-ish apply with best-effort in-memory restore on failure.
---
## Security
**Synopsis:** `openclaw security audit [options]`
**Description:** Security audit tools with optional fixes.
### Audit
```bash
openclaw security audit
openclaw security audit --deep
openclaw security audit --deep --token <token>
openclaw security audit --fix
openclaw security audit --json
```
### What Audit Warns About
- Multiple DM senders sharing main session (recommends `session.dmScope="per-channel-peer"`)
- Shared-user ingress heuristic (open DM/group policy, wildcard sender rules)
- Small models (≤300B) without sandboxing with web/browser tools enabled
- Webhook ingress security (token reuse, short token, path="/", etc.)
- Sandbox Docker settings with sandbox mode off
- Ineffective `gateway.nodes.denyCommands` patterns (exact command-name only, not shell-text)
- Open groups exposing runtime/filesystem tools without sandbox guards
- `gateway.allowRealIpFallback=true` (header-spoofing risk)
- `discovery.mdns.mode="full"` (metadata leakage)
- Unpinned/missing-integrity npm-based plugin install records
- Channel allowlists using mutable names instead of stable IDs
- `gateway.auth.mode="none"` (no shared secret)
- Sandbox browser using Docker `bridge` network without `sandbox.browser.cdpSourceRange`
### JSON for CI/Policy Checks
```bash
openclaw security audit --json | jq '.summary'
openclaw security audit --deep --json | jq '.findings[] | select(.severity=="critical") | .checkId'
openclaw security audit --fix --json | jq '{fix: .fix.ok, summary: .report.summary}'
```
### What `--fix` Changes
✅ Changes:
- Flips `groupPolicy="open"` to `groupPolicy="allowlist"`
- Seeds `groupAllowFrom` from `allowFrom` file (WhatsApp)
- Sets `logging.redactSensitive` from `"off"` to `"tools"`
- Tightens permissions for state/config and sensitive files
❌ Does NOT change:
- Rotate tokens/passwords/API keys
- Disable tools
- Change gateway bind/auth/network choices
- Remove plugins/skills
---
## Sessions
**Synopsis:** `openclaw sessions [options]`
**Description:** List stored conversation sessions.
### Examples
```bash
openclaw sessions
openclaw sessions --agent work
openclaw sessions --all-agents
openclaw sessions --active 120
openclaw sessions --verbose
openclaw sessions --json
```
### Scope Selection
- Default: configured default agent store
- `--agent <id>`: one configured agent store
- `--all-agents`: all configured agent stores
- `--store <path>`: explicit store path
### Cleanup
```bash
openclaw sessions cleanup --dry-run
openclaw sessions cleanup --agent work --dry-run
openclaw sessions cleanup --all-agents --dry-run
openclaw sessions cleanup --enforce
openclaw sessions cleanup --enforce --active-key "agent:main:telegram:direct:123"
openclaw sessions cleanup --json
```
**Cleanup options:** `--dry-run`, `--enforce` (apply even in warn mode), `--fix-missing`, `--active-key <key>`, `--agent <id>`, `--all-agents`, `--store <path>`, `--json`
### Notes/Gotchas
- `sessions cleanup` maintains session stores/transcripts only. Cron run logs managed separately.
- `--all-agents` reads configured stores AND discovers disk-only stores under default `agents/` root.
---
## Setup
**Synopsis:** `openclaw setup [options]`
**Description:** Initialize `~/.openclaw/openclaw.json` and agent workspace.
### Options
| Flag | Description |
|---|---|
| `--workspace <dir>` | Agent workspace directory |
| `--wizard` | Run onboarding |
| `--non-interactive` | Run without prompts |
| `--mode <local\|remote>` | Onboarding mode |
| `--remote-url <url>` | Remote Gateway WebSocket URL |
| `--remote-token <token>` | Remote Gateway token |
### Examples
```bash
openclaw setup
openclaw setup --workspace ~/.openclaw/workspace
openclaw setup --wizard
openclaw setup --non-interactive --mode remote --remote-url wss://gateway-host:18789 --remote-token <token>
```
### Notes/Gotchas
- Plain `openclaw setup` initializes config + workspace WITHOUT the full onboarding flow.
- Onboarding auto-runs when any onboarding flags present (`--wizard`, `--non-interactive`, `--mode`, etc.).
---
## Skills
**Synopsis:** `openclaw skills <subcommand> [options]`
**Description:** Inspect local skills and install/update from ClawHub.
### Commands
```bash
openclaw skills search "calendar"
openclaw skills search --limit 20 --json
openclaw skills install <slug>
openclaw skills install <slug> --version <version>
openclaw skills install <slug> --force
openclaw skills update <slug>
openclaw skills update --all
openclaw skills list
openclaw skills list --eligible
openclaw skills list --json
openclaw skills list --verbose
openclaw skills info <name>
openclaw skills info <name> --json
openclaw skills check
openclaw skills check --json
```
### Notes/Gotchas
- `search`/`install`/`update` use ClawHub; install into active workspace `skills/` directory.
- `list`/`info`/`check` inspect local skills (not live ClawHub).
- `install --force` overwrites existing workspace skill folder.
- `update --all` only updates tracked ClawHub installs in active workspace.
- `list` is default action when no subcommand provided.
---
## Status
**Synopsis:** `openclaw status [options]`
**Description:** Diagnostics for channels + sessions.
### Examples
```bash
openclaw status
openclaw status --all
openclaw status --deep
openclaw status --usage
```
### Notes/Gotchas
- `--deep`: runs live probes (WhatsApp Web, Telegram, Discord, Slack, Signal).
- `--usage`: prints normalized provider usage windows as `X% left`.
- Session status: `Runtime:` = execution path/sandbox state; `Runner:` = embedded Pi, CLI-backed, or ACP.
- Output includes per-agent session stores when multiple agents configured.
- Overview includes Gateway + node host service status, update channel + git SHA.
- If update available, hints to run `openclaw update`.
- `--all` includes Secrets overview and diagnosis section.
- SecretRefs resolved in read-only mode for targeted config paths.
---
## System
**Synopsis:** `openclaw system <subcommand> [options]`
**Description:** System-level helpers for the Gateway: system events, heartbeat control, presence.
All subcommands use Gateway RPC. Shared options: `--url`, `--token`, `--timeout <ms>`, `--expect-final`
### Commands
```bash
# Enqueue system event (injected at next heartbeat as "System:" prompt line)
openclaw system event --text "Check for urgent follow-ups" --mode now
openclaw system event --text "Check for urgent follow-ups" --url ws://127.0.0.1:18789 --token "$OPENCLAW_GATEWAY_TOKEN"
# Heartbeat controls
openclaw system heartbeat enable
openclaw system heartbeat disable
openclaw system heartbeat last
# Presence
openclaw system presence
openclaw system presence --json
```
### `system event` Flags
- `--text <text>`: required event text
- `--mode <now|next-heartbeat>`: when to trigger (default `next-heartbeat`)
- `--json`
### Notes/Gotchas
- Requires running Gateway.
- System events are ephemeral — not persisted across restarts.
---
## Tasks
**Synopsis:** `openclaw tasks [subcommand] [options]`
**Description:** Inspect durable background tasks and Task Flow state. No subcommand = `tasks list`.
### Commands
```bash
openclaw tasks
openclaw tasks list
openclaw tasks list --runtime acp
openclaw tasks list --status running
openclaw tasks list --json
openclaw tasks show <lookup>
openclaw tasks notify <lookup> state_changes
openclaw tasks cancel <lookup>
openclaw tasks audit
openclaw tasks audit --severity warn --code <name> --limit 50 --json
openclaw tasks maintenance
openclaw tasks maintenance --apply
openclaw tasks maintenance --apply --json
openclaw tasks flow list
openclaw tasks flow list --status running --json
openclaw tasks flow show <lookup>
openclaw tasks flow cancel <lookup>
```
### Root Options
- `--json`
- `--runtime <subagent|acp|cron|cli>`: filter by runtime kind
- `--status <queued|running|succeeded|failed|timed_out|cancelled|lost>`: filter by status
### Subcommands
| Subcommand | Description |
|---|---|
| `list` | List background tasks, newest first |
| `show <lookup>` | Show one task by task ID, run ID, or session key |
| `notify <lookup> <policy>` | Change notification policy: `done_only\|state_changes\|silent` |
| `cancel <lookup>` | Cancel a running task |
| `audit` | Surface stale, lost, delivery-failed, or inconsistent task records |
| `maintenance [--apply]` | Preview or apply task reconciliation, cleanup, and pruning |
| `flow list` | List Task Flow state |
| `flow show <lookup>` | Show one Task Flow |
| `flow cancel <lookup>` | Cancel a Task Flow |
---
## TUI / chat / terminal
**Synopsis:** `openclaw tui [options]`
**Aliases:** `openclaw chat` = `openclaw terminal` = `openclaw tui --local`
**Description:** Open the terminal UI connected to the Gateway, or run in local embedded mode.
### Examples
```bash
openclaw chat
openclaw tui --local
openclaw tui
openclaw tui --url ws://127.0.0.1:18789 --token <token>
openclaw tui --session main --deliver
openclaw chat --message "Compare my config to the docs and tell me what to fix"
openclaw tui --session bugfix # auto-infers agent when run inside agent workspace dir
```
### Key Options
- `--local`: run embedded agent directly (most local tools work; Gateway-only features unavailable)
- `--url <url>`, `--token <token>`, `--password <password>`: explicit Gateway connection
- `--session <key>`: session key
- `--deliver`: send reply back to selected channel
### Config Repair Loop
```bash
# When config is already valid, use local mode:
openclaw chat
# Inside TUI:
# !openclaw config file
# !openclaw docs gateway auth token secretref
# !openclaw config validate
# !openclaw doctor
```
> If `openclaw config validate` is failing, use `openclaw configure` or `openclaw doctor --fix` first.
### Notes/Gotchas
- `chat` and `terminal` are aliases for `openclaw tui --local`.
- `--local` cannot be combined with `--url`, `--token`, or `--password`.
- Plugin approval gates still apply in local mode.
- When launched from inside a configured agent workspace, auto-selects that agent for session key default.
- Local mode adds `/auth [provider]` inside the TUI command surface.
---
## Uninstall
**Synopsis:** `openclaw uninstall [options]`
**Description:** Uninstall gateway service + local data (CLI binary remains).
### Options
| Flag | Description |
|---|---|
| `--service` | Remove gateway service |
| `--state` | Remove state and config |
| `--workspace` | Remove workspace directories |
| `--app` | Remove macOS app |
| `--all` | Remove service, state, workspace, and app |
| `--yes` | Skip confirmation prompts |
| `--non-interactive` | Disable prompts (requires `--yes`) |
| `--dry-run` | Print actions without removing files |
### Examples
```bash
openclaw backup create # backup first!
openclaw uninstall
openclaw uninstall --service --yes --non-interactive
openclaw uninstall --state --workspace --yes --non-interactive
openclaw uninstall --all --yes
openclaw uninstall --dry-run
```
---
## Update
**Synopsis:** `openclaw update [options]`
**Description:** Safely update OpenClaw and switch between stable/beta/dev channels.
### Commands
```bash
openclaw update
openclaw update status
openclaw update wizard
openclaw update --channel beta
openclaw update --channel dev
openclaw update --tag beta
openclaw update --tag main
openclaw update --dry-run
openclaw update --no-restart
openclaw update --yes
openclaw update --json
openclaw --update # shorthand
```
### Options
| Flag | Description |
|---|---|
| `--no-restart` | Skip restarting Gateway after update |
| `--channel <stable\|beta\|dev>` | Set update channel (persisted in config) |
| `--tag <dist-tag\|version\|spec>` | Override package target for this update only |
| `--dry-run` | Preview actions without writing/installing |
| `--json` | Print machine-readable `UpdateRunResult` JSON |
| `--timeout <seconds>` | Per-step timeout (default 1200s) |
| `--yes` | Skip confirmation prompts |
### `update status`
```bash
openclaw update status
openclaw update status --json
openclaw update status --timeout 10
```
### `update wizard`
Interactive flow to pick channel and confirm restart behavior.
### Channels
| Channel | Behavior |
|---|---|
| `stable` | npm `latest` tag |
| `beta` | npm `beta` tag (falls back to stable if missing/older) |
| `dev` | git checkout of `main` branch |
### Git Checkout Flow (source installs)
1. Requires clean worktree.
2. Switches to selected channel (tag or branch).
3. Fetches upstream (dev only).
4. Dev: preflight lint + TypeScript build in temp worktree; walks back up to 10 commits if tip fails.
5. Rebases onto selected commit (dev only).
6. Installs deps.
7. Builds + Control UI.
8. Runs `openclaw doctor`.
9. Syncs plugins.
### Notes/Gotchas
- Downgrades require confirmation (older versions can break config).
- If exact pinned npm plugin resolves to artifact with different integrity, update aborts.
- For git checkout with pnpm: bootstraps `pnpm` on demand via `corepack`.
---
## Voicecall (plugin)
**Synopsis:** `openclaw voicecall <subcommand>`
**Description:** Plugin-provided command. Only appears if voice-call plugin is installed and enabled.
### Commands
```bash
openclaw voicecall status --call-id <id>
openclaw voicecall call --to "+15555550123" --message "Hello" --mode notify
openclaw voicecall continue --call-id <id> --message "Any questions?"
openclaw voicecall dtmf --call-id <id> --digits "ww123456#"
openclaw voicecall end --call-id <id>
# Expose webhooks (Tailscale)
openclaw voicecall expose --mode serve
openclaw voicecall expose --mode funnel
openclaw voicecall expose --mode off
```
### Notes/Gotchas
- Only expose webhook endpoint to trusted networks. Prefer Tailscale Serve over Funnel.
- See: [Voice Call plugin docs](https://docs.openclaw.ai/plugins/voice-call)
---
## Webhooks
**Synopsis:** `openclaw webhooks <subcommand> [options]`
**Description:** Webhook helpers and integrations (Gmail Pub/Sub).
### Gmail
```bash
openclaw webhooks gmail setup --account [email protected]
openclaw webhooks gmail setup --account [email protected] --project my-gcp-project --json
openclaw webhooks gmail setup --account [email protected] --hook-url https://gateway.example.com/hooks/gmail
openclaw webhooks gmail run --account [email protected]
```
### `webhooks gmail setup` Options
| Flag | Description |
|---|---|
| `--account <email>` | Required |
| `--project <id>` | GCP project |
| `--topic <name>` | Pub/Sub topic |
| `--subscription <name>` | Pub/Sub subscription |
| `--label <label>` | Gmail label |
| `--hook-url <url>` | OpenClaw webhook URL |
| `--hook-token <token>` | Webhook auth token |
| `--push-token <token>` | Pub/Sub push token |
| `--bind <host>` | Bind host |
| `--port <port>` | Bind port |
| `--path <path>` | Webhook path |
| `--include-body` | Include email body |
| `--max-bytes <n>` | Max body bytes |
| `--renew-minutes <n>` | Auto-renewal interval |
| `--tailscale <funnel\|serve\|off>` | Tailscale exposure mode |
| `--tailscale-path <path>` | Tailscale path |
| `--tailscale-target <target>` | Tailscale target |
| `--push-endpoint <url>` | Push endpoint URL |
| `--json` | Machine-readable output |
### `webhooks gmail run` Options
Same as `setup` minus `--push-endpoint` and `--json`. Runs `gog watch serve` plus auto-renew loop.
---
## Wiki
**Synopsis:** `openclaw wiki <subcommand> [options]`
**Description:** Inspect and maintain the `memory-wiki` vault. Provided by the bundled `memory-wiki` plugin.
### Commands
```bash
openclaw wiki status
openclaw wiki doctor
openclaw wiki init
openclaw wiki ingest ./notes/alpha.md
openclaw wiki compile
openclaw wiki lint
openclaw wiki search "alpha"
openclaw wiki get entity.alpha
openclaw wiki get syntheses/alpha-summary.md --from 1 --lines 80
# Apply narrow mutations
openclaw wiki apply synthesis "Alpha Summary" \
--body "Short synthesis body" \
--source-id source.alpha
openclaw wiki apply metadata entity.alpha \
--source-id source.alpha \
--status review \
--question "Still active?"
# Bridge import (bridge mode)
openclaw wiki bridge import
# Unsafe local import
openclaw wiki unsafe-local import
# Obsidian helpers
openclaw wiki obsidian status
openclaw wiki obsidian search "alpha"
openclaw wiki obsidian open syntheses/alpha-summary.md
openclaw wiki obsidian command workspace:quick-switcher
openclaw wiki obsidian daily
```
### Command Reference
| Command | Description |
|---|---|
| `wiki status` | Inspect vault mode, health, Obsidian CLI availability |
| `wiki doctor` | Run health checks, surface config/vault problems |
| `wiki init` | Create wiki vault layout and starter pages |
| `wiki ingest <path-or-url>` | Import content into wiki source layer |
| `wiki compile` | Rebuild indexes, dashboards, compiled digests |
| `wiki lint` | Lint vault: structural issues, contradictions, stale pages |
| `wiki search <query>` | Search wiki content |
| `wiki get <lookup>` | Read wiki page by id or relative path |
| `wiki apply` | Apply narrow mutations (synthesis, metadata, claims) |
| `wiki bridge import` | Import from active memory plugin (bridge mode) |
| `wiki unsafe-local import` | Import from local paths (experimental) |
| `wiki obsidian ...` | Obsidian helper commands |
### Configuration Tie-ins
- `plugins.entries.memory-wiki.config.vaultMode`
- `plugins.entries.memory-wiki.config.search.backend`
- `plugins.entries.memory-wiki.config.search.corpus`
- `plugins.entries.memory-wiki.config.bridge.*`
- `plugins.entries.memory-wiki.config.obsidian.*`
- `plugins.entries.memory-wiki.config.render.*`
- `plugins.entries.memory-wiki.config.context.includeCompiledDigestPrompt`
### Practical Guidance
- Use `wiki search` + `wiki get` when provenance and page identity matter.
- Use `wiki apply` instead of hand-editing managed generated sections.
- Use `wiki lint` before trusting contradictory or low-confidence content.
- Use `wiki compile` after bulk imports for fresh dashboards immediately.
- Use `wiki bridge import` when bridge mode depends on newly exported memory artifacts.
---
## Quick Reference Table
| Command | One-line Description |
|---|---|
| `openclaw acp` | ACP bridge from IDE → Gateway over WebSocket |
| `openclaw acp client` | Debug ACP client — type prompts interactively |
| `openclaw agent` | Run a single agent turn via Gateway |
| `openclaw agents list` | List configured agents |
| `openclaw agents add` | Add a new isolated agent |
| `openclaw agents bind` | Add channel routing binding to agent |
| `openclaw agents unbind` | Remove channel routing binding |
| `openclaw agents delete` | Delete an isolated agent |
| `openclaw agents set-identity` | Set agent identity (name/emoji/avatar) |
| `openclaw approvals get` | Show effective exec approval policy |
| `openclaw approvals set` | Replace exec approvals from file/stdin |
| `openclaw approvals allowlist` | Add/remove exec allowlist entries |
| `openclaw exec-policy` | Local shortcut for exec approval presets |
| `openclaw backup create` | Create backup archive of state + config + workspace |
| `openclaw backup verify` | Validate a backup archive |
| `openclaw browser` | Manage browser control (lifecycle/tabs/automation) |
| `openclaw channels list` | List configured channel accounts |
| `openclaw channels status` | Show channel status (with optional live probe) |
| `openclaw channels capabilities` | Inspect channel capabilities |
| `openclaw channels resolve` | Resolve names to channel IDs |
| `openclaw channels logs` | Tail channel logs |
| `openclaw channels add` | Add a channel account |
| `openclaw channels remove` | Remove a channel account |
| `openclaw channels login` | Interactive OAuth login for channel |
| `openclaw channels logout` | Logout channel account |
| `openclaw clawbot qr` | Legacy alias for `openclaw qr` |
| `openclaw completion` | Generate shell completion scripts |
| `openclaw config get` | Get a config value by path |
| `openclaw config set` | Set a config value (value/SecretRef/provider/batch) |
| `openclaw config unset` | Unset a config value |
| `openclaw config file` | Print active config file path |
| `openclaw config schema` | Print JSON schema for openclaw.json |
| `openclaw config validate` | Validate config file |
| `openclaw configure` | Interactive config setup wizard |
| `openclaw cron list` | List cron jobs |
| `openclaw cron add` | Add a cron job |
| `openclaw cron edit` | Edit a cron job |
| `openclaw cron rm` | Remove a cron job |
| `openclaw cron run` | Manually trigger a cron job |
| `openclaw cron enable/disable` | Enable or disable a cron job |
| `openclaw cron runs` | Show run history for a cron job |
| `openclaw daemon` | Legacy alias for `openclaw gateway` service commands |
| `openclaw dashboard` | Open Control UI |
| `openclaw devices list` | List paired devices |
| `openclaw devices approve` | Approve a device pairing request |
| `openclaw devices reject` | Reject a device pairing request |
| `openclaw devices remove` | Remove a paired device |
| `openclaw devices clear` | Clear pending device requests |
| `openclaw devices rotate` | Rotate a device token |
| `openclaw devices revoke` | Revoke a device token |
| `openclaw directory self` | Look up own channel identity |
| `openclaw directory peers list` | List channel contacts/peers |
| `openclaw directory groups list` | List channel groups |
| `openclaw directory groups members` | List group members |
| `openclaw dns setup` | Configure wide-area DNS discovery |
| `openclaw docs` | Search live docs |
| `openclaw doctor` | Health checks + quick fixes |
| `openclaw gateway` | Run or query the Gateway |
| `openclaw gateway health` | Check Gateway health |
| `openclaw gateway status` | Show Gateway service status |
| `openclaw gateway probe` | Debug-probe all Gateway targets |
| `openclaw gateway stability` | Fetch diagnostic stability recorder |
| `openclaw gateway diagnostics export` | Export diagnostics zip |
| `openclaw gateway usage-cost` | Fetch usage-cost summaries |
| `openclaw gateway call` | Low-level Gateway RPC call |
| `openclaw gateway discover` | Discover Gateways via Bonjour |
| `openclaw gateway install/start/stop/restart/uninstall` | Manage Gateway service |
| `openclaw health` | Fetch health from running Gateway |
| `openclaw hooks list` | List agent hooks |
| `openclaw hooks info` | Show hook details |
| `openclaw hooks check` | Show hook eligibility summary |
| `openclaw hooks enable` | Enable a hook |
| `openclaw hooks disable` | Disable a hook |
| `openclaw infer model run` | Run a text/model prompt |
| `openclaw infer image generate` | Generate an image |
| `openclaw infer image describe` | Describe an image file |
| `openclaw infer audio transcribe` | Transcribe audio file |
| `openclaw infer tts convert` | Synthesize speech |
| `openclaw infer video generate` | Generate a video |
| `openclaw infer video describe` | Describe a video file |
| `openclaw infer web search` | Search the web |
| `openclaw infer web fetch` | Fetch a web page |
| `openclaw infer embedding create` | Create vector embeddings |
| `openclaw logs` | Tail Gateway file logs |
| `openclaw mcp serve` | Run OpenClaw as MCP server |
| `openclaw mcp list/show/set/unset` | Manage saved MCP server definitions |
| `openclaw memory status` | Show memory plugin status |
| `openclaw memory index` | Reindex memory store |
| `openclaw memory search` | Search semantic memory |
| `openclaw memory promote` | Preview/apply memory promotions to MEMORY.md |
| `openclaw memory promote-explain` | Explain promotion score breakdown |
| `openclaw memory rem-harness` | Preview REM reflections without writing |
| `openclaw message send` | Send message on any channel |
| `openclaw message poll` | Create a poll |
| `openclaw message react` | React to a message |
| `openclaw message read` | Read messages from a channel |
| `openclaw message broadcast` | Broadcast message to multiple targets |
| `openclaw models status` | Show model/auth status |
| `openclaw models list` | List available models |
| `openclaw models set` | Set default model |
| `openclaw models auth` | Manage provider auth profiles |
| `openclaw node run` | Run headless node host (foreground) |
| `openclaw node install` | Install headless node host as service |
| `openclaw nodes list` | List paired nodes |
| `openclaw nodes approve/reject` | Approve/reject node pairing |
| `openclaw nodes invoke` | Invoke a node capability |
| `openclaw onboard` | Interactive Gateway onboarding |
| `openclaw pairing list` | List pending DM pairing requests |
| `openclaw pairing approve` | Approve a DM pairing code |
| `openclaw plugins list` | List installed plugins |
| `openclaw plugins install` | Install a plugin |
| `openclaw plugins update` | Update a plugin |
| `openclaw plugins enable/disable` | Enable or disable a plugin |
| `openclaw plugins uninstall` | Uninstall a plugin |
| `openclaw plugins inspect` | Deep-inspect a plugin |
| `openclaw plugins doctor` | Report plugin load errors |
| `openclaw plugins marketplace list` | List plugins from a marketplace |
| `openclaw proxy start` | Start local debug proxy |
| `openclaw proxy run` | Start proxy then run a child command |
| `openclaw proxy query` | Query captured traffic by preset |
| `openclaw proxy sessions` | List proxy capture sessions |
| `openclaw proxy purge` | Purge local proxy capture data |
| `openclaw qr` | Generate mobile pairing QR and setup code |
| `openclaw reset` | Reset local config/state |
| `openclaw sandbox explain` | Explain effective sandbox settings |
| `openclaw sandbox list` | List sandbox runtimes |
| `openclaw sandbox recreate` | Recreate sandbox runtimes |
| `openclaw secrets audit` | Scan for plaintext secrets and unresolved refs |
| `openclaw secrets configure` | Interactive secret provider setup |
| `openclaw secrets apply` | Apply a secrets migration plan |
| `openclaw secrets reload` | Reload Gateway runtime secret snapshot |
| `openclaw security audit` | Security audit with optional fixes |
| `openclaw sessions` | List conversation sessions |
| `openclaw sessions cleanup` | Run session maintenance |
| `openclaw setup` | Initialize config and workspace |
| `openclaw skills search` | Search ClawHub for skills |
| `openclaw skills install` | Install a skill from ClawHub |
| `openclaw skills update` | Update installed skills |
| `openclaw skills list` | List local skills |
| `openclaw skills info` | Show skill details |
| `openclaw skills check` | Check skill eligibility |
| `openclaw status` | Diagnostics for channels + sessions |
| `openclaw system event` | Enqueue a system event |
| `openclaw system heartbeat` | Control/query heartbeat |
| `openclaw system presence` | List system presence entries |
| `openclaw tasks list` | List background tasks |
| `openclaw tasks show` | Show one task |
| `openclaw tasks cancel` | Cancel a running task |
| `openclaw tasks notify` | Change task notification policy |
| `openclaw tasks audit` | Surface inconsistent/stale task records |
| `openclaw tasks maintenance` | Apply task cleanup/pruning |
| `openclaw tasks flow` | Inspect Task Flow state |
| `openclaw tui` | Open terminal UI connected to Gateway |
| `openclaw chat` | Alias for `openclaw tui --local` |
| `openclaw terminal` | Alias for `openclaw tui --local` |
| `openclaw uninstall` | Uninstall Gateway service + local data |
| `openclaw update` | Update OpenClaw |
| `openclaw update status` | Show update channel and availability |
| `openclaw update wizard` | Interactive update channel wizard |
| `openclaw voicecall` | Voice call plugin commands (if installed) |
| `openclaw webhooks gmail setup` | Configure Gmail Pub/Sub webhook |
| `openclaw webhooks gmail run` | Run Gmail watch + auto-renew loop |
| `openclaw wiki status` | Wiki vault status |
| `openclaw wiki search` | Search wiki content |
| `openclaw wiki get` | Read a wiki page |
| `openclaw wiki apply` | Apply wiki mutations |
| `openclaw wiki compile` | Rebuild wiki indexes and digests |
| `openclaw wiki lint` | Lint wiki vault |
| `openclaw wiki ingest` | Import content into wiki |
| `openclaw wiki bridge import` | Import from memory plugin (bridge mode) |
| `openclaw wiki obsidian` | Obsidian integration helpers |
FILE:references/04-channels.md
# OpenClaw Channels — Complete Reference
> Generated from official docs at https://docs.openclaw.ai/channels (all 33 pages)
---
## Table of Contents
1. [Channels Overview](#channels-overview)
2. [Channel Routing](#channel-routing)
3. [Groups](#groups)
4. [Group Messages (WhatsApp specifics)](#group-messages-whatsapp-specifics)
5. [Pairing](#pairing)
6. [Broadcast Groups](#broadcast-groups)
7. [Location Parsing](#location-parsing)
8. [QA Channel (Internal Testing)](#qa-channel-internal-testing)
9. [BlueBubbles (iMessage - Recommended)](#bluebubbles-imessage---recommended)
10. [Discord](#discord)
11. [Feishu / Lark](#feishu--lark)
12. [Google Chat](#google-chat)
13. [iMessage (Legacy)](#imessage-legacy)
14. [IRC](#irc)
15. [LINE](#line)
16. [Matrix](#matrix)
17. [Matrix Push Rules](#matrix-push-rules)
18. [Mattermost](#mattermost)
19. [Microsoft Teams](#microsoft-teams)
20. [Nextcloud Talk](#nextcloud-talk)
21. [Nostr](#nostr)
22. [QQ Bot](#qq-bot)
23. [Signal](#signal)
24. [Slack](#slack)
25. [Synology Chat](#synology-chat)
26. [Telegram](#telegram)
27. [Tlon (Urbit)](#tlon-urbit)
28. [Twitch](#twitch)
29. [WeChat](#wechat)
30. [WhatsApp](#whatsapp)
31. [Zalo (Bot API)](#zalo-bot-api)
32. [Zalo Personal](#zalo-personal)
33. [Troubleshooting](#troubleshooting)
34. [Feature Comparison Table](#feature-comparison-table)
---
## Channels Overview
OpenClaw connects to any chat app you already use via the **Gateway**. Text is supported everywhere; media and reactions vary by channel. Multiple channels can run simultaneously.
### Key delivery notes
- Telegram replies with markdown image syntax `` are converted to media replies on the outbound path.
- Slack multi-person DMs route as group chats (group policy applies).
- WhatsApp setup is install-on-demand; the Gateway loads the runtime only when the channel is active.
### Quickest setup
- **Telegram** — just a bot token, no QR required.
- **WhatsApp** — requires QR pairing and stores state on disk.
---
## Channel Routing
OpenClaw routes replies **back to the channel where the message came from**. Routing is deterministic and model-independent.
### Key terms
| Term | Meaning |
|------|---------|
| **Channel** | `telegram`, `whatsapp`, `discord`, `slack`, `signal`, `irc`, `googlechat`, `imessage`, `line`, plus plugin channels |
| **AccountId** | Per-channel account instance when multiple accounts are supported |
| **AgentId** | An isolated workspace + session store ("brain") |
| **SessionKey** | Bucket key for context storage and concurrency control |
### Session key shapes
```
agent:<agentId>:<mainKey> # DMs → main session (default)
agent:<agentId>:<channel>:group:<id> # Groups
agent:<agentId>:<channel>:channel:<id> # Rooms/channels
agent:<agentId>:discord:channel:123456:thread:987654 # Threads
agent:<agentId>:telegram:group:-100123:topic:42 # Telegram forum topics
```
### Routing rules (priority order)
1. Exact peer match (`bindings` with `peer.kind` + `peer.id`)
2. Parent peer match (thread inheritance)
3. Guild + roles match (Discord)
4. Guild match (Discord via `guildId`)
5. Team match (Slack via `teamId`)
6. Account match (`accountId`)
7. Channel match (any account on that channel)
8. Default agent (`agents.list[].default`, else first entry, fallback to `main`)
### Bindings config example
```json5
{
agents: {
list: [{ id: "support", name: "Support", workspace: "~/.openclaw/workspace-support" }],
},
bindings: [
{ match: { channel: "slack", teamId: "T123" }, agentId: "support" },
{ match: { channel: "telegram", peer: { kind: "group", id: "-100123" } }, agentId: "support" },
],
}
```
### Broadcast groups (run multiple agents per peer)
```json5
{
broadcast: {
strategy: "parallel",
"[email protected]": ["alfred", "baerbel"],
"+15555550123": ["support", "logger"],
},
}
```
`broadcast` takes priority over `bindings`.
---
## Groups
OpenClaw handles group chats consistently across: Discord, iMessage, Matrix, Microsoft Teams, Signal, Slack, Telegram, WhatsApp, Zalo.
### Default behavior
- Groups are restricted (`groupPolicy: "allowlist"`)
- Replies require a mention unless overridden
- Heartbeats are skipped for group sessions
### Group message evaluation flow
```
groupPolicy? disabled → drop
groupPolicy? allowlist → group allowed? no → drop
requireMention? yes → mentioned? no → store for context only
otherwise → reply
```
### Group policy values
| Policy | Behavior |
|--------|----------|
| `"open"` | Groups bypass allowlists; mention-gating still applies |
| `"disabled"` | Block all group messages entirely |
| `"allowlist"` | Only allow groups/rooms matching the configured allowlist |
### Mention gating
Replying to or quoting a bot message counts as an implicit mention on channels that expose reply metadata (Telegram, WhatsApp, Slack, Discord, MS Teams, ZaloUser).
```json5
{
channels: {
whatsapp: {
groups: {
"*": { requireMention: true },
"[email protected]": { requireMention: false },
},
},
},
agents: {
list: [{
id: "main",
groupChat: {
mentionPatterns: ["@openclaw", "openclaw", "\\+15555550123"],
historyLimit: 50,
},
}],
},
}
```
### Group/channel tool restrictions
```json5
{
channels: {
telegram: {
groups: {
"*": { tools: { deny: ["exec"] } },
"-1001234567890": {
tools: { deny: ["exec", "read", "write"] },
toolsBySender: {
"id:123456789": { alsoAllow: ["exec"] },
},
},
},
},
},
}
```
`toolsBySender` key prefixes: `id:`, `e164:`, `username:`, `name:`, or `"*"` wildcard.
### Session keys
- Group sessions: `agent:<agentId>:<channel>:group:<id>`
- Telegram forum topics add `:topic:<threadId>`
- DMs use main session (or per-sender if configured)
### Per-group system prompts (where supported)
Under `channels.<channel>.groups.<id>.systemPrompt`.
### Context visibility and allowlists
Two separate controls for group safety:
- **Trigger authorization:** who can trigger the agent (`groupPolicy`, `groups`, `groupAllowFrom`)
- **Context visibility:** what supplemental context is injected (reply text, quotes, thread history, forwarded metadata)
By default, allowlists decide who can trigger actions, not a universal redaction boundary. Current behavior is channel-specific:
- Some channels already filter supplemental context by sender (Slack thread seeding, Matrix reply/thread lookups)
- Others pass quote/reply/forward context as received
**Hardening direction (planned):**
- `contextVisibility: "all"` (default) — as-received behavior
- `contextVisibility: "allowlist"` — filter supplemental context to allowlisted senders
- `contextVisibility: "allowlist_quote"` — allowlist + one explicit quote/reply exception
### Pattern: personal DMs + public groups (single agent)
In single-agent mode, DMs land in the **main** session key, groups use **non-main** keys. With `sandbox.mode: "non-main"`, groups run sandboxed while DMs stay on-host.
```json5
{
agents: {
defaults: {
sandbox: {
mode: "non-main",
scope: "session",
workspaceAccess: "none",
},
},
},
tools: {
sandbox: {
tools: {
allow: ["group:messaging", "group:sessions"],
deny: ["group:runtime", "group:fs", "group:ui", "nodes", "cron", "gateway"],
},
},
},
}
```
To allow groups to see specific folders: keep `workspaceAccess: "none"` and mount allowlisted paths via `docker.binds: ["/host/path:/data:ro"]`.
For truly separate workspaces/personas, use a second agent + bindings (see Multi-Agent Routing).
---
## Group Messages (WhatsApp specifics)
### Activation modes
- `mention` (default): requires ping via `mentionedJids`, safe regex patterns, or bot's E.164 anywhere in text
- `always`: wakes on every message; agent returns `NO_REPLY` if nothing meaningful to say
### Config example
```json5
{
channels: {
whatsapp: {
groups: {
"*": { requireMention: true },
},
},
},
agents: {
list: [{
id: "main",
groupChat: {
historyLimit: 50,
mentionPatterns: ["@?openclaw", "\\+?15555550123"],
},
}],
},
}
```
### Context injection
Pending (unprocessed) group messages (default 50) are prefixed as:
```
[Chat messages since your last reply - for context]
[Current message - respond to this]
```
Already-processed messages are not re-injected.
### Owner-only activation command
- `/activation mention`
- `/activation always`
Only the owner number (from `allowFrom` or self E.164) can change this.
---
## Pairing
Pairing is OpenClaw's explicit **owner approval** step used for:
1. DM pairing (who can talk to the bot)
2. Node pairing (which devices/nodes can join the gateway network)
### DM pairing
When `dmPolicy: "pairing"` is set, unknown senders receive an 8-character code (uppercase, no ambiguous chars), and their messages are **not processed** until approved.
- Codes expire after **1 hour**
- Max **3 pending requests per channel** by default
```bash
openclaw pairing list telegram
openclaw pairing approve telegram <CODE>
```
Supported channels for pairing: `bluebubbles`, `discord`, `feishu`, `googlechat`, `imessage`, `irc`, `line`, `matrix`, `mattermost`, `msteams`, `nextcloud-talk`, `nostr`, `openclaw-weixin`, `signal`, `slack`, `synology-chat`, `telegram`, `twitch`, `whatsapp`, `zalo`, `zalouser`.
### Pairing state storage
```
~/.openclaw/credentials/<channel>-pairing.json # Pending
~/.openclaw/credentials/<channel>-allowFrom.json # Approved (default account)
~/.openclaw/credentials/<channel>-<accountId>-allowFrom.json # Named accounts
```
> **Important:** DM pairing approval does NOT grant group access. Group sender authorization is separate and requires explicit `groupAllowFrom` config.
### Node/device pairing (iOS/Android/macOS)
```bash
openclaw devices list
openclaw devices approve <requestId>
openclaw devices reject <requestId>
```
Via Telegram: `/pair` → scan QR in app → `/pair pending` → `/pair approve <requestId>`
---
## Broadcast Groups
**Status:** Experimental (added 2026.1.9). Currently **WhatsApp only**.
Broadcast Groups enable multiple agents to process and respond to the same message simultaneously in a single WhatsApp group or DM.
### Key behaviors
- Evaluated after channel allowlists and group activation rules
- Each agent has isolated session key, conversation history, workspace, tool access, and memory
- Shared: group context buffer (recent group messages for context)
- Broadcast takes priority over bindings
### Configuration
```json5
{
"broadcast": {
"strategy": "parallel", // or "sequential"
"[email protected]": ["code-reviewer", "security-auditor", "docs-generator"],
"[email protected]": ["support-en", "support-de"],
"+15555550123": ["assistant", "logger"]
}
}
```
### Strategies
- `"parallel"` (default): all agents process simultaneously
- `"sequential"`: agents process in array order
### TypeScript schema
```typescript
interface OpenClawConfig {
broadcast?: {
strategy?: "parallel" | "sequential";
[peerId: string]: string[];
};
}
```
### Limitations
1. No hard agent limit, but 10+ agents may be slow
2. Agents don't see each other's responses (by design)
3. Parallel responses may arrive in any order
4. All agents count toward WhatsApp rate limits
### Troubleshooting
```bash
tail -f ~/.openclaw/logs/gateway.log | grep broadcast
```
---
## Location Parsing
OpenClaw normalizes shared locations from chat channels into terse coordinate text + structured context fields.
### Supported channels
- **Telegram** (location pins + venues + live locations)
- **WhatsApp** (locationMessage + liveLocationMessage)
- **Matrix** (m.location with geo_uri)
### Text rendering
```
📍 48.858844, 2.294351 ±12m # Pin
🛰 Live location: 48.858844, 2.294351 ±12m # Live share
```
Labels, addresses, and captions are rendered as fenced untrusted metadata JSON (not inline).
### Context fields added
| Field | Type |
|-------|------|
| `LocationLat` | number |
| `LocationLon` | number |
| `LocationAccuracy` | number (meters, optional) |
| `LocationName` | string (optional) |
| `LocationAddress` | string (optional) |
| `LocationSource` | `pin \| place \| live` |
| `LocationIsLive` | boolean |
| `LocationCaption` | string (optional) |
---
## QA Channel (Internal Testing)
`qa-channel` is a **bundled synthetic message transport for automated OpenClaw QA**. Not a production channel.
### Features
- Slack-class target grammar: `dm:<user>`, `channel:<room>`, `thread:<room>/<thread>`
- HTTP-backed synthetic bus for inbound injection, outbound capture, thread creation, reactions, edits, deletes, search
- Bundled host-side self-check runner (Markdown report output)
### Config
```json
{
"channels": {
"qa-channel": {
"baseUrl": "http://127.0.0.1:43123",
"botUserId": "openclaw",
"botDisplayName": "OpenClaw QA",
"allowFrom": ["*"],
"pollTimeoutMs": 1000
}
}
}
```
### Runner commands
```bash
pnpm qa:e2e # Run deterministic self-check + Markdown report
pnpm qa:lab:up # Start QA Lab Docker stack + browser UI
pnpm openclaw qa suite # Full QA suite
```
---
## BlueBubbles (iMessage - Recommended)
**Status:** Bundled plugin. Recommended for iMessage (over legacy `imsg`).
BlueBubbles runs on macOS via the BlueBubbles helper app and communicates via REST API.
### What it is
- Talks to BlueBubbles macOS server over HTTP
- Incoming messages via webhooks; outgoing via REST
- Supports: edit, unsend, reply threading, message effects, group management, tapbacks
- Tested on macOS Sequoia (15); edit broken on macOS Tahoe (26)
### Quick setup
```json5
{
channels: {
bluebubbles: {
enabled: true,
serverUrl: "http://192.168.1.100:1234",
password: "example-password",
webhookPath: "/bluebubbles-webhook",
},
},
}
```
Webhook URL: `https://your-gateway-host:3000/bluebubbles-webhook?password=<password>`
Or via CLI:
```bash
openclaw onboard
openclaw channels add bluebubbles --http-url http://192.168.1.100:1234 --password <password>
```
### Access control
DMs:
- Default: `dmPolicy = "pairing"`
- `openclaw pairing list bluebubbles`
- `openclaw pairing approve bluebubbles <CODE>`
Groups:
- `channels.bluebubbles.groupPolicy = open | allowlist | disabled` (default: `allowlist`)
- `channels.bluebubbles.groupAllowFrom`
### Advanced actions config
```json5
{
channels: {
bluebubbles: {
actions: {
reactions: true, // tapbacks (default: true)
edit: true, // edit sent messages (macOS 13+, broken on macOS 26 Tahoe)
unsend: true, // unsend messages (macOS 13+)
reply: true, // reply threading by message GUID
sendWithEffect: true, // message effects (slam, loud, etc.)
renameGroup: true, // rename group chats
setGroupIcon: true, // set group icon (flaky on macOS 26 Tahoe)
addParticipant: true,
removeParticipant: true,
leaveGroup: true,
sendAttachment: true,
},
},
},
}
```
### Available actions
| Action | Params | Notes |
|--------|--------|-------|
| `react` | `messageId`, `emoji`, `remove` | iMessage tapbacks: love/like/dislike/laugh/emphasize/question; unknown emoji fallbacks to `love` |
| `edit` | `messageId`, `text` | macOS 13+ only |
| `unsend` | `messageId` | macOS 13+ only |
| `reply` | `messageId`, `text`, `to` | Requires Private API |
| `sendWithEffect` | `text`, `to`, `effectId` | |
| `renameGroup` | `chatGuid`, `displayName` | |
| `setGroupIcon` | `chatGuid`, `media` | Flaky on Tahoe |
| `addParticipant` | `chatGuid`, `address` | |
| `removeParticipant` | `chatGuid`, `address` | |
| `leaveGroup` | `chatGuid` | |
| `upload-file` | `to`, `buffer`, `filename`, `asVoice` | Voice memos: `asVoice: true` with MP3 or CAF |
### Message IDs
- Short IDs (e.g., `1`, `2`) expire on restart/cache eviction
- Full IDs via `MessageSidFull` / `ReplyToIdFull` are durable
### Typing + read receipts
```json5
{
channels: {
bluebubbles: {
sendReadReceipts: false, // disable read receipts
},
},
}
```
> **WhatsApp read receipts:** Read receipts are enabled by default for accepted inbound WhatsApp messages. Disable globally with `channels.whatsapp.sendReadReceipts: false`. Per-account override: `channels.whatsapp.accounts.<id>.sendReadReceipts: false`. Self-chat turns skip read receipts even when globally enabled.
### Coalescing split-send DMs
When user types command + URL, Apple splits into 2 webhooks ~1s apart:
```json5
{
channels: {
bluebubbles: {
coalesceSameSenderDms: true, // 2500ms debounce window
},
},
}
```
### Per-group config
```json5
{
channels: {
bluebubbles: {
groups: {
"*": { requireMention: true },
"iMessage;-;chat123": {
requireMention: false,
systemPrompt: "Keep responses under 3 sentences.",
},
},
},
},
}
```
### Contact name enrichment
```json5
{
channels: {
bluebubbles: {
enrichGroupParticipantsFromContacts: true, // default: false
},
},
}
```
### ACP bindings
```json5
{
bindings: [{
type: "acp",
agentId: "codex",
match: {
channel: "bluebubbles",
accountId: "default",
peer: { kind: "dm", id: "+15555550123" },
},
acp: { label: "codex-imessage" },
}],
}
```
### Keeping Messages.app alive (headless)
Create `~/Scripts/poke-messages.scpt` + `~/Library/LaunchAgents/com.user.poke-messages.plist` to touch Messages every 300 seconds.
### Limitations/gotchas
- Edit broken on macOS 26 Tahoe
- Group icon updates may not sync on macOS 26 Tahoe
- Webhook auth always required; password checked before parsing body
- Tapbacks outside iMessage native set fall back to `love`
---
## Discord
**Status:** Ready for DMs and guild channels via the official Discord gateway.
### Quick setup
1. Create application + bot at https://discord.com/developers/applications
2. Enable: Message Content Intent, Server Members Intent (recommended)
3. Reset/copy bot token
4. Invite bot with OAuth2 scopes: `bot`, `applications.commands`
5. Set bot token securely:
```bash
export DISCORD_BOT_TOKEN="YOUR_BOT_TOKEN"
openclaw config set channels.discord.token --ref-provider default --ref-source env --ref-id DISCORD_BOT_TOKEN
openclaw config set channels.discord.enabled true --strict-json
openclaw gateway
```
### Config
```json5
{
channels: {
discord: {
enabled: true,
token: {
source: "env",
provider: "default",
id: "DISCORD_BOT_TOKEN",
},
},
},
}
```
Env fallback: `DISCORD_BOT_TOKEN=...` (default account only)
### Access control
DM policy (`channels.discord.dmPolicy`):
- `pairing` (default)
- `allowlist`
- `open` (requires `allowFrom: ["*"]`)
- `disabled`
Guild policy (`channels.discord.groupPolicy`):
- `open`, `allowlist` (default secure baseline), `disabled`
Guild config:
```json5
{
channels: {
discord: {
groupPolicy: "allowlist",
guilds: {
"123456789012345678": {
requireMention: true,
ignoreOtherMentions: true, // drops messages that mention another user/role but not the bot (excluding @everyone/@here)
users: ["987654321098765432"],
roles: ["123456789012345678"],
channels: {
general: { allow: true },
help: { allow: true, requireMention: true },
},
},
},
},
},
}
```
### Role-based agent routing
```json5
{
bindings: [
{
agentId: "opus",
match: {
channel: "discord",
guildId: "123456789012345678",
roles: ["111111111111111111"],
},
},
{
agentId: "sonnet",
match: { channel: "discord", guildId: "123456789012345678" },
},
],
}
```
### Interactive components (Discord Components v2)
Supported blocks: `text`, `section`, `separator`, `actions`, `media-gallery`, `file`
- Action rows: up to 5 buttons or 1 select menu
- Select types: `string`, `user`, `role`, `mentionable`, `channel`
- Modal forms: up to 5 fields (`text`, `checkbox`, `radio`, `select`, `role-select`, `user-select`)
- Set `components.reusable: true` for multi-use buttons
- `allowedUsers` on buttons restricts who can click (ephemeral denial for others)
- Button `style` values: `success`, `danger`, and `primary` (the `message` tool also supports `default`)
```json5
{
channel: "discord",
action: "send",
to: "channel:123456789012345678",
message: "Optional fallback text",
components: {
reusable: true,
text: "Choose a path",
blocks: [
{
type: "actions",
buttons: [
{ label: "Approve", style: "success", allowedUsers: ["123456789012345678"] },
{ label: "Decline", style: "danger" },
],
},
],
},
}
```
### Forum channels
- Send to forum parent to auto-create a thread (title = first non-empty line)
- Or use `openclaw message thread create --channel discord --target channel:<forumId> --thread-name "Title" --message "Body"`
- Forum parents do NOT accept Discord components
### Native slash commands
- `commands.native` defaults to `"auto"` (enabled for Discord)
- `/model` and `/models` open interactive model picker (ephemeral, invoking user only)
- `commands.native=false` clears previously registered native commands
### Reply tags
- `[[reply_to_current]]`
- `[[reply_to:<id>]]`
- Controlled by `channels.discord.replyToMode`
### Session model
- DMs share agent main session (default `session.dmScope=main`)
- Guild channels: `agent:<agentId>:discord:channel:<channelId>`
- Native slash commands: `agent:<agentId>:discord:slash:<userId>`
- Threads: append `:thread:<threadId>`
- Group DMs: ignored by default (`dm.groupEnabled=false`); use `dm.groupChannels` to allowlist specific group DM channel IDs or slugs
### Gotchas
- If `DISCORD_BOT_TOKEN` set without `channels.discord` block, runtime fallback is `groupPolicy="allowlist"` (with log warning)
- `dangerouslyAllowNameMatching: true` needed for name/tag matching (IDs safer)
- Bare numeric IDs are ambiguous and rejected without explicit `user:`/`channel:` prefix
---
## Feishu / Lark
**Status:** Production-ready for bot DMs + group chats. Bundled plugin.
**Requires OpenClaw 2026.4.24+**
### Quick setup
```bash
openclaw channels login --channel feishu # Scan QR code to auto-create bot
openclaw gateway restart
```
### Access control
DM policy (`channels.feishu.dmPolicy`): `pairing | allowlist | open | disabled`
Group policy:
```json5
{
channels: {
feishu: {
groupPolicy: "allowlist", // open | allowlist | disabled
groupAllowFrom: ["oc_xxx", "oc_yyy"],
requireMention: true, // default: true
groups: {
oc_xxx: {
allowFrom: ["ou_user1", "ou_user2"],
requireMention: false, // per-group override
},
},
},
},
}
```
### ID formats
- Group IDs: `oc_xxx` (found in group Settings)
- User IDs: `ou_xxx` (open_ids, visible in logs or pairing list)
### Streaming
```json5
{
channels: {
feishu: {
streaming: true, // enable streaming card output (default: true)
blockStreaming: true, // block-level streaming (default: true)
},
},
}
```
### Quota optimization
```json5
{
channels: {
feishu: {
typingIndicator: false, // skip typing reaction calls
resolveSenderNames: false, // skip sender profile lookups
},
},
}
```
### Multi-account
```json5
{
channels: {
feishu: {
defaultAccount: "main",
accounts: {
main: { appId: "cli_xxx", appSecret: "xxx", name: "Primary bot" },
backup: { appId: "cli_yyy", appSecret: "yyy", enabled: false },
},
},
},
}
```
### ACP bindings
```json5
{
bindings: [{
type: "acp",
agentId: "codex",
match: {
channel: "feishu",
accountId: "default",
peer: { kind: "direct", id: "ou_1234567890" },
},
}],
}
```
In-chat ACP: `/acp spawn codex --thread here`
### Full config reference
| Setting | Description | Default |
|---------|-------------|---------|
| `channels.feishu.enabled` | Enable/disable | `true` |
| `channels.feishu.domain` | `feishu` or `lark` | `feishu` |
| `channels.feishu.connectionMode` | `websocket` or `webhook` | `websocket` |
| `channels.feishu.defaultAccount` | Default outbound account | `default` |
| `channels.feishu.verificationToken` | Required for webhook mode | — |
| `channels.feishu.encryptKey` | Required for webhook mode | — |
| `channels.feishu.webhookPath` | Webhook route | `/feishu/events` |
| `channels.feishu.dmPolicy` | DM policy | `allowlist` |
| `channels.feishu.groupPolicy` | Group policy | `allowlist` |
| `channels.feishu.requireMention` | Require @mention in groups | `true` |
| `channels.feishu.textChunkLimit` | Message chunk size | `2000` |
| `channels.feishu.mediaMaxMb` | Media size limit | `30` |
| `channels.feishu.streaming` | Streaming card output | `true` |
| `channels.feishu.typingIndicator` | Send typing reactions | `true` |
| `channels.feishu.resolveSenderNames` | Resolve sender names | `true` |
### Supported message types
Receive: ✅ Text, Rich text, Images, Files, Audio, Video, Stickers
Send: ✅ Text, Images, Files, Audio, Video, Interactive cards (streaming updates)
Threads: ✅ Inline replies, Thread replies
### Troubleshooting
- Bot not in group → add bot to group
- No @mention → mention required by default
- Not receiving → verify published in Feishu Open Platform, `im.message.receive_v1` event, persistent connection (WebSocket)
---
## Google Chat
**Status:** Ready for DMs + spaces via HTTP webhook.
### Setup
1. Create Google Cloud project + enable Google Chat API
2. Create Service Account → download JSON key
3. Create Google Chat app in Cloud Console with HTTP endpoint URL pointing to your gateway's public URL + `/googlechat`
4. Set status to "Live - available to users"
5. Configure OpenClaw:
```json5
{
channels: {
googlechat: {
enabled: true,
serviceAccountFile: "/path/to/service-account.json",
audienceType: "app-url", // or "project-number"
audience: "https://gateway.example.com/googlechat",
webhookPath: "/googlechat",
botUser: "users/1234567890", // optional, helps mention detection
dm: {
policy: "pairing",
allowFrom: ["users/1234567890"],
},
groupPolicy: "allowlist",
groups: {
"spaces/AAAA": {
allow: true,
requireMention: true,
users: ["users/1234567890"],
systemPrompt: "Short answers only.",
},
},
actions: { reactions: true },
typingIndicator: "message", // none | message | reaction
mediaMaxMb: 20,
},
},
}
```
### Targets
- DMs: `users/<userId>` (recommended)
- Spaces: `spaces/<spaceId>`
- Email matching: only with `dangerouslyAllowNameMatching: true`
### Public URL options
1. **Tailscale Funnel** (recommended): expose only `/googlechat` publicly
2. **Caddy reverse proxy**: `reverse_proxy /googlechat* localhost:18789`
3. **Cloudflare Tunnel**: route only `/googlechat` path
### Auth verification
Google Chat sends `Authorization: Bearer <token>`. OpenClaw verifies via `audienceType` + `audience`.
### Session keys
- DMs: `agent:<agentId>:googlechat:direct:<spaceId>`
- Spaces: `agent:<agentId>:googlechat:group:<spaceId>`
### Troubleshooting
- **405 Method Not Allowed**: channel not configured, plugin not enabled, or gateway not restarted
- Check: `openclaw channels status --probe`
- Missing mentions: set `botUser` to app's user resource name
---
## iMessage (Legacy)
> **Warning:** Deprecated. Use [BlueBubbles](#bluebubbles-imessage---recommended) for new setups. The `imsg` integration may be removed in a future release.
### What it is
- Gateway spawns `imsg rpc` and communicates over JSON-RPC on stdio
- No separate daemon/port
### Quick setup
```bash
brew install steipete/tap/imsg
imsg rpc --help
```
```json5
{
channels: {
imessage: {
enabled: true,
cliPath: "/usr/local/bin/imsg",
dbPath: "/Users/user/Library/Messages/chat.db",
},
},
}
```
### Requirements
- Messages signed in on Mac
- Full Disk Access for OpenClaw/imsg process
- Automation permission
### DM policy
`channels.imessage.dmPolicy`: `pairing` (default) | `allowlist` | `open` | `disabled`
### Group policy
`channels.imessage.groupPolicy`: `allowlist` (default) | `open` | `disabled`
### Target formats
- `chat_id:123` (recommended, stable)
- `chat_guid:...`
- `chat_identifier:...`
- `imessage:+1555...`
- `[email protected]`
### Remote Mac over SSH
```bash
#!/usr/bin/env bash
exec ssh -T gateway-host imsg "$@"
```
```json5
{
channels: {
imessage: {
enabled: true,
cliPath: "~/.openclaw/scripts/imsg-ssh",
remoteHost: "user@gateway-host",
includeAttachments: true,
},
},
}
```
### ACP bindings supported
`match.peer.id` accepts: normalized handle, `chat_id:<id>`, `chat_guid:<guid>`, `chat_identifier:<identifier>`
---
## IRC
**Status:** Bundled plugin. Classic IRC servers; channels + DMs with pairing/allowlist controls.
### Quick setup
```json5
{
channels: {
irc: {
enabled: true,
host: "irc.example.com",
port: 6697,
tls: true,
nick: "openclaw-bot",
channels: ["#openclaw"],
},
},
}
```
### Security defaults
- `dmPolicy`: defaults to `"pairing"`
- `groupPolicy`: defaults to `"allowlist"`
- TLS recommended (`tls: true`)
### Two-gate access control
1. **Channel access** (`groupPolicy` + `groups`): whether bot accepts messages from a channel
2. **Sender access** (`groupAllowFrom` / `groups["#channel"].allowFrom`): who can trigger inside that channel
### Config example (allow anyone in a channel, no mention required)
```json5
{
channels: {
irc: {
groupPolicy: "allowlist",
groups: {
"#tuirc-dev": {
requireMention: false,
allowFrom: ["*"],
tools: {
deny: ["group:runtime", "group:fs", "gateway", "nodes", "cron", "browser"],
},
toolsBySender: {
"*": { deny: ["group:runtime", "group:fs"] },
"id:eigen": { deny: ["gateway", "nodes", "cron"] },
},
},
},
},
},
}
```
### NickServ
```json5
{
channels: {
irc: {
nickserv: {
enabled: true,
service: "NickServ",
password: "your-nickserv-password",
register: true, // one-time; disable after registration
registerEmail: "[email protected]",
},
},
},
}
```
### Environment variables
`IRC_HOST`, `IRC_PORT`, `IRC_TLS`, `IRC_NICK`, `IRC_USERNAME`, `IRC_REALNAME`, `IRC_PASSWORD`, `IRC_CHANNELS` (comma-separated), `IRC_NICKSERV_PASSWORD`, `IRC_NICKSERV_REGISTER_EMAIL`
> Note: `IRC_HOST` cannot be set from workspace `.env`
### Common gotcha
`allowFrom` is for DMs, not channels. Use `groupAllowFrom` or `groups["#channel"].allowFrom` for channel sender access.
---
## LINE
**Status:** Bundled plugin. DMs, group chats, media, locations, Flex messages, template messages, quick replies supported.
### Setup
1. Create LINE Developers account → Provider → Messaging API channel
2. Copy Channel access token + Channel secret
3. Enable "Use webhook"
4. Set webhook URL: `https://gateway-host/line/webhook`
### Config
```json5
{
channels: {
line: {
enabled: true,
channelAccessToken: "LINE_CHANNEL_ACCESS_TOKEN",
channelSecret: "LINE_CHANNEL_SECRET",
dmPolicy: "pairing",
},
},
}
```
Env vars: `LINE_CHANNEL_ACCESS_TOKEN`, `LINE_CHANNEL_SECRET`
File-backed:
```json5
{
channels: {
line: {
tokenFile: "/path/to/line-token.txt",
secretFile: "/path/to/line-secret.txt",
},
},
}
```
### Access control
- `dmPolicy`: `pairing | allowlist | open | disabled`
- `allowFrom`: LINE user IDs (format: `U` + 32 hex chars)
- `groupPolicy`: `allowlist | open | disabled`
- `groupAllowFrom`: user IDs for groups
- Group IDs: `C` + 32 hex chars; Room IDs: `R` + 32 hex chars
### Rich messages via `channelData.line`
```json5
{
text: "Here you go",
channelData: {
line: {
quickReplies: ["Status", "Help"],
location: { title: "Office", address: "123 Main St", latitude: 35.681236, longitude: 139.767125 },
flexMessage: { altText: "Status card", contents: { /* Flex payload */ } },
templateMessage: {
type: "confirm",
text: "Proceed?",
confirmLabel: "Yes",
confirmData: "yes",
cancelLabel: "No",
cancelData: "no",
},
},
},
}
```
### Message behavior
- Text chunked at 5000 characters
- Markdown stripped; code blocks/tables → Flex cards
- Streaming responses buffered; LINE receives full chunks with loading animation
- Media cap: `mediaMaxMb` (default 10)
- Outbound media URLs must be public HTTPS; loopback/private addresses rejected
### Outbound media
- Images: LINE image messages with auto preview
- Videos: explicit preview + content-type handling
- Audio: LINE audio messages
### ACP support
`/acp spawn <agent> --bind here` to bind LINE chat to ACP session
### Limitations
- No reactions
- No threads
---
## Matrix
**Status:** Bundled plugin. Supports DMs, rooms, threads, media, reactions, polls, location, E2EE.
### Setup
1. Create Matrix account on your homeserver
2. Configure `channels.matrix` with `homeserver` + `accessToken` (or `userId` + `password`)
3. Restart gateway; invite bot to rooms
**Important:** `channels.matrix.autoJoin` defaults to `off`. Bot won't join invited rooms without it.
```json5
{
channels: {
matrix: {
enabled: true,
homeserver: "https://matrix.example.org",
accessToken: "syt_xxx",
encryption: true,
dm: {
policy: "pairing",
sessionScope: "per-room",
threadReplies: "off",
},
groupPolicy: "allowlist",
groupAllowFrom: ["@admin:example.org"],
groups: {
"!roomid:example.org": { requireMention: true },
},
autoJoin: "allowlist", // off | allowlist | always
autoJoinAllowlist: ["!roomid:example.org"],
threadReplies: "inbound",
replyToMode: "off",
streaming: "partial", // off | partial | quiet
},
},
}
```
### Environment variables
- `MATRIX_HOMESERVER`, `MATRIX_ACCESS_TOKEN`, `MATRIX_USER_ID`, `MATRIX_PASSWORD`, `MATRIX_DEVICE_ID`, `MATRIX_DEVICE_NAME`
- Non-default accounts: `MATRIX_<ACCOUNT_ID>_*` (punctuation escaped, e.g., `-` → `_X2D_`)
### Credentials cache
`~/.openclaw/credentials/matrix/credentials.json` (default account)
`~/.openclaw/credentials/matrix/credentials-<account>.json` (named accounts)
### Streaming previews
| Setting | Behavior |
|---------|----------|
| `streaming: "off"` (default) | Send complete reply once |
| `streaming: "partial"` | Live preview message, edited in place while generating |
| `streaming: "quiet"` | Quiet draft, finalized with custom push rule; only notifies once done |
| `blockStreaming: true` | Keep completed blocks as separate messages |
### Bot-to-bot rooms
```json5
{
channels: {
matrix: {
allowBots: "mentions", // true | "mentions"
},
},
}
```
### E2EE verification commands
```bash
openclaw matrix verify status [--verbose] [--json]
openclaw matrix verify bootstrap [--force-reset-cross-signing]
openclaw matrix verify device "<recovery-key>"
openclaw matrix verify self
openclaw matrix verify backup status
openclaw matrix verify backup restore
openclaw matrix verify backup reset --yes
openclaw matrix verify accept|start|sas|confirm-sas|cancel <id>
openclaw matrix devices list --account <id>
```
### E2EE in encrypted rooms
- Outbound image events use `thumbnail_file` (encrypted) in E2EE rooms
- Unencrypted rooms use plain `thumbnail_url`
- Auto-detected; no config needed
### Startup behavior with encryption
- `startupVerification` defaults to `"if-unverified"`: requests self-verification on startup for unverified devices
- Tune with `startupVerificationCooldownHours`
- Disable with `startupVerification: "off"`
### Gotchas
- `autoJoin` applies to all invites (can't distinguish DM vs group at invite time)
- Room allowlist entries must use stable IDs: `!roomId:server` or `#alias:server`
- Cross-signing verified ≠ locally trusted alone
---
## Matrix Push Rules
For `streaming: "quiet"` mode, install per-user push rules that match the `com.openclaw.finalized_preview` content flag.
### Prerequisites
- Recipient user access token
- Bot user MXID
- Working pushers already configured for recipient
### Steps
1. Configure quiet previews:
```json5
{ channels: { matrix: { streaming: "quiet" } } }
```
2. Get recipient's access token
3. Verify pushers exist
4. Install override push rule:
```bash
curl -X PUT \
"https://matrix.example.org/_matrix/client/v3/pushrules/global/override/openclaw-finalized-preview-botname" \
-H "Authorization: Bearer $USER_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"conditions": [
{ "kind": "event_match", "key": "type", "pattern": "m.room.message" },
{ "kind": "event_property_is", "key": "content.m\\.relates_to.rel_type", "value": "m.replace" },
{ "kind": "event_property_is", "key": "content.com\\.openclaw\\.finalized_preview", "value": true },
{ "kind": "event_match", "key": "sender", "pattern": "@bot:example.org" }
],
"actions": ["notify", {"set_tweak": "sound", "value": "default"}, {"set_tweak": "highlight", "value": false}]
}'
```
5. Verify + test
For multiple bots, create one rule per bot with distinct rule ID and sender match.
---
## Mattermost
**Status:** Bundled plugin. Channels, groups, and DMs supported via bot token + WebSocket events.
### Quick setup
```json5
{
channels: {
mattermost: {
enabled: true,
botToken: "mm-token",
baseUrl: "https://chat.example.com",
dmPolicy: "pairing",
},
},
}
```
Env vars: `MATTERMOST_BOT_TOKEN`, `MATTERMOST_URL` (default account only)
### Chat modes
| Mode | Behavior |
|------|----------|
| `oncall` (default) | Respond only when @mentioned |
| `onmessage` | Respond to every channel message |
| `onchar` | Respond when message starts with trigger prefix |
```json5
{
channels: {
mattermost: {
chatmode: "onchar",
oncharPrefixes: [">", "!"],
},
},
}
```
### Threading
`channels.mattermost.replyToMode`: `off` (default) | `first` | `all`
- `first`/`all`: start thread under triggering post (thread-scoped sessions)
- DMs: always non-threaded
### Native slash commands
```json5
{
channels: {
mattermost: {
commands: {
native: true,
nativeSkills: true,
callbackPath: "/api/channels/mattermost/command",
callbackUrl: "https://gateway.example.com/api/channels/mattermost/command",
},
},
},
}
```
Reachability: callback must be reachable from Mattermost server. Check: `curl https://<gateway-host>/api/channels/mattermost/command` should return `405`.
### Reactions
```
message action=react channel=mattermost target=channel:<channelId> messageId=<postId> emoji=thumbsup
message action=react channel=mattermost target=channel:<channelId> messageId=<postId> emoji=thumbsup remove=true
```
Config: `channels.mattermost.actions.reactions` (default: true)
### Interactive buttons
```json5
{
channels: {
mattermost: {
capabilities: ["inlineButtons"],
interactions: {
callbackBaseUrl: "https://gateway.example.com",
},
},
},
}
```
Button fields: `text` (required), `callback_data` (required), `style` (optional: `default|primary|danger`)
HMAC verification: automatic. HMAC secret derived from bot token:
```python
secret = hmac.new(b"openclaw-mattermost-interactions", bot_token.encode(), hashlib.sha256).hexdigest()
```
### Preview streaming
```json5
{
channels: {
mattermost: {
streaming: "partial", // off | partial | block | progress
},
},
}
```
### DM channel retry
```json5
{
channels: {
mattermost: {
dmChannelRetry: {
maxRetries: 3,
initialDelayMs: 1000,
maxDelayMs: 10000,
timeoutMs: 30000,
},
},
},
}
```
### Multi-account
```json5
{
channels: {
mattermost: {
accounts: {
default: { name: "Primary", botToken: "mm-token", baseUrl: "https://chat.example.com" },
alerts: { name: "Alerts", botToken: "mm-token-2", baseUrl: "https://alerts.example.com" },
},
},
},
}
```
### Targets
- `channel:<id>` for a channel
- `user:<id>` for a DM
- `@username` for a DM (resolved via API)
- Bare opaque IDs: resolved user-first (user then channel)
### Troubleshooting
- Buttons as white boxes: check `text` + `callback_data` fields
- Buttons return 404: button `id` may contain hyphens/underscores (must be alphanumeric only)
- `Unauthorized: invalid command token`: registration failed or wrong callback target
---
## Microsoft Teams
**Status:** Bundled plugin. Text and DM attachments supported. Channel/group file sending requires `sharePointSiteId` + Graph permissions.
### Quick setup (minimal config with client secret)
```json5
{
channels: {
msteams: {
enabled: true,
appId: "<APP_ID>",
appPassword: "<APP_PASSWORD>",
tenantId: "<TENANT_ID>",
webhook: { port: 3978, path: "/api/messages" },
},
},
}
```
### Federated authentication (production recommended)
**Certificate-based:**
```json5
{
channels: {
msteams: {
authType: "federated",
certificatePath: "/path/to/cert.pem",
},
},
}
```
**Managed Identity (Azure):**
```json5
{
channels: {
msteams: {
authType: "federated",
useManagedIdentity: true,
managedIdentityClientId: "<MI_CLIENT_ID>", // only for user-assigned
},
},
}
```
### Access control
DMs: `dmPolicy` (default: `"pairing"`), `allowFrom` (use stable AAD object IDs)
Groups:
- Default: `groupPolicy = "allowlist"` (blocked until `groupAllowFrom` set)
- `groupPolicy: "open"` → mention-gated
- Teams allowlist: `channels.msteams.teams`
```json5
{
channels: {
msteams: {
groupPolicy: "allowlist",
groupAllowFrom: ["[email protected]"],
teams: {
"My Team": {
channels: {
General: { requireMention: true },
},
},
},
},
},
}
```
### RSC permissions (Teams manifest)
```json
"authorization": {
"permissions": {
"resourceSpecific": [
{ "name": "ChannelMessage.Read.Group", "type": "Application" },
{ "name": "ChannelMessage.Send.Group", "type": "Application" },
{ "name": "Member.Read.Group", "type": "Application" },
{ "name": "ChatMessage.Read.Chat", "type": "Application" }
// ... additional permissions
]
}
}
```
### Capabilities comparison
| Capability | RSC only | RSC + Graph API |
|------------|----------|-----------------|
| Real-time messages | ✅ | ✅ |
| Historical messages | ❌ | ✅ |
| Images/file contents | ❌ | ✅ |
| SharePoint/OneDrive attachments | ❌ | ✅ |
### History context
- `channels.msteams.historyLimit` (default 50, set `0` to disable)
- Fetched thread history filtered by sender allowlists
### Local dev tunneling
- ngrok: `ngrok http 3978`
- Tailscale Funnel: `tailscale funnel 3978`
### Config writes
Disable: `channels.msteams.configWrites: false`
---
## Nextcloud Talk
**Status:** Bundled plugin. DMs, rooms, reactions, and markdown messages supported.
### Setup
1. Install OpenClaw bot on Nextcloud server:
```bash
./occ talk:bot:install "OpenClaw" "<shared-secret>" "<webhook-url>" --feature reaction
```
2. Enable bot in target room settings
3. Configure OpenClaw:
```json5
{
channels: {
"nextcloud-talk": {
enabled: true,
baseUrl: "https://cloud.example.com",
botSecret: "shared-secret",
dmPolicy: "pairing",
},
},
}
```
CLI setup:
```bash
openclaw channels add --channel nextcloud-talk --url https://cloud.example.com --token "<shared-secret>"
```
### Access control
- DMs: `dmPolicy` (default: `pairing`); `allowFrom` = Nextcloud user IDs only
- Rooms: `groupPolicy = allowlist | open | disabled`; per-room config under `rooms`
### Capabilities
| Feature | Status |
|---------|--------|
| Direct messages | Supported |
| Rooms | Supported |
| Threads | Not supported |
| Media | URL-only |
| Reactions | Supported |
| Native commands | Not supported |
### Notes
- Bots cannot initiate DMs; user must message first
- `apiUser` + `apiPassword` needed for DM detection (room-type lookup)
- Webhook URL must be reachable by the Gateway; set `webhookPublicUrl` if behind a proxy
### Config reference
`channels.nextcloud-talk.enabled|baseUrl|botSecret|botSecretFile|apiUser|apiPassword|webhookPort(8788)|webhookHost(0.0.0.0)|webhookPath(/nextcloud-talk-webhook)|webhookPublicUrl|dmPolicy|allowFrom|groupPolicy|groupAllowFrom|rooms|historyLimit|dmHistoryLimit|textChunkLimit|chunkMode|mediaMaxMb`
---
## Nostr
**Status:** Bundled plugin, disabled by default until configured. DMs only via NIP-04.
### Quick setup
```bash
nak key generate # generate keypair
export NOSTR_PRIVATE_KEY="nsec1..."
```
```json5
{
channels: {
nostr: {
privateKey: "NOSTR_PRIVATE_KEY",
relays: ["wss://relay.damus.io", "wss://relay.primal.net"],
dmPolicy: "pairing",
},
},
}
```
CLI:
```bash
openclaw channels add --channel nostr --private-key "$NOSTR_PRIVATE_KEY"
openclaw channels add --channel nostr --private-key "$NOSTR_PRIVATE_KEY" --relay-urls "wss://relay.damus.io,wss://relay.primal.net"
```
### Config reference
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `privateKey` | string | required | `nsec...` or 64-char hex |
| `relays` | string[] | `['wss://relay.damus.io', 'wss://nos.lol']` | Relay WebSocket URLs |
| `dmPolicy` | string | `pairing` | DM access policy |
| `allowFrom` | string[] | `[]` | Allowed sender pubkeys (`npub...` or hex) |
| `enabled` | boolean | `true` | |
| `profile` | object | — | NIP-01 profile metadata |
### Profile metadata
```json5
{
channels: {
nostr: {
profile: {
name: "openclaw",
displayName: "OpenClaw",
about: "Personal assistant DM bot",
picture: "https://example.com/avatar.png",
nip05: "[email protected]",
lud16: "[email protected]",
},
},
},
}
```
### Security
- Event signatures verified BEFORE sender policy and decryption
- Pairing replies sent without processing original DM body
- Rate limited; oversized payloads dropped before decrypt
### Protocol support
| NIP | Status |
|-----|--------|
| NIP-01 | Supported (basic events + profile) |
| NIP-04 | Supported (encrypted DMs, kind:4) |
| NIP-17 | Planned (gift-wrapped DMs) |
| NIP-44 | Planned (versioned encryption) |
### Limitations
- DMs only (no group chats)
- No media attachments
- NIP-04 only (NIP-17 planned)
- Duplicate responses expected with multiple relays (deduplicated by event ID)
---
## QQ Bot
**Status:** Bundled plugin. C2C private chat, group @messages, guild channels, rich media.
### Setup
1. Register at https://q.qq.com/
2. Create bot → copy AppID + AppSecret (never stored in plaintext by Tencent)
```bash
openclaw channels add --channel qqbot --token "AppID:AppSecret"
```
```json5
{
channels: {
qqbot: {
enabled: true,
appId: "YOUR_APP_ID",
clientSecret: "YOUR_APP_SECRET",
},
},
}
```
Env vars: `QQBOT_APP_ID`, `QQBOT_CLIENT_SECRET`
### Target formats
| Format | Description |
|--------|-------------|
| `qqbot:c2c:OPENID` | Private chat (C2C) |
| `qqbot:group:GROUP_OPENID` | Group chat |
| `qqbot:channel:CHANNEL_ID` | Guild channel |
> Each bot has its own user OpenIDs; OpenIDs from Bot A cannot be used with Bot B.
### Voice (STT/TTS)
```json5
{
channels: {
qqbot: {
stt: { provider: "your-provider", model: "your-stt-model" },
tts: { provider: "your-provider", model: "your-tts-model", voice: "your-voice" },
},
},
}
```
### Built-in slash commands
`/bot-ping`, `/bot-version`, `/bot-help`, `/bot-upgrade`, `/bot-logs`, `/bot-approve`
### Multi-account
```json5
{
channels: {
qqbot: {
appId: "111111111",
clientSecret: "secret-of-bot-1",
accounts: {
bot2: { appId: "222222222", clientSecret: "secret-of-bot-2" },
},
},
},
}
```
---
## Signal
**Status:** External CLI integration. Gateway talks to `signal-cli` over HTTP JSON-RPC + SSE.
### Prerequisites
- `signal-cli` installed on gateway host (JVM build requires Java)
- Phone number for verification (SMS path) or existing Signal account (QR link path)
### Setup path A: Link existing Signal account (QR)
```bash
signal-cli link -n "OpenClaw" # scan QR in Signal app
```
### Setup path B: Register dedicated number
```bash
# Install signal-cli native binary
VERSION=$(curl -Ls -o /dev/null -w %{url_effective} https://github.com/AsamK/signal-cli/releases/latest | sed -e 's/^.*\/v//')
curl -L -O "https://github.com/AsamK/signal-cli/releases/download/vVERSION/signal-cli-VERSION-Linux-native.tar.gz"
sudo tar xf "signal-cli-VERSION-Linux-native.tar.gz" -C /opt
sudo ln -sf /opt/signal-cli /usr/local/bin/
# Register (captcha may be required)
signal-cli -a +<BOT_PHONE_NUMBER> register --captcha '<SIGNALCAPTCHA_URL>'
signal-cli -a +<BOT_PHONE_NUMBER> verify <VERIFICATION_CODE>
```
### Config
```json5
{
channels: {
signal: {
enabled: true,
account: "+15551234567",
cliPath: "signal-cli",
dmPolicy: "pairing",
allowFrom: ["+15557654321"],
},
},
}
```
### Access control
DMs: `dmPolicy` (default: `pairing`); `allowFrom` accepts E.164 or `uuid:<id>`
Groups:
- `groupPolicy = open | allowlist | disabled`
- `groupAllowFrom`
- `channels.signal.groups["<group-id>" | "*"]` for per-group overrides (`requireMention`, `tools`, `toolsBySender`)
### External daemon mode
```json5
{
channels: {
signal: {
httpUrl: "http://127.0.0.1:8080",
autoStart: false,
},
},
}
```
### Reactions
```
message action=react channel=signal target=uuid:123e4567-... messageId=1737630212345 emoji=🔥
message action=react channel=signal target=signal:group:<groupId> targetAuthor=uuid:<sender-uuid> messageId=1737630212345 emoji=✅
```
Config: `channels.signal.reactionLevel: off | ack | minimal | extensive`
### Delivery targets
- DMs: `signal:+15551234567` or plain E.164
- UUID DMs: `uuid:<id>`
- Groups: `signal:group:<groupId>`
### Typing + read receipts
- Typing indicators: automatic (via `signal-cli sendTyping`)
- Read receipts: `channels.signal.sendReadReceipts` (for allowed DMs)
### Media
- Text chunk: `channels.signal.textChunkLimit` (default 4000)
- Chunk mode: `length` (default) or `newline`
- Attachments: `channels.signal.ignoreAttachments` to skip
- Media cap: `channels.signal.mediaMaxMb` (default 8)
### Security notes
- `signal-cli` stores account keys in `~/.local/share/signal-cli/data/`
- Registering a number with `signal-cli` can de-authenticate the main Signal app for that number → use dedicated bot number
---
## Slack
**Status:** Production-ready for DMs and channels. Default: Socket Mode; HTTP Request URLs also supported.
### Quick setup (Socket Mode)
1. Create Slack app from manifest at https://api.slack.com/apps/new
2. Generate App-Level Token (`xapp-...`) with `connections:write`
3. Install app, copy Bot Token (`xoxb-...`)
```json5
{
channels: {
slack: {
enabled: true,
mode: "socket", // or "http"
appToken: "xapp-...", // Socket Mode only
botToken: "xoxb-...",
signingSecret: "...", // HTTP mode only
},
},
}
```
Env fallback: `SLACK_APP_TOKEN`, `SLACK_BOT_TOKEN` (default account only)
### Required OAuth scopes
`app_mentions:read`, `assistant:write`, `channels:history`, `channels:read`, `chat:write`, `commands`, `emoji:read`, `files:read`, `files:write`, `groups:history`, `groups:read`, `im:history`, `im:read`, `im:write`, `mpim:history`, `mpim:read`, `mpim:write`, `pins:read`, `pins:write`, `reactions:read`, `reactions:write`, `users:read`
Optional: `chat:write.customize` (for custom agent identity in messages)
### Access control
DM policy (`channels.slack.dmPolicy`): `pairing` (default) | `allowlist` | `open` | `disabled`
Channel policy (`channels.slack.groupPolicy`): `open | allowlist | disabled`
Channel allowlist uses stable channel IDs under `channels.slack.channels`.
Per-channel controls: `requireMention`, `users`, `allowBots`, `skills`, `systemPrompt`, `tools`, `toolsBySender`
### Threading and sessions
- DMs → `direct`; channels → `channel`; MPIMs → `group`
- `channels.slack.replyToMode`: `off | first | all | batched` (default `off`)
- Thread sessions: `agent:<agentId>:slack:channel:<channelId>:thread:<threadTs>`
- `thread.historyScope` default: `thread`; `thread.inheritParent` default: `false`
- `thread.requireExplicitMention` default: `false`
> **Note:** `replyToMode="off"` disables ALL reply threading, including explicit `[[reply_to_*]]` tags. Different from Telegram.
### Ack reactions
Resolution order: account `ackReaction` → channel `ackReaction` → `messages.ackReaction` → agent identity emoji (default "👀")
```json5
{ channels: { slack: { ackReaction: "eyes" } } }
```
### Text streaming
```json5
{
channels: {
slack: {
streaming: "partial", // off | partial | block | progress
},
},
}
```
### Actions
| Group | Default |
|-------|---------|
| messages | enabled |
| reactions | enabled |
| pins | enabled |
| memberInfo | enabled |
| emojiList | enabled |
Actions: `send`, `upload-file`, `download-file`, `read`, `edit`, `delete`, `pin`, `unpin`, `list-pins`, `member-info`, `emoji-list`
### User token (`userToken`)
- Config-only (no env fallback)
- Defaults to read-only (`userTokenReadOnly: true`)
- For writes: `userTokenReadOnly: false` (only when bot token unavailable)
### Multi-account
Use `channels.slack.accounts` with per-account tokens. Named accounts inherit `channels.slack.allowFrom` when own `allowFrom` unset; they do NOT inherit `accounts.default.allowFrom`.
---
## Synology Chat
**Status:** Bundled plugin. Direct messages only via outgoing + incoming webhooks.
### Setup
1. In Synology Chat integrations: create incoming + outgoing webhooks
2. Point outgoing webhook URL to: `https://gateway-host/webhook/synology`
3. Configure OpenClaw:
```json5
{
channels: {
"synology-chat": {
enabled: true,
token: "synology-outgoing-token",
incomingUrl: "https://nas.example.com/webapi/entry.cgi?api=SYNO.Chat.External&method=incoming&version=2&token=...",
webhookPath: "/webhook/synology",
dmPolicy: "allowlist",
allowedUserIds: ["123456"],
rateLimitPerMinute: 30,
allowInsecureSsl: false,
},
},
}
```
CLI setup:
```bash
openclaw channels add --channel synology-chat --token <token> --url <incoming-webhook-url>
```
### Webhook auth
OpenClaw accepts token from (in order): `body.token`, `?token=...`, headers (`x-synology-token`, `x-webhook-token`, `x-openclaw-token`, `Authorization: Bearer <token>`)
### Access control
- `dmPolicy`: `allowlist` (recommended) | `open` | `disabled`
- `allowedUserIds`: list of Synology numeric user IDs
- Empty `allowedUserIds` with `dmPolicy: "allowlist"` = misconfiguration (route won't start)
### Targets for outbound delivery
```bash
openclaw message send --channel synology-chat --target 123456 --text "Hello"
openclaw message send --channel synology-chat --target synology-chat:123456 --text "Hello"
```
Media sends are URL-based; outbound URLs must use http/https.
### Multi-account
Give each account a distinct `webhookPath`. Duplicate paths rejected (fail-closed).
### Env vars
`SYNOLOGY_CHAT_TOKEN`, `SYNOLOGY_CHAT_INCOMING_URL`, `SYNOLOGY_NAS_HOST`, `SYNOLOGY_ALLOWED_USER_IDS`, `SYNOLOGY_RATE_LIMIT`, `OPENCLAW_BOT_NAME`
> Note: `SYNOLOGY_CHAT_INCOMING_URL` cannot be set from workspace `.env`
### Security
- Invalid token checks: constant-time comparison, fail-closed
- Rate limited per sender
- `dangerouslyAllowNameMatching: false` (keeps stable numeric user ID matching)
- `dangerouslyAllowInheritedWebhookPath: false`
---
## Telegram
**Status:** Production-ready via grammY. Long polling (default) or webhook mode.
### Quick setup
1. Chat with @BotFather → `/newbot` → save token
2. Configure:
```json5
{
channels: {
telegram: {
enabled: true,
botToken: "123:abc",
dmPolicy: "pairing",
groups: { "*": { requireMention: true } },
},
},
}
```
Env fallback: `TELEGRAM_BOT_TOKEN=...` (default account only)
> Token resolution is account-aware: config values win over env; `TELEGRAM_BOT_TOKEN` only applies to the default account. Telegram does NOT use `openclaw channels login telegram`.
3. Approve first DM:
```bash
openclaw gateway
openclaw pairing list telegram
openclaw pairing approve telegram <CODE>
```
### Telegram bot settings
- **Privacy Mode**: disable via `/setprivacy` or make bot admin (allows seeing all group messages)
- When toggling privacy mode, **remove + re-add the bot** in each group so Telegram applies the change
- Group permissions: `/setjoingroups`, `/setprivacy`
### Access control
DM policy (`channels.telegram.dmPolicy`): `pairing` (default) | `allowlist` | `open` | `disabled`
`allowFrom` accepts numeric Telegram user IDs (with optional `telegram:` or `tg:` prefix).
Finding your Telegram user ID:
```bash
# DM your bot, then:
openclaw logs --follow # read from.id
# Or:
curl "https://api.telegram.org/bot<bot_token>/getUpdates"
```
Group policy:
- `groupPolicy: "allowlist"` (default) + `channels.telegram.groups` entries
- Groups as negative chat IDs go under `channels.telegram.groups`
- User IDs for sender filtering go in `groupAllowFrom`
- `groupAllowFrom` does NOT inherit DM pairing-store approvals
- Pattern for one-owner bots: set user ID in `allowFrom`, leave `groupAllowFrom` unset, allow target groups under `groups`
```json5
{
channels: {
telegram: {
groups: {
"-1001234567890": {
groupPolicy: "open",
requireMention: false,
allowFrom: ["8734062810", "745123456"], // restrict senders
},
},
},
},
}
```
### Feature reference
**Live streaming preview:**
```json5
{
channels: {
telegram: {
streaming: "partial", // off | partial | block | progress
},
},
}
```
- DM: keeps same preview, final edit in place
- Group/topic: same behavior
- Reasoning stream: `/reasoning stream` → live preview while generating
- `streaming.preview.toolProgress` (default: `true`): controls whether tool/progress updates reuse the same edited preview message. Set `false` to keep separate tool/progress messages.
**Formatting:** Outbound uses `parse_mode: "HTML"`. Falls back to plain text on parse failure.
**Native commands:** Registered via `setMyCommands`. Custom commands:
```json5
{
channels: {
telegram: {
customCommands: [
{ command: "backup", description: "Git backup" },
{ command: "generate", description: "Create an image" },
],
},
},
}
```
**Inline buttons:**
```json5
{
channels: {
telegram: {
capabilities: {
inlineButtons: "allowlist", // off | dm | group | all | allowlist
},
},
},
}
```
Legacy `capabilities: ["inlineButtons"]` (array format) maps to `inlineButtons: "all"`.
```json5
{
action: "send",
channel: "telegram",
to: "123456789",
message: "Choose an option:",
buttons: [
[{ text: "Yes", callback_data: "yes" }, { text: "No", callback_data: "no" }],
[{ text: "Cancel", callback_data: "cancel" }],
],
}
```
**Message actions:**
- `sendMessage`, `react`, `deleteMessage`, `editMessage`, `createForumTopic`
- Config gates: `channels.telegram.actions.sendMessage|deleteMessage|reactions|sticker`
**Reply tags:**
- `[[reply_to_current]]`, `[[reply_to:<id>]]`
- `channels.telegram.replyToMode`: `off (default) | first | all`
- Even with `off`, explicit tags are honored
**Forum topics:**
- Session keys: `agent:<agentId>:telegram:group:<chatId>:topic:<threadId>`
- Per-topic agent routing via `agentId` in topic config
- Topic entries inherit group settings unless overridden (`requireMention`, `allowFrom`, `skills`, `systemPrompt`, `enabled`, `groupPolicy`)
- **Important:** `agentId` is topic-only and does NOT inherit from group defaults. All other listed settings do inherit.
### Runtime model
- Long-polling via grammY runner with per-chat/per-thread sequencing
- Polling watchdog: restart after 120s without `getUpdates` liveness
- `pollingStallThresholdMs`: 30000–600000 ms range
- DM messages can carry `message_thread_id`; OpenClaw routes with thread-aware session keys and preserves thread ID for replies
- No read-receipt support
### Doctor fixes for Telegram
- If config contains `@username` allowlist entries from older setups, run `openclaw doctor --fix` to resolve them (best-effort; requires bot token)
- `doctor --fix` can recover pairing-store entries into `channels.telegram.allowFrom` in allowlist flows
### Device pairing (device-pair plugin)
```
/pair → setup code → paste in iOS app → /pair pending → /pair approve <requestId>
```
### Common mistakes
- `groupAllowFrom` ≠ group chat allowlist (group chat IDs go in `groups`)
- `dmPolicy: "allowlist"` with empty `allowFrom` blocks all DMs
---
## Tlon (Urbit)
**Status:** Bundled plugin. DMs, group mentions, thread replies, rich text, image uploads. No reactions or polls.
### What it is
Connects to your Urbit ship (Tlon messenger) via the `zca-js` API.
### Setup
```json5
{
channels: {
tlon: {
enabled: true,
ship: "~sampel-palnet",
url: "https://your-ship-host",
code: "lidlut-tabwed-pillex-ridrup",
ownerShip: "~your-main-ship", // recommended: always authorized
},
},
}
```
For private/LAN ships:
```json5
{
channels: {
tlon: {
url: "http://localhost:8080",
allowPrivateNetwork: true, // disables SSRF protection for this URL
},
},
}
```
### Access control
DM allowlist:
```json5
{ channels: { tlon: { dmAllowlist: ["~zod", "~nec"] } } }
```
Group authorization:
```json5
{
channels: {
tlon: {
defaultAuthorizedShips: ["~zod"],
authorization: {
channelRules: {
"chat/~host-ship/general": {
mode: "restricted",
allowedShips: ["~zod", "~nec"],
},
"chat/~host-ship/announcements": { mode: "open" },
},
},
},
},
}
```
### Owner ship
- Always authorized everywhere (DMs auto-accepted, channel messages always allowed)
- Receives notifications for unauthorized access attempts
### Auto-accept settings
```json5
{
channels: {
tlon: {
autoAcceptDmInvites: true,
autoAcceptGroupInvites: true,
},
},
}
```
### Delivery targets
- DM: `~sampel-palnet` or `dm/~sampel-palnet`
- Group: `chat/~host-ship/channel` or `group:~host-ship/channel`
### Bundled skill
Provides CLI access: contacts, channels, groups, DMs, reactions, settings (via slash commands).
### Capabilities
| Feature | Status |
|---------|--------|
| Direct messages | ✅ |
| Groups/channels | ✅ (mention-gated) |
| Threads | ✅ (auto-replies in thread) |
| Rich text | ✅ (Markdown → Tlon format) |
| Images | ✅ (uploaded to Tlon storage) |
| Reactions | ✅ (via bundled skill) |
| Polls | ❌ |
| Native commands | ✅ (owner-only by default) |
---
## Twitch
**Status:** Bundled plugin. Twitch chat via IRC connection.
### Quick setup
1. Create dedicated Twitch account for bot
2. Generate credentials at https://twitchtokengenerator.com/ (select Bot Token, scopes: `chat:read`, `chat:write`)
3. Find your Twitch user ID
```json5
{
channels: {
twitch: {
enabled: true,
username: "openclaw",
accessToken: "oauth:abc123...",
clientId: "xyz789...",
channel: "vevisk",
allowFrom: ["123456789"], // your Twitch user ID (permanent, unlike usernames)
},
},
}
```
Env var: `OPENCLAW_TWITCH_ACCESS_TOKEN=...` (default account only)
### Access control
- `allowFrom`: hard allowlist by Twitch user ID (permanent, unlike usernames)
- `allowedRoles`: `"moderator" | "owner" | "vip" | "subscriber" | "all"`
- `requireMention`: default `true`
When both are set, `allowFrom` takes precedence.
### Token refresh
```json5
{
channels: {
twitch: {
clientSecret: "your_client_secret",
refreshToken: "your_refresh_token",
},
},
}
```
Requires own Twitch application at https://dev.twitch.tv/console.
### Multi-account
```json5
{
channels: {
twitch: {
accounts: {
channel1: { username: "openclaw", accessToken: "...", clientId: "...", channel: "vevisk" },
channel2: { username: "openclaw", accessToken: "...", clientId: "...", channel: "secondchannel" },
},
},
},
}
```
### Limits
- 500 characters per message (auto-chunked at word boundaries)
- No built-in rate limiting (uses Twitch's limits)
- Markdown stripped before chunking
### Tool actions
- `action: "twitch"` with `params: { message: "...", to: "#mychannel" }`
---
## WeChat
**Status:** External plugin (`@tencent-weixin/openclaw-weixin`). Direct chats and media supported. Group chats not advertised.
### Naming
- Channel id: `openclaw-weixin`
- npm package: `@tencent-weixin/openclaw-weixin`
- Use `openclaw-weixin` in CLI commands and config paths
### Install
```bash
npx -y @tencent-weixin/openclaw-weixin-cli install
# or manual:
openclaw plugins install "@tencent-weixin/openclaw-weixin"
openclaw config set plugins.entries.openclaw-weixin.enabled true
openclaw gateway restart
```
### Login (QR)
```bash
openclaw channels login --channel openclaw-weixin
```
### Pairing
```bash
openclaw pairing list openclaw-weixin
openclaw pairing approve openclaw-weixin <CODE>
```
### Compatibility
| Plugin line | OpenClaw version |
|-------------|-----------------|
| `2.x` | `>=2026.3.22` |
| `1.x` | `>=2026.1.0 <2026.3.22` |
### Multi-account session isolation
```bash
openclaw config set session.dmScope per-account-channel-peer
```
### Troubleshooting
- Gateway restart loops after enabling WeChat: update both OpenClaw and plugin
- Temporary disable: `openclaw config set plugins.entries.openclaw-weixin.enabled false`
---
## WhatsApp
**Status:** Production-ready via WhatsApp Web (Baileys). Gateway owns linked session(s).
### Install (on demand)
```bash
openclaw plugins install @openclaw/whatsapp
# or: openclaw onboard, openclaw channels add --channel whatsapp
# Custom auth dir:
openclaw channels add --channel whatsapp --account work --auth-dir /path/to/wa-auth
openclaw channels login --channel whatsapp --account work
```
> Recommended: use a separate/dedicated number for OpenClaw. Personal-number setups also supported with `selfChatMode: true`.
### Quick setup
```json5
{
channels: {
whatsapp: {
dmPolicy: "pairing",
allowFrom: ["+15551234567"],
groupPolicy: "allowlist",
groupAllowFrom: ["+15551234567"],
},
},
}
```
```bash
openclaw channels login --channel whatsapp
openclaw gateway
openclaw pairing list whatsapp
openclaw pairing approve whatsapp <CODE>
```
### Access control
DM policy (`channels.whatsapp.dmPolicy`): `pairing | allowlist | open | disabled`
Group access (two layers):
1. **Group membership**: `channels.whatsapp.groups` (when configured, acts as allowlist)
2. **Group sender**: `groupPolicy` + `groupAllowFrom`
Mention detection:
- Explicit WhatsApp mentions
- Configured regex patterns
- Implicit reply-to-bot
- Quote/reply satisfies mention gating but does NOT grant sender authorization
### System prompts
Distinct from Telegram multi-account behavior. Resolution hierarchy:
1. Group-specific: `groups["<groupId>"].systemPrompt` (empty string suppresses wildcard)
2. Group wildcard: `groups["*"].systemPrompt`
3. Direct-specific: `direct["<peerId>"].systemPrompt`
4. Direct wildcard: `direct["*"].systemPrompt`
**Multi-account override behavior:** If an account defines its own `groups` map, it **fully replaces** the root `groups` map (no deep merge). The same applies to `direct`. Prompt lookup then runs on the resulting single map.
### Personal-number and self-chat
When linked self number is in `allowFrom`:
- Skip read receipts for self-chat turns
- Avoid mention-JID auto-trigger
- Response prefix defaults to `[{identity.name}]`
- Set `selfChatMode: true` for personal-number mode
- OpenClaw never auto-pairs outbound `fromMe` DMs (messages sent from linked device)
- `@status` and `@broadcast` chats are ignored
### Media
- Text chunk: `channels.whatsapp.textChunkLimit` (default 4000); `chunkMode: length | newline`
- Images auto-optimized (resize/quality sweep)
- Media cap: `channels.whatsapp.mediaMaxMb` (default 50)
- `audio/ogg` rewritten to `audio/ogg; codecs=opus` for voice-note compatibility
- GIF playback: `gifPlayback: true` on video sends
### Reply quoting
`channels.whatsapp.replyToMode`: `"auto"` (default) | `"on"` | `"off"`
### Reaction level
| Level | Ack reactions | Agent reactions |
|-------|--------------|-----------------|
| `"off"` | No | No |
| `"ack"` | Yes | No |
| `"minimal"` (default) | Yes | Yes (conservative) |
| `"extensive"` | Yes | Yes (encouraged) |
### Acknowledgment reactions
```json5
{
channels: {
whatsapp: {
ackReaction: {
emoji: "👀",
direct: true,
group: "mentions", // always | mentions | never
},
},
},
}
```
### Plugin hooks (opt-in)
```json5
{
channels: {
whatsapp: {
pluginHooks: { messageReceived: true },
},
},
}
```
### Proxy support
Standard proxy env vars: `HTTPS_PROXY`, `HTTP_PROXY`, `NO_PROXY` (preferred over channel-specific proxy settings)
### Config writes
Disable: `channels.whatsapp.configWrites: false`
### Multi-account credentials
- Auth path: `~/.openclaw/credentials/whatsapp/<accountId>/creds.json`
- Logout: `openclaw channels logout --channel whatsapp [--account <id>]`
### Broadcast groups
WhatsApp supports broadcast groups (see [Broadcast Groups](#broadcast-groups) section).
### Troubleshooting
- Not linked: `openclaw channels login --channel whatsapp`
- Reconnect loop: `openclaw doctor && openclaw logs --follow`
- Group messages ignored: check `groupPolicy`, `groupAllowFrom`, `groups` allowlist, mention gating
- Bun runtime: use Node (Bun flagged incompatible for stable WhatsApp/Telegram)
- Pairing requests expire after 1 hour; pending requests capped at 3 per channel
---
## Zalo (Bot API)
**Status:** Experimental. DMs supported. Marketplace bots only (groups not available for Marketplace bots).
### Quick setup
```json5
{
channels: {
zalo: {
enabled: true,
accounts: {
default: {
botToken: "12345689:abc-xyz",
dmPolicy: "pairing",
},
},
},
},
}
```
Env var: `ZALO_BOT_TOKEN=...` (default account only)
Get token from https://bot.zaloplatforms.com
### Long-polling vs webhook
- Default: long-polling (no public URL required)
- Webhook: set `channels.zalo.webhookUrl` + `channels.zalo.webhookSecret` (8-256 chars, HTTPS required)
- `X-Bot-Api-Secret-Token` header for webhook verification
- Polling and webhook are mutually exclusive
### Capabilities (Marketplace bots)
| Feature | Status |
|---------|--------|
| Direct messages | ✅ |
| Groups | ❌ Not available |
| Media (inbound images) | ⚠️ Limited |
| Plain URLs | ✅ |
| Link previews | ⚠️ Unreliable |
| Reactions | ❌ |
| Stickers | ⚠️ No agent reply |
| Voice/audio/video | ⚠️ No agent reply |
| Threads | ❌ |
| Native commands | ❌ |
| Streaming | ⚠️ Blocked (2000 char limit) |
### Config reference
`channels.zalo.enabled|botToken|tokenFile|dmPolicy|allowFrom|groupPolicy|groupAllowFrom|mediaMaxMb(5)|webhookUrl|webhookSecret|webhookPath|proxy`
Multi-account: `channels.zalo.accounts.<id>.*`
---
## Zalo Personal
**Status:** Experimental. Automates a **personal Zalo account** via `zca-js`.
> **Warning:** Unofficial integration. May result in account suspension/ban.
### Setup
```bash
openclaw channels login --channel zalouser # QR code scan
```
```json5
{
channels: {
zalouser: {
enabled: true,
dmPolicy: "pairing",
},
},
}
```
### Finding IDs
```bash
openclaw directory self --channel zalouser
openclaw directory peers list --channel zalouser --query "name"
openclaw directory groups list --channel zalouser --query "work"
```
### Access control
DMs: `dmPolicy`: `pairing | allowlist | open | disabled`; `allowFrom` = user IDs or names
Groups: `groupPolicy = open` (default) | `allowlist` | `disabled`
Group allowlist:
```json5
{
channels: {
zalouser: {
groupPolicy: "allowlist",
groupAllowFrom: ["1471383327500481391"],
groups: {
"123456789": { allow: true },
"Work Chat": { allow: true },
"*": { allow: true, requireMention: true },
},
},
},
}
```
### Group mention gating
- `requireMention` per group (default: `true`)
- Quoting a bot message = implicit mention
- Authorized control commands bypass mention gating
### Multi-account
```json5
{
channels: {
zalouser: {
defaultAccount: "default",
accounts: { work: { enabled: true, profile: "work" } },
},
},
}
```
### Reactions
`message action=react` with `remove: true` to remove specific emoji.
### Limits
- Text chunked to ~2000 chars
- Streaming blocked by default
---
## Troubleshooting
### Universal command ladder
```bash
openclaw status
openclaw gateway status
openclaw logs --follow
openclaw doctor
openclaw channels status --probe
```
### Healthy baseline
- `Runtime: running`
- `Connectivity probe: ok`
- Channel probe shows transport connected
### Channel-specific troubleshooting
| Channel | Common issue | Quick check | Fix |
|---------|-------------|-------------|-----|
| WhatsApp | No DM replies | `openclaw pairing list whatsapp` | Approve sender or switch DM policy |
| WhatsApp | Group messages ignored | Check `requireMention` + mention patterns | Mention bot or relax policy |
| Telegram | No group replies | Verify mention requirement and privacy mode | Disable privacy mode or mention bot |
| Telegram | Polling stalls | `openclaw logs --follow` | Tune `pollingStallThresholdMs` |
| Discord | No guild replies | `openclaw channels status --probe` | Allow guild/channel, verify message content intent |
| Slack | No responses | Check tokens + scopes | Verify `botTokenStatus` / `appTokenStatus` |
| iMessage/BB | No inbound events | Check webhook/server reachability | Fix webhook URL or BlueBubbles server state |
| Signal | No replies | `openclaw channels status --probe` | Verify signal-cli daemon + account |
| QQ Bot | "Gone to Mars" | Verify appId + clientSecret | Set credentials or restart gateway |
| Matrix | Encrypted rooms fail | `openclaw matrix verify status` | Re-verify device; check backup status |
---
## Feature Comparison Table
| Channel | DMs | Groups | Reactions | Media | Voice | Buttons | Threads | Streaming | E2EE |
|---------|-----|--------|-----------|-------|-------|---------|---------|-----------|------|
| **BlueBubbles** (iMessage) | ✅ | ✅ | ✅ (tapbacks) | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ (native) |
| **Discord** | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ (Components v2) | ✅ | ❌ | ❌ |
| **Feishu/Lark** | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ | ✅ (cards) | ❌ |
| **Google Chat** | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| **iMessage (Legacy)** | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ (native) |
| **IRC** | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| **LINE** | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ (quick replies) | ❌ | ❌ | ❌ |
| **Matrix** | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ (partial/quiet) | ✅ (E2EE) |
| **Mattermost** | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ |
| **Microsoft Teams** | ✅ | ✅ | ❌ | ✅ (DM) | ❌ | ✅ (Adaptive Cards) | ✅ | ❌ | ✅ (native) |
| **Nextcloud Talk** | ✅ | ✅ | ✅ | ❌ (URL only) | ❌ | ❌ | ❌ | ❌ | ❌ |
| **Nostr** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ (NIP-04) |
| **QQ Bot** | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
| **Signal** | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ (native) |
| **Slack** | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ |
| **Synology Chat** | ✅ | ❌ | ❌ | ✅ (URL) | ❌ | ❌ | ❌ | ❌ | ❌ |
| **Telegram** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ (inline) | ✅ (topics) | ✅ (partial) | ❌ |
| **Tlon** | ✅ | ✅ | ✅ (skill) | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ (Urbit) |
| **Twitch** | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| **WeChat** | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| **WhatsApp** | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ (native) |
| **Zalo (Bot API)** | ✅ | ❌ | ❌ | ⚠️ | ❌ | ❌ | ❌ | ❌ | ❌ |
| **Zalo Personal** | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
### DM Policy options by channel
| Channel | pairing | allowlist | open | disabled |
|---------|---------|-----------|------|---------|
| BlueBubbles | ✅ default | ✅ | ✅ | ✅ |
| Discord | ✅ default | ✅ | ✅ | ✅ |
| Feishu | ✅ | ✅ default | ✅ | ✅ |
| Google Chat | ✅ default | ✅ | ✅ | ✅ |
| iMessage | ✅ default | ✅ | ✅ | ✅ |
| IRC | ✅ default | ✅ | ✅ | ✅ |
| LINE | ✅ default | ✅ | ✅ | ✅ |
| Matrix | ✅ default | ✅ | ✅ | ✅ |
| Mattermost | ✅ default | ✅ | ✅ | ✅ |
| MS Teams | ✅ default | ✅ | ✅ | ✅ |
| Nextcloud Talk | ✅ default | ✅ | ✅ | ✅ |
| Nostr | ✅ default | ✅ | ✅ | ✅ |
| QQ Bot | — | — | — | — |
| Signal | ✅ default | ✅ | ✅ | ✅ |
| Slack | ✅ default | ✅ | ✅ | ✅ |
| Synology Chat | ✅ | ✅ default | ✅ | ✅ |
| Telegram | ✅ default | ✅ | ✅ | ✅ |
| Tlon | — | owner ship | — | — |
| Twitch | — | ✅ | — | — |
| WeChat | ✅ default | ✅ | ✅ | ✅ |
| WhatsApp | ✅ default | ✅ | ✅ | ✅ |
| Zalo (Bot) | ✅ default | ✅ | ✅ | ✅ |
| Zalo Personal | ✅ default | ✅ | ✅ | ✅ |
### Bundled vs external plugins
| Channel | Type | Install |
|---------|------|---------|
| BlueBubbles | Bundled | Included |
| Discord | Core | Included |
| Feishu | Bundled | Included |
| Google Chat | Bundled | `openclaw plugins install @openclaw/googlechat` (older builds) |
| iMessage | Bundled | Included (legacy) |
| IRC | Bundled | Included |
| LINE | Bundled | `openclaw plugins install @openclaw/line` (older builds) |
| Matrix | Bundled | `openclaw plugins install @openclaw/matrix` (older builds) |
| Mattermost | Bundled | `openclaw plugins install @openclaw/mattermost` (older builds) |
| MS Teams | Bundled | `openclaw plugins install @openclaw/msteams` (older builds) |
| Nextcloud Talk | Bundled | `openclaw plugins install @openclaw/nextcloud-talk` (older builds) |
| Nostr | Bundled | `openclaw plugins install @openclaw/nostr` (older builds) |
| QQ Bot | Bundled | Included |
| Signal | External CLI | Requires `signal-cli` |
| Slack | Core | Included |
| Synology Chat | Bundled | Included |
| Telegram | Core | Included |
| Tlon | Bundled | `openclaw plugins install @openclaw/tlon` (older builds) |
| Twitch | Bundled | `openclaw plugins install @openclaw/twitch` (older builds) |
| WeChat | External | `npx -y @tencent-weixin/openclaw-weixin-cli install` |
| WhatsApp | On-demand | `openclaw plugins install @openclaw/whatsapp` |
| Zalo (Bot) | Bundled | `openclaw plugins install @openclaw/zalo` (older builds) |
| Zalo Personal | Bundled | `openclaw plugins install @openclaw/zalouser` (older builds) |
FILE:references/05-providers.md
# OpenClaw Providers Reference
OpenClaw supports many LLM providers. Set the default model as `provider/model` in config.
## Table of Contents
- [Anthropic](#anthropic)
- [OpenAI](#openai)
- [Google (Gemini)](#google-gemini)
- [OpenRouter](#openrouter)
- [MiniMax](#minimax)
- [DeepSeek](#deepseek)
- [Groq](#groq)
- [Ollama](#ollama)
- [Together AI](#together-ai)
- [Mistral](#mistral)
- [Fireworks](#fireworks)
- [xAI](#xai)
- [Perplexity](#perplexity)
- [Amazon Bedrock](#amazon-bedrock)
- [Cloudflare AI Gateway](#cloudflare-ai-gateway)
- [Z.AI / GLM (Zhipu)](#zai--glm-zhipu)
- [Additional Providers (Brief)](#additional-providers-brief)
- [Provider Configuration: Model Selection & Failover](#provider-configuration-model-selection--failover)
- [Environment Variables Quick Reference](#environment-variables-quick-reference)
```json5
{
agents: { defaults: { model: { primary: "anthropic/claude-opus-4-6" } } }
}
```
Run `openclaw onboard` to authenticate, then `openclaw models list` to verify.
---
## Anthropic
**Provider ID:** `anthropic`
**Auth:** API key (`ANTHROPIC_API_KEY`) or Claude CLI reuse
**Models:** `claude-opus-4-6`, `claude-sonnet-4-6`, `claude-haiku-4-5`, etc.
**Docs:** https://docs.openclaw.ai/providers/anthropic
### Auth Routes
| Route | Model prefix | Auth |
|---|---|---|
| API key | `anthropic/*` | `ANTHROPIC_API_KEY` |
| Claude CLI | `anthropic/*` | Reuses existing Claude CLI credentials |
### Setup (API key)
```bash
openclaw onboard --anthropic-api-key "$ANTHROPIC_API_KEY"
openclaw models list --provider anthropic
```
### Config example
```json5
{
env: { ANTHROPIC_API_KEY: "sk-ant-..." },
agents: { defaults: { model: { primary: "anthropic/claude-opus-4-6" } } }
}
```
### Thinking (Claude 4.6+)
Claude 4.6 defaults to `adaptive` thinking. Override per-model:
```json5
{
agents: {
defaults: {
models: {
"anthropic/claude-opus-4-6": {
params: { thinking: "adaptive" } // off | minimal | low | medium | high | adaptive | max
}
}
}
}
}
```
### Prompt Caching
| Value | Duration | Description |
|---|---|---|
| `"short"` (default) | 5 min | Auto-applied for API-key auth |
| `"long"` | 1 hour | Extended cache |
| `"none"` | — | Disable caching |
```json5
{
agents: {
defaults: {
models: {
"anthropic/claude-opus-4-6": {
params: { cacheRetention: "long" }
}
}
}
}
}
```
### Fast Mode
```json5
{
agents: {
defaults: {
models: {
"anthropic/claude-sonnet-4-6": {
params: { fastMode: true } // maps to service_tier: "auto"
}
}
}
}
}
```
### 1M Context Window (beta)
```json5
{
agents: {
defaults: {
models: {
"anthropic/claude-opus-4-6": {
params: { context1m: true }
}
}
}
}
}
```
⚠️ Requires long-context access. `sk-ant-oat-*` tokens are rejected for 1M context.
**Note:** `anthropic/claude-opus-4.7` (and its `claude-cli` variant) have 1M context **by default** — no `params.context1m: true` needed.
### Per-agent cache overrides
Use model-level params as baseline, then override specific agents via `agents.list[].params`:
```json5
{
agents: {
defaults: {
models: { "anthropic/claude-opus-4-6": { params: { cacheRetention: "long" } } },
},
list: [
{ id: "research", default: true },
{ id: "alerts", params: { cacheRetention: "none" } },
],
},
}
```
Config merge order:
1. `agents.defaults.models["provider/model"].params`
2. `agents.list[].params` (overrides by key)
### Bedrock Claude caching notes
- Anthropic Claude models on Bedrock accept `cacheRetention` pass-through
- Non-Anthropic Bedrock models forced to `cacheRetention: "none"`
- API-key smart defaults seed `"short"` for Claude-on-Bedrock refs
### Fast mode notes
- Only injected for direct `api.anthropic.com` requests
- Proxy routes leave `service_tier` untouched
### Claude CLI policy
- Anthropic staff told OpenClaw team that Claude CLI reuse is sanctioned
- For long-lived gateway hosts, API keys remain the clearest production path
### Troubleshooting
- **401 errors**: Token expired; switch to API key.
- **"No API key found"**: Anthropic auth is per-agent; re-run onboarding for each agent.
- **Cooldown**: Check `openclaw models status --json` for `auth.unusableProfiles`.
---
## OpenAI
**Provider ID:** `openai`, `openai-codex`
**Auth:** `OPENAI_API_KEY` (API) or Codex OAuth (subscription)
**Docs:** https://docs.openclaw.ai/providers/openai
### Routes
| Goal | Model ref | Auth |
|---|---|---|
| API billing | `openai/gpt-5.4` | `OPENAI_API_KEY` |
| Codex subscription (PI) | `openai-codex/gpt-5.5` | Codex OAuth |
| Codex app-server | `openai/gpt-5.5` + `embeddedHarness.runtime: "codex"` | Codex auth |
| Image generation | `openai/gpt-image-2` | Either |
> **GPT-5.5** is currently subscription/OAuth only. Direct API-key access for `openai/gpt-5.5` available once OpenAI enables it on the public API. Use `openai/gpt-5.4` for `OPENAI_API_KEY` setups.
> **gpt-5.3-codex-spark** is NOT exposed by OpenClaw (rejected by live API).
> Enabling the OpenAI plugin or selecting `openai-codex/*` does NOT auto-enable the Codex app-server plugin. That requires explicit `embeddedHarness.runtime: "codex"`.
### Setup (API key)
```bash
openclaw onboard --auth-choice openai-api-key
# or
openclaw onboard --openai-api-key "$OPENAI_API_KEY"
```
### Setup (Codex OAuth)
```bash
openclaw onboard --auth-choice openai-codex
# For headless:
openclaw models auth login --provider openai-codex --device-code
openclaw config set agents.defaults.model.primary openai-codex/gpt-5.5
```
### Config examples
```json5
// API key
{
env: { OPENAI_API_KEY: "sk-..." },
agents: { defaults: { model: { primary: "openai/gpt-5.4" } } }
}
// Codex OAuth
{
agents: { defaults: { model: { primary: "openai-codex/gpt-5.5" } } }
}
```
### Image Generation
```json5
{
agents: {
defaults: {
imageGenerationModel: { primary: "openai/gpt-image-2" }
}
}
}
```
- Supports text-to-image and reference-image editing (up to 5 images)
- Sizes: `1024x1024`, `1536x1024`, `1024x1536`, up to `3840x2160`
- `gpt-image-2` is the default for new image workflows. `gpt-image-1` remains usable as an explicit model override but should not be used for new workflows.
### Video Generation
```json5
{
agents: {
defaults: {
videoGenerationModel: { primary: "openai/sora-2" }
}
}
}
```
### Speech Synthesis (TTS)
| Setting | Config path | Default |
|---|---|---|
| Model | `messages.tts.providers.openai.model` | `gpt-4o-mini-tts` |
| Voice | `messages.tts.providers.openai.voice` | `coral` |
| Format | `messages.tts.providers.openai.responseFormat` | `opus`/`mp3` |
Available models: `gpt-4o-mini-tts`, `tts-1`, `tts-1-hd`
Available voices: `alloy`, `ash`, `ballad`, `cedar`, `coral`, `echo`, `fable`, `juniper`, `marin`, `onyx`, `nova`, `sage`, `shimmer`, `verse`
### Speech-to-Text (Batch)
- Default model: `gpt-4o-transcribe`
- Config via `tools.media.audio`
### Streaming Speech-to-Text
- Config: `streaming.provider: "openai"` in Voice Call plugin config
- Enables real-time streaming STT for voice calls
### Realtime Voice
- Config: `realtime.provider: "openai"` in Voice Call plugin config
- Enables Voice Call / Control UI Talk via OpenAI Realtime
### GPT-5 Prompt Overlay
OpenClaw adds a shared GPT-5 prompt contribution that applies **by model id** — so `openai-codex/gpt-5.5`, `openai/gpt-5.4`, `openrouter/openai/gpt-5.5`, `opencode/gpt-5.5`, and other compatible GPT-5 refs all receive the same overlay. Older GPT-4.x models do not.
```json5
{
agents: {
defaults: {
promptOverlays: {
gpt5: { personality: "friendly" } // "friendly" | "on" | "off"
}
}
}
}
```
### Context Window Cap (Codex)
Default cap for `openai-codex/gpt-5.5` is `272000` (native is 1M). Override:
```json5
{
models: {
providers: {
"openai-codex": {
models: [{ id: "gpt-5.5", contextTokens: 160000 }]
}
}
}
}
```
---
## Google (Gemini)
**Provider ID:** `google`, `google-gemini-cli`
**Auth:** `GEMINI_API_KEY` or `GOOGLE_API_KEY`; OAuth via Gemini CLI
**Docs:** https://docs.openclaw.ai/providers/google
### Capabilities
| Capability | Supported |
|---|---|
| Chat completions | Yes |
| Image generation | Yes |
| Music generation | Yes |
| Text-to-speech | Yes |
| Realtime voice (Google Live API) | Yes |
| Image/audio/video understanding | Yes |
| Web search (Grounding) | Yes |
| Thinking/reasoning | Yes (Gemini 2.5+ / Gemini 3+) |
| Gemma 4 models | Yes |
### Setup (API key)
```bash
openclaw onboard --auth-choice gemini-api-key
# or
openclaw onboard --non-interactive --mode local --auth-choice gemini-api-key --gemini-api-key "$GEMINI_API_KEY"
```
### Setup (Gemini CLI OAuth)
```bash
brew install gemini-cli # or: npm install -g @google/gemini-cli
openclaw models auth login --provider google-gemini-cli --set-default
```
⚠️ Unofficial integration — some users report account restrictions.
Default model for `google-gemini-cli`: `google/gemini-3.1-pro-preview` (with `google-gemini-cli` runtime)
### Config example
```json5
{
agents: {
defaults: {
model: { primary: "google/gemini-3.1-pro-preview" }
}
}
}
```
### Image Generation
- Default model: `google/gemini-3.1-flash-image-preview`
- Edit mode: up to 5 input images
```json5
{
agents: {
defaults: {
imageGenerationModel: { primary: "google/gemini-3.1-flash-image-preview" }
}
}
}
```
### Video Generation
- Default model: `google/veo-3.1-fast-generate-preview`
- Duration: 4–8 seconds
```json5
{
agents: {
defaults: {
videoGenerationModel: { primary: "google/veo-3.1-fast-generate-preview" }
}
}
}
```
### Music Generation
- Default model: `google/lyria-3-clip-preview` (MP3 output)
- Also: `google/lyria-3-pro-preview` (supports both MP3 and WAV output)
- Reference inputs: up to 10 images supported
```json5
{
agents: {
defaults: {
musicGenerationModel: { primary: "google/lyria-3-clip-preview" }
}
}
}
```
### Text-to-Speech
- Default model: `gemini-3.1-flash-tts-preview`
- Default voice: `Kore`
- Supports expressive tags: `[whispers]`, `[laughs]`
```json5
{
messages: {
tts: {
auto: "always",
provider: "google",
providers: {
google: { model: "gemini-3.1-flash-tts-preview", voiceName: "Kore" }
}
}
}
}
```
### TTS text-only block (hide from chat, spoken aloud)
```text
Here is the clean reply text.
[[tts:text]][whispers] Here is the spoken version.[[/tts:text]]
```
### Realtime Voice (Google Live API)
```json5
{
plugins: {
entries: {
"voice-call": {
enabled: true,
config: {
realtime: {
enabled: true,
provider: "google",
providers: {
google: {
model: "gemini-2.5-flash-native-audio-preview-12-2025",
voice: "Kore"
}
}
}
}
}
}
}
}
```
### Gemini Cache Reuse
```json5
{
agents: {
defaults: {
models: {
"google/gemini-2.5-pro": {
params: { cachedContent: "cachedContents/prebuilt-context" }
}
}
}
}
}
```
### Thinking (Gemini 3+ / Gemma 4)
Gemini 3+ uses `thinkingLevel` (not `thinkingBudget`). OpenClaw maps automatically.
- **`/think adaptive`**: Gemini 3/3.1 omit `thinkingLevel` so Google can choose; Gemini 2.5 sends `thinkingBudget: -1`
- **Gemma 4** models support thinking; OpenClaw rewrites `thinkingBudget` to `thinkingLevel`. Setting `off` preserves disabled (does NOT map to MINIMAL)
- **`google-gemini-cli/*` model refs are legacy** — use `google/*` model refs + `google-gemini-cli` runtime instead
### Gemini CLI OAuth env vars
- `OPENCLAW_GEMINI_OAUTH_CLIENT_ID`, `OPENCLAW_GEMINI_OAUTH_CLIENT_SECRET` (or `GEMINI_CLI_*` variants)
- If OAuth fails, set `GOOGLE_CLOUD_PROJECT` or `GOOGLE_CLOUD_PROJECT_ID` on the gateway host
---
## OpenRouter
**Provider ID:** `openrouter`
**Auth:** `OPENROUTER_API_KEY`
**Docs:** https://docs.openclaw.ai/providers/openrouter
Unified API routing requests to many models behind a single endpoint.
### Setup
```bash
openclaw onboard --auth-choice openrouter-api-key
```
### Config example
```json5
{
env: { OPENROUTER_API_KEY: "sk-or-..." },
agents: {
defaults: {
model: { primary: "openrouter/auto" }
}
}
}
```
### Model refs
| Model ref | Notes |
|---|---|
| `openrouter/auto` | OpenRouter automatic routing |
| `openrouter/moonshotai/kimi-k2.6` | Kimi K2.6 via MoonshotAI |
| `openrouter/openai/gpt-5.5` | GPT-5.5 via OpenRouter |
| `openrouter/google/gemini-3.1-flash-image-preview` | Gemini image via OpenRouter |
### Image Generation via OpenRouter
```json5
{
agents: {
defaults: {
imageGenerationModel: {
primary: "openrouter/google/gemini-3.1-flash-image-preview"
}
}
}
}
```
### Headers added by OpenClaw
| Header | Value |
|---|---|
| `HTTP-Referer` | `https://openclaw.ai` |
| `X-OpenRouter-Title` | `OpenClaw` |
| `X-OpenRouter-Categories` | `cli-agent` |
### Notes
- Anthropic cache markers preserved on OpenRouter routes
- Thinking/reasoning mapped for supported non-`auto` routes
- Native OpenAI-only request shaping (`serviceTier`, Responses `store`) not forwarded
---
## MiniMax
**Provider ID:** `minimax` (API key), `minimax-portal` (OAuth)
**Auth:** `MINIMAX_API_KEY` or MiniMax Coding Plan OAuth
**Docs:** https://docs.openclaw.ai/providers/minimax
### Built-in catalog
| Model | Type | Description |
|---|---|---|
| `MiniMax-M2.7` | Chat (reasoning) | Default hosted model |
| `MiniMax-M2.7-highspeed` | Chat (faster) | Fast reasoning tier |
| `MiniMax-VL-01` | Vision | Image understanding |
| `image-01` | Image generation | Text-to-image/editing |
| `music-2.6` | Music | Default music model |
| `music-2.5` | Music | Previous tier |
| `music-2.0` | Music | Legacy tier |
| `MiniMax-Hailuo-2.3` | Video | Text-to-video |
### Setup (OAuth — International)
```bash
openclaw onboard --auth-choice minimax-global-oauth
```
### Setup (API key — International)
```bash
openclaw onboard --auth-choice minimax-global-api
```
### Setup (China)
```bash
# OAuth:
openclaw onboard --auth-choice minimax-cn-oauth
# API key:
openclaw onboard --auth-choice minimax-cn-api
```
China routes use `api.minimaxi.com` as base URL.
### Config example (API key)
```json5
{
env: { MINIMAX_API_KEY: "sk-..." },
agents: { defaults: { model: { primary: "minimax/MiniMax-M2.7" } } },
models: {
mode: "merge",
providers: {
minimax: {
baseUrl: "https://api.minimax.io/anthropic",
apiKey: "MINIMAX_API_KEY",
api: "anthropic-messages",
models: [
{
id: "MiniMax-M2.7",
name: "MiniMax M2.7",
reasoning: true,
input: ["text", "image"],
cost: { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0.375 },
contextWindow: 204800,
maxTokens: 131072
}
]
}
}
}
}
```
⚠️ OpenClaw disables MiniMax thinking by default on Anthropic-compatible path to prevent `reasoning_content` leakage.
### Fast Mode
`/fast on` rewrites `MiniMax-M2.7` → `MiniMax-M2.7-highspeed`.
### Image Generation
```json5
{
agents: {
defaults: {
imageGenerationModel: { primary: "minimax/image-01" }
}
}
}
```
- Up to 9 output images
- Aspect ratios: `1:1`, `16:9`, `4:3`, `3:2`, `2:3`, `3:4`, `9:16`, `21:9`
### Web Search
- Uses MiniMax Coding Plan search API
- Key: `MINIMAX_CODE_PLAN_KEY` or `MINIMAX_CODING_API_KEY`
### Troubleshooting
"Unknown model: minimax/MiniMax-M2.7" → Provider not configured. Run `openclaw configure` and choose a MiniMax auth option, or set `MINIMAX_API_KEY`.
---
## DeepSeek
**Provider ID:** `deepseek`
**Auth:** `DEEPSEEK_API_KEY`
**API:** OpenAI-compatible
**Base URL:** `https://api.deepseek.com`
**Docs:** https://docs.openclaw.ai/providers/deepseek
### Built-in catalog
| Model | Context | Max output | Notes |
|---|---|---|---|
| `deepseek/deepseek-v4-flash` | 1,000,000 | 384,000 | Default; V4 thinking-capable |
| `deepseek/deepseek-v4-pro` | 1,000,000 | 384,000 | V4 thinking-capable |
| `deepseek/deepseek-chat` | 131,072 | 8,192 | V3.2 non-thinking |
| `deepseek/deepseek-reasoner` | 131,072 | 65,536 | V3.2 reasoning |
### Setup
```bash
openclaw onboard --auth-choice deepseek-api-key
```
### Config example
```json5
{
env: { DEEPSEEK_API_KEY: "sk-..." },
agents: {
defaults: { model: { primary: "deepseek/deepseek-v4-flash" } }
}
}
```
### Thinking and Tools
V4 thinking sessions replay `reasoning_content` on tool-call follow-ups. OpenClaw handles this automatically.
When thinking is disabled, OpenClaw sends `thinking: { type: "disabled" }` and strips `reasoning_content`.
---
## Groq
**Provider ID:** `groq`
**Auth:** `GROQ_API_KEY`
**API:** OpenAI-compatible
**Docs:** https://docs.openclaw.ai/providers/groq
Ultra-fast inference using custom LPU hardware.
### Setup
```bash
export GROQ_API_KEY="gsk_..."
```
### Config example
```json5
{
env: { GROQ_API_KEY: "gsk_..." },
agents: {
defaults: { model: { primary: "groq/llama-3.3-70b-versatile" } }
}
}
```
### Models
| Model | Notes |
|---|---|
| `groq/llama-3.3-70b-versatile` | General-purpose |
| `groq/llama-3.1-8b-instant` | Fast, lightweight |
| `groq/gemma-2-9b-it` | Compact, efficient |
| `groq/mixtral-8x7b-32768` | MoE architecture |
Run `openclaw models list --provider groq` for current list.
### Audio Transcription
```json5
{
tools: {
media: {
audio: {
models: [{ provider: "groq" }] // uses whisper-large-v3-turbo
}
}
}
}
```
---
## Ollama
**Provider ID:** `ollama`
**Auth:** `OLLAMA_API_KEY` (any value for local)
**API:** Native Ollama API (`/api/chat`)
**Docs:** https://docs.openclaw.ai/providers/ollama
### Modes
| Mode | Description |
|---|---|
| Cloud + Local | Local Ollama host + cloud models via `ollama signin` |
| Cloud only | `https://ollama.com` with `OLLAMA_API_KEY` |
| Local only | Local Ollama server at `http://127.0.0.1:11434` |
⚠️ Do **NOT** use `/v1` URL — breaks tool calling. Use: `baseUrl: "http://host:11434"`.
### Setup (recommended)
```bash
openclaw onboard
# Select Ollama, then choose Cloud + Local / Cloud only / Local only
```
### Implicit discovery (simplest)
```bash
export OLLAMA_API_KEY="ollama-local"
# OpenClaw auto-discovers models from http://127.0.0.1:11434
```
### Config examples
```json5
// Basic local
{
agents: { defaults: { model: { primary: "ollama/gemma4" } } }
}
// Custom host
{
models: {
providers: {
ollama: {
apiKey: "ollama-local",
baseUrl: "http://ollama-host:11434", // no /v1!
api: "ollama"
}
}
}
}
// Cloud only
{
models: {
providers: {
ollama: {
baseUrl: "https://ollama.com",
apiKey: "OLLAMA_API_KEY",
api: "ollama",
models: [
{
id: "kimi-k2.5:cloud",
name: "kimi-k2.5:cloud",
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 128000,
maxTokens: 8192
}
]
}
}
}
}
```
### Pull models
```bash
ollama pull gemma4
ollama pull gpt-oss:20b
ollama pull llama3.3
```
### Vision models
OpenClaw reads `/api/show` to detect vision capability. Models with `vision` capability auto-get `input: ["text", "image"]`.
```json5
{
agents: {
defaults: {
imageModel: { primary: "ollama/qwen2.5vl:7b" }
}
}
}
```
### Web Search
```json5
{
tools: { web: { search: { provider: "ollama" } } }
}
```
Requires `ollama signin`.
### Memory Embeddings
```json5
{
agents: {
defaults: {
memorySearch: { provider: "ollama" } // uses nomic-embed-text, auto-pulled
}
}
}
```
### Reasoning detection
Models with names containing `r1`, `reasoning`, or `think` are auto-marked as reasoning-capable.
### Cost
All Ollama model costs are set to `$0`.
---
## Together AI
**Provider ID:** `together`
**Auth:** `TOGETHER_API_KEY`
**API:** OpenAI-compatible
**Base URL:** `https://api.together.xyz/v1`
**Docs:** https://docs.openclaw.ai/providers/together
### Setup
```bash
openclaw onboard --auth-choice together-api-key
```
### Built-in catalog
| Model ref | Input | Context | Notes |
|---|---|---|---|
| `together/moonshotai/Kimi-K2.5` | text, image | 262,144 | Default; reasoning enabled |
| `together/meta-llama/Llama-4-Scout-17B-16E-Instruct` | text, image | 10,000,000 | Multimodal |
| `together/meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8` | text, image | 20,000,000 | Multimodal |
| `together/deepseek-ai/DeepSeek-V3.1` | text | 131,072 | General |
| `together/deepseek-ai/DeepSeek-R1` | text | 131,072 | Reasoning |
### Video Generation
```json5
{
agents: {
defaults: {
videoGenerationModel: { primary: "together/Wan-AI/Wan2.2-T2V-A14B" }
}
}
}
```
---
## Mistral
**Provider ID:** `mistral`
**Auth:** `MISTRAL_API_KEY`
**Base URL:** `https://api.mistral.ai/v1`
**Docs:** https://docs.openclaw.ai/providers/mistral
### Setup
```bash
openclaw onboard --auth-choice mistral-api-key
```
### Built-in catalog
| Model | Input | Context | Max output | Notes |
|---|---|---|---|---|
| `mistral/mistral-large-latest` | text, image | 262,144 | 16,384 | Default |
| `mistral/mistral-medium-2508` | text, image | 262,144 | 8,192 | Medium 3.1 |
| `mistral/mistral-small-latest` | text, image | 128,000 | 16,384 | Adjustable reasoning |
| `mistral/pixtral-large-latest` | text, image | 128,000 | 32,768 | Pixtral |
| `mistral/codestral-latest` | text | 256,000 | 4,096 | Coding |
| `mistral/magistral-small` | text | 128,000 | 40,000 | Reasoning-enabled |
### Audio Transcription (Voxtral)
```json5
{
tools: {
media: {
audio: {
enabled: true,
models: [{ provider: "mistral", model: "voxtral-mini-latest" }]
}
}
}
}
```
### Voice Call Streaming STT (Voxtral Realtime)
```json5
{
plugins: {
entries: {
"voice-call": {
config: {
streaming: {
enabled: true,
provider: "mistral",
providers: {
mistral: {
apiKey: "MISTRAL_API_KEY",
targetStreamingDelayMs: 800
}
}
}
}
}
}
}
}
```
| Setting | Default |
|---|---|
| Model | `voxtral-mini-transcribe-realtime-2602` |
| Encoding | `pcm_mulaw` |
| Sample rate | `8000` |
| Target delay | `800ms` |
### Reasoning (mistral-small-latest)
Maps OpenClaw thinking level to `reasoning_effort`:
| OpenClaw level | Mistral `reasoning_effort` |
|---|---|
| off / minimal | `none` |
| low / medium / high / adaptive / max | `high` |
### Memory Embeddings
```json5
{
memorySearch: { provider: "mistral" } // uses mistral-embed via /v1/embeddings
}
```
---
## Fireworks
**Provider ID:** `fireworks`
**Auth:** `FIREWORKS_API_KEY`
**API:** OpenAI-compatible
**Base URL:** `https://api.fireworks.ai/inference/v1`
**Default model:** `fireworks/accounts/fireworks/routers/kimi-k2p5-turbo`
**Docs:** https://docs.openclaw.ai/providers/fireworks
### Setup
```bash
openclaw onboard --auth-choice fireworks-api-key
```
### Built-in catalog
| Model ref | Name | Context | Notes |
|---|---|---|---|
| `fireworks/accounts/fireworks/models/kimi-k2p6` | Kimi K2.6 | 262,144 | Latest Kimi; thinking disabled |
| `fireworks/accounts/fireworks/routers/kimi-k2p5-turbo` | Kimi K2.5 Turbo | 256,000 | Default |
### Custom model IDs
```json5
{
agents: {
defaults: {
model: {
primary: "fireworks/accounts/fireworks/routers/kimi-k2p5-turbo"
}
}
}
}
```
OpenClaw strips the `fireworks/` prefix and sends the rest to the Fireworks endpoint.
---
## xAI
**Provider ID:** `xai`
**Auth:** `XAI_API_KEY`
**Docs:** https://docs.openclaw.ai/providers/xai
### Setup
```bash
openclaw onboard --auth-choice xai-api-key
```
### Built-in catalog
| Family | Model IDs |
|---|---|
| Grok 3 | `grok-3`, `grok-3-fast`, `grok-3-mini`, `grok-3-mini-fast` |
| Grok 4 | `grok-4`, `grok-4-0709` |
| Grok 4 Fast | `grok-4-fast`, `grok-4-fast-non-reasoning` |
| Grok 4.1 Fast | `grok-4-1-fast`, `grok-4-1-fast-non-reasoning` |
| Grok 4.20 Beta | `grok-4.20-beta-latest-reasoning`, `grok-4.20-beta-latest-non-reasoning` |
| Grok Code | `grok-code-fast-1` |
### Fast Mode
| Source model | Fast-mode target |
|---|---|
| `grok-3` | `grok-3-fast` |
| `grok-3-mini` | `grok-3-mini-fast` |
| `grok-4` | `grok-4-fast` |
### Capabilities
| Capability | Status |
|---|---|
| Chat / Responses | Yes (xAI Responses API) |
| Web search (`grok` provider) | Yes |
| X search (`x_search` tool) | Yes |
| Remote code execution | Yes |
| Image generation | Yes |
| Video generation | Yes |
| TTS (batch) | Yes |
| Streaming TTS | Not exposed (OpenClaw returns complete buffers) |
| STT (batch) | Yes |
| Streaming STT | Yes (Voice Call WebSocket) |
| Realtime voice | Not exposed yet (different session/WebSocket contract) |
> Image-capable models: `grok-4-fast`, `grok-4-1-fast`, `grok-4.20-beta-*`
> Plugin forward-resolves newer `grok-4*` and `grok-code-fast*` ids when they follow the same API shape.
### Web Search
```bash
openclaw config set tools.web.search.provider grok
```
### Image Generation
- Default model: `xai/grok-imagine-image`
- Also: `xai/grok-imagine-image-pro`
- Aspect ratios: `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `2:3`, `3:2`
- Resolutions: `1K`, `2K`
```json5
{
agents: {
defaults: {
imageGenerationModel: { primary: "xai/grok-imagine-image" }
}
}
}
```
### Video Generation
- Default model: `xai/grok-imagine-video`
- Duration: 1–15 seconds
- Aspect ratios: `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2`, `2:3`
```json5
{
agents: {
defaults: {
videoGenerationModel: { primary: "xai/grok-imagine-video" }
}
}
}
```
### Text-to-Speech
- Voices: `eve` (default), `ara`, `rex`, `sal`, `leo`, `una`
- Formats: `mp3`, `wav`, `pcm`, `mulaw`, `alaw`
```json5
{
messages: {
tts: {
provider: "xai",
providers: {
xai: { voiceId: "eve" }
}
}
}
}
```
### x_search
```json5
{
plugins: {
entries: {
xai: {
config: {
xSearch: {
enabled: true,
model: "grok-4-1-fast",
inlineCitations: true
}
}
}
}
}
}
```
### Code Execution (Remote)
```json5
{
plugins: {
entries: {
xai: {
config: {
codeExecution: {
enabled: true,
model: "grok-4-1-fast"
}
}
}
}
}
}
```
### Streaming STT (Voice Call)
```json5
{
plugins: {
entries: {
"voice-call": {
config: {
streaming: {
enabled: true,
provider: "xai",
providers: {
xai: {
apiKey: "XAI_API_KEY",
endpointingMs: 800,
language: "en"
}
}
}
}
}
}
}
}
```
---
## Perplexity
**Provider ID:** Web search provider (not a model provider)
**Auth:** `PERPLEXITY_API_KEY` (native) or `OPENROUTER_API_KEY` (via OpenRouter)
**Docs:** https://docs.openclaw.ai/providers/perplexity-provider
### Setup
```bash
openclaw configure --section web
# or
openclaw config set plugins.entries.perplexity.config.webSearch.apiKey "pplx-xxxx"
```
### Key prefix routing
| Key prefix | Transport | Features |
|---|---|---|
| `pplx-` | Native Perplexity Search API | Structured results, domain/language/date filters |
| `sk-or-` | OpenRouter (Sonar) | AI-synthesized answers with citations |
### Native API Filtering (pplx- keys only)
| Filter | Description |
|---|---|
| Country | 2-letter code (`us`, `de`) |
| Language | ISO 639-1 (`en`, `fr`) |
| Date range | `day`, `week`, `month`, `year` |
| Domain filters | Allowlist/denylist (max 20 domains) |
| Content budget | `max_tokens`, `max_tokens_per_page` |
---
## Amazon Bedrock
**Provider ID:** `amazon-bedrock`
**Auth:** AWS credentials (env vars, shared config, or instance role)
**Region:** `AWS_REGION` or `AWS_DEFAULT_REGION` (default: `us-east-1`)
**Docs:** https://docs.openclaw.ai/providers/bedrock
### Setup (env vars)
```bash
export AWS_ACCESS_KEY_ID="AKIA..."
export AWS_SECRET_ACCESS_KEY="..."
export AWS_REGION="us-east-1"
```
### Config example
```json5
{
models: {
providers: {
"amazon-bedrock": {
baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com",
api: "bedrock-converse-stream",
auth: "aws-sdk",
models: [
{
id: "us.anthropic.claude-opus-4-6-v1:0",
name: "Claude Opus 4.6 (Bedrock)",
reasoning: true,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 200000,
maxTokens: 8192
}
]
}
}
},
agents: {
defaults: {
model: { primary: "amazon-bedrock/us.anthropic.claude-opus-4-6-v1:0" }
}
}
}
```
### Auto-discovery (requires IAM permissions)
```bash
openclaw config set plugins.entries.amazon-bedrock.config.discovery.enabled true
openclaw config set plugins.entries.amazon-bedrock.config.discovery.region us-east-1
```
Required IAM permissions: `bedrock:InvokeModel`, `bedrock:InvokeModelWithResponseStream`, `bedrock:ListFoundationModels`, `bedrock:ListInferenceProfiles`
### Discovery config
```json5
{
plugins: {
entries: {
"amazon-bedrock": {
config: {
discovery: {
enabled: true,
region: "us-east-1",
providerFilter: ["anthropic", "amazon"],
refreshInterval: 3600,
defaultContextWindow: 32000,
defaultMaxTokens: 4096
}
}
}
}
}
}
```
### Guardrails
```json5
{
plugins: {
entries: {
"amazon-bedrock": {
config: {
guardrail: {
guardrailIdentifier: "abc123",
guardrailVersion: "1",
streamProcessingMode: "sync",
trace: "enabled"
}
}
}
}
}
}
```
### Memory Embeddings
```json5
{
agents: {
defaults: {
memorySearch: {
provider: "bedrock",
model: "amazon.titan-embed-text-v2:0"
}
}
}
}
```
---
## Cloudflare AI Gateway
**Provider ID:** `cloudflare-ai-gateway`
**Auth:** `CLOUDFLARE_AI_GATEWAY_API_KEY` (your Anthropic API key)
**Default model:** `cloudflare-ai-gateway/claude-sonnet-4-6`
**Docs:** https://docs.openclaw.ai/providers/cloudflare-ai-gateway
Routes Anthropic requests through Cloudflare for analytics, caching, controls.
### Setup
```bash
openclaw onboard --auth-choice cloudflare-ai-gateway-api-key
# Prompts for account ID, gateway ID, and API key
```
### Config example
```json5
{
agents: {
defaults: {
model: { primary: "cloudflare-ai-gateway/claude-sonnet-4-6" }
}
}
}
```
### Authenticated gateway header
```json5
{
models: {
providers: {
"cloudflare-ai-gateway": {
headers: {
"cf-aig-authorization": "Bearer <cloudflare-ai-gateway-token>"
}
}
}
}
}
```
---
## Z.AI / GLM (Zhipu)
**Provider ID:** `zai`
**Auth:** `ZAI_API_KEY`
**Models:** `zai/glm-5`, `zai/glm-5.1`
### Auth choices
| Auth choice | Best for |
|---|---|
| `zai-api-key` | Generic API-key (auto-detect endpoint) |
| `zai-coding-global` | Coding Plan (global) |
| `zai-coding-cn` | Coding Plan (China) |
| `zai-global` | General API (global) |
| `zai-cn` | General API (China) |
```bash
openclaw onboard --auth-choice zai-api-key
openclaw config set agents.defaults.model.primary "zai/glm-5.1"
```
---
## Additional Providers (Brief)
### Alibaba Model Studio
- **Provider ID:** `alibaba`
- **Auth:** `MODELSTUDIO_API_KEY`
- Video generation: `wan2.6-t2v` model
- Run: `openclaw onboard --auth-choice alibaba-api-key`
### Cerebras
- **Provider ID:** See docs for `cerebras`
- Fast inference for open models
### SambaNova
- **Provider ID:** See docs for `sambanova`
- Enterprise AI inference
### Cohere
- See `https://docs.openclaw.ai/providers/cohere` for setup
### GCP Vertex AI
- **Provider ID:** varies by model family
- Auth via Google Cloud credentials
- See `https://docs.openclaw.ai/providers/gcp-vertex`
### Azure OpenAI
- Configure via `models.providers.openai.baseUrl` pointing to Azure endpoint
- See `https://docs.openclaw.ai/providers/azure`
### NVIDIA
- **Provider ID:** `nvidia`
- See `https://docs.openclaw.ai/providers/nvidia`
### LM Studio (Local)
- **Provider ID:** `lmstudio`
- See `https://docs.openclaw.ai/providers/lmstudio`
### Hugging Face Inference
- **Provider ID:** `huggingface`
- Auth: `HUGGINGFACE_API_KEY`
### Moonshot AI (Kimi)
- **Provider ID:** `moonshot`
- Auth: `KIMI_API_KEY` or `MOONSHOT_API_KEY`
- Default model: `kimi-k2.6`
- Web search via Kimi
### ElevenLabs
- **Provider ID:** ElevenLabs plugin
- **Auth:** `ELEVENLABS_API_KEY` or `XI_API_KEY`
- **Capabilities:** TTS (`eleven_multilingual_v2`, `eleven_v3`), Batch STT (`scribe_v2`), Streaming STT (`scribe_v2_realtime`)
- Config: `messages.tts.providers.elevenlabs.voiceId`, `modelId`
### fal
- **Auth:** `FAL_KEY`
- Video generation provider (e.g., Wan2.1 models)
- Image generation provider
### Venice (privacy-focused)
- **Provider ID:** `venice`
- See `https://docs.openclaw.ai/providers/venice`
### Qwen Cloud
- **Provider ID:** `qwen`
- Auth: `QWEN_API_KEY`
- Video generation: `wan2.6-t2v`
### Runway
- **Provider ID:** `runway`
- Auth: `RUNWAYML_API_SECRET`
- Video generation: `gen4.5` model
### Arcee AI
- **Provider ID:** `arcee`
- Auth: `ARCEEAI_API_KEY` (direct) or via OpenRouter
- API: OpenAI-compatible, base URL `https://api.arcee.ai/api/v1`
- Models: Trinity family (mixture-of-experts, Apache 2.0)
- Run: `openclaw onboard --auth-choice arceeai-api-key`
### Amazon Bedrock Mantle
- **Provider ID:** `amazon-bedrock-mantle`
- Auth: `AWS_BEARER_TOKEN_BEDROCK` or IAM credential chain
- Routes to third-party models (GPT-OSS, Qwen, Kimi, GLM) through Bedrock
### Chutes
- **Provider ID:** `chutes`
- Auth: `CHUTES_API_KEY` or browser OAuth
- API: OpenAI-compatible, open-source model catalogs
### Claude Max API Proxy
- Routes Claude Max subscription through local proxy
- Uses existing Anthropic Claude Max subscription without API key
### ComfyUI
- **Provider ID:** `comfy`
- Auth: none (local) or `COMFY_API_KEY` / `COMFY_CLOUD_API_KEY` (Comfy Cloud)
- Models: `comfy/workflow`
- Image generation via ComfyUI workflows
### GitHub Copilot
- **Provider ID:** `github-copilot`
- Auth: GitHub account/plan (OAuth)
- Two modes: built-in provider or via Copilot Extension
### Gradium
- **Provider ID:** `gradium` (bundled TTS provider)
- Auth: `GRADIUM_API_KEY`
- Capabilities: TTS (normal audio, voice-note Opus, 8 kHz u-law telephony)
### Inferrs
- Local model serving behind OpenAI-compatible backend
- Not a dedicated provider plugin — uses `openai-completions` API
### Kilocode (Kilo Gateway)
- **Provider ID:** `kilocode`
- Unified API routing to many models behind single endpoint
- API: OpenAI-compatible
### LiteLLM
- **Provider ID:** `litellm`
- Open-source LLM gateway (100+ model providers)
- Cost tracking, model routing, automatic failover
- API: OpenAI-compatible proxy
### OpenCode Go
- **Provider ID:** `opencode-go`
- Auth: `OPENCODE_API_KEY` (same as OpenCode Zen catalog)
- Go catalog within OpenCode ecosystem
### Qianfan (Baidu)
- **Provider ID:** `qianfan`
- Auth: `QIANFAN_API_KEY`
- API: OpenAI-compatible
### SGLang
- **Provider ID:** `sglang`
- Auth: `SGLANG_API_KEY` (any value for local)
- API: OpenAI-compatible, auto-discovers available models
### Synthetic
- **Provider ID:** `synthetic`
- Auth: `SYNTHETIC_API_KEY`
- Base URL: `https://api.synthetic.new/anthropic`
- API: Anthropic-compatible
### Tencent Cloud (TokenHub)
- **Provider ID:** `tencent-tokenhub`
- Access to Tencent Hy3 preview models
- API: OpenAI-compatible
### Vercel AI Gateway
- **Provider ID:** `vercel-ai-gateway`
- Auth: `AI_GATEWAY_API_KEY`
- Auto-discovers models via `/v1/models`
### vLLM
- **Provider ID:** `vllm`
- Auth: `VLLM_API_KEY` (any value for local)
- API: OpenAI-compatible (`openai-completions`)
- Auto-discovers available models
### Volcengine (Doubao)
- **Providers:** `volcengine` (general) + `volcengine-plan` (coding)
- Auth: `VOLCANO_ENGINE_API_KEY`
- Access to Doubao models and third-party models
### Vydra
- **Provider ID:** `vydra`
- Auth: `VYDRA_API_KEY`
- Base URL: `https://www.vydra.ai/api/v1` (use `www` to avoid redirect auth issues)
### Xiaomi MiMo
- **Provider ID:** `xiaomi`
- Auth: `XIAOMI_API_KEY`
- API: OpenAI-compatible
---
## Provider Configuration: Model Selection & Failover
```json5
{
agents: {
defaults: {
model: {
primary: "anthropic/claude-opus-4-6",
fallbacks: ["openai/gpt-5.4", "minimax/MiniMax-M2.7"]
}
}
}
}
```
## Environment Variables Quick Reference
| Provider | Env Var |
|---|---|
| Anthropic | `ANTHROPIC_API_KEY` |
| OpenAI | `OPENAI_API_KEY` |
| Google/Gemini | `GEMINI_API_KEY` or `GOOGLE_API_KEY` |
| OpenRouter | `OPENROUTER_API_KEY` |
| MiniMax | `MINIMAX_API_KEY` |
| DeepSeek | `DEEPSEEK_API_KEY` |
| Groq | `GROQ_API_KEY` |
| Ollama | `OLLAMA_API_KEY` (any value for local) |
| Together | `TOGETHER_API_KEY` |
| Mistral | `MISTRAL_API_KEY` |
| Fireworks | `FIREWORKS_API_KEY` |
| xAI | `XAI_API_KEY` |
| Perplexity | `PERPLEXITY_API_KEY` |
| AWS Bedrock | `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY` + `AWS_REGION` |
| Cloudflare AI GW | `CLOUDFLARE_AI_GATEWAY_API_KEY` |
| Runway | `RUNWAYML_API_SECRET` |
| ElevenLabs | `ELEVENLABS_API_KEY` or `XI_API_KEY` |
| Brave Search | `BRAVE_API_KEY` |
| fal | `FAL_KEY` |
| Arcee AI | `ARCEEAI_API_KEY` |
| Chutes | `CHUTES_API_KEY` |
| GitHub Copilot | GitHub OAuth |
| Gradium | `GRADIUM_API_KEY` |
| Kilocode | See provider docs |
| Qianfan (Baidu) | `QIANFAN_API_KEY` |
| SGLang | `SGLANG_API_KEY` |
| Synthetic | `SYNTHETIC_API_KEY` |
| Vercel AI GW | `AI_GATEWAY_API_KEY` |
| Volcengine | `VOLCANO_ENGINE_API_KEY` |
| Vydra | `VYDRA_API_KEY` |
| Xiaomi | `XIAOMI_API_KEY` |
FILE:references/06-tools.md
# OpenClaw Tools Reference
Tools are typed functions the agent can invoke. OpenClaw ships built-in tools; plugins can register additional ones.
## Table of Contents
- [Tool Architecture](#tool-architecture)
- [Built-in Tools Overview](#built-in-tools-overview)
- [exec](#exec)
- [code_execution](#code_execution)
- [browser](#browser)
- [message (Agent Send)](#message-agent-send)
- [image_generate](#image_generate)
- [video_generate](#video_generate)
- [music_generate](#music_generate)
- [tts](#tts)
- [web_fetch](#web_fetch)
- [sessions_spawn (Sub-agents)](#sessions_spawn-sub-agents)
- [web_search](#web_search)
- [Tool Configuration](#tool-configuration)
- [gateway tool](#gateway-tool)
- [memory_search / memory_get](#memory_search--memory_get)
- [session_status](#session_status)
- [Plugin-provided tools (examples)](#plugin-provided-tools-examples)
## Tool Architecture
- **Tools**: typed functions the agent calls (`exec`, `browser`, `web_search`, `message`)
- **Skills**: markdown files (`SKILL.md`) injected into system prompt giving context and guidance
- **Plugins**: packages that register capabilities (channels, providers, tools, skills, etc.)
---
## Built-in Tools Overview
| Tool | What it does | Page |
|---|---|---|
| `exec` / `process` | Run shell commands, manage background processes | exec |
| `code_execution` | Run sandboxed remote Python analysis | code-execution |
| `browser` | Control a Chromium browser | browser |
| `web_search` / `x_search` / `web_fetch` | Search web, X posts, fetch pages | web / web-fetch |
| `read` / `write` / `edit` | File I/O in the workspace | — |
| `apply_patch` | Multi-hunk file patches | apply-patch |
| `message` | Send messages across all channels | agent-send |
| `canvas` | Drive node Canvas (present, eval, snapshot) | — |
| `nodes` | Discover and target paired devices | — |
| `cron` / `gateway` | Manage scheduled jobs; inspect/patch/restart gateway | — |
| `image` / `image_generate` | Analyze or generate images | image-generation |
| `music_generate` | Generate music tracks | music-generation |
| `video_generate` | Generate videos | video-generation |
| `tts` | One-shot text-to-speech | tts |
| `sessions_*` / `subagents` / `agents_list` | Session management, sub-agent orchestration | subagents |
| `session_status` | Lightweight status readback | session-tool |
| `memory_search` / `memory_get` | Search memory files | memory |
---
## exec
Run shell commands in the workspace. Supports foreground + background execution via `process`.
**Docs:** https://docs.openclaw.ai/tools/exec
### Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| `command` | string | required | Shell command to run |
| `workdir` | string | cwd | Working directory |
| `env` | object | — | Key/value env overrides |
| `yieldMs` | number | 10000 | Auto-background after this delay (ms) |
| `background` | boolean | false | Background immediately |
| `timeout` | number | 1800 | Kill after this many seconds |
| `pty` | boolean | false | Run in pseudo-terminal (for TTY-only CLIs) |
| `host` | string | `auto` | Where to execute: `auto`, `sandbox`, `gateway`, `node` |
| `security` | string | `deny` for sandbox; `full` for gateway+node | `deny`, `allowlist`, or `full` |
| `ask` | string | — | `off`, `on-miss`, or `always` |
| `node` | string | — | Node id/name when `host=node` |
| `elevated` | boolean | false | Escape sandbox onto configured host path |
### host resolution
- `auto` → `sandbox` when sandbox runtime is active; `gateway` otherwise
- `host=node` requires a paired node device
- `elevated` escapes the sandbox; only when elevated access is enabled
### Examples
```json
// Foreground
{ "tool": "exec", "command": "ls -la" }
// Background + poll
{ "tool": "exec", "command": "npm run build", "yieldMs": 1000 }
{ "tool": "process", "action": "poll", "sessionId": "<id>" }
// PTY for interactive CLIs
{ "tool": "exec", "command": "claude", "pty": true }
```
### process actions
| Action | Description |
|---|---|
| `list` | List background sessions |
| `poll` | Check status of a session |
| `log` | Get output from a session |
| `write` | Write to session stdin |
| `send-keys` | Send keys (tmux-style): `["Enter"]`, `["C-c"]`, `["Up","Up","Enter"]` |
| `submit` | Send CR only |
| `paste` | Paste text (bracketed by default) |
| `kill` | Terminate a session |
### Config
```json5
{
tools: {
exec: {
host: "auto", // default routing
security: "full", // deny | allowlist | full
ask: "off", // off | on-miss | always
notifyOnExit: true, // heartbeat on exit
approvalRunningNoticeMs: 10000,
pathPrepend: ["~/bin", "/opt/oss/bin"],
safeBins: ["cat", "grep"], // stdin-only safe binaries (NOT interpreters)
safeBinTrustedDirs: ["/opt/custom/bin"], // extra trusted directories for safe-bin path checks (built-in defaults: /bin and /usr/bin)
safeBinProfiles: { // optional custom argv policy per safe bin
"grep": { minPositional: 1, maxPositional: 2, allowedValueFlags: ["-i", "-n"], deniedFlags: ["-r"] }
},
strictInlineEval: false, // when true, inline eval always needs approval
applyPatch: { // enabled defaults to true; only set when you want to disable it
workspaceOnly: true,
allowModels: ["gpt-5.5"]
}
}
}
}
```
`openclaw security audit` warns when interpreter/runtime `safeBins` entries are missing explicit profiles, and `openclaw doctor --fix` can scaffold missing `safeBinProfiles` entries.
### Session overrides
```
/exec host=auto security=allowlist ask=on-miss node=mac-1
```
### PATH handling
- `host=gateway`: merges login-shell PATH; `env.PATH` overrides rejected
- `host=sandbox`: runs `sh -lc` (login shell); `tools.exec.pathPrepend` applies
- `host=node`: only non-blocked env overrides sent; `env.PATH` rejected
### Security rules
- Manual allowlist matches **resolved binary paths only** (no basename matches)
- In `security=allowlist`: shell commands auto-allowed only if every pipeline segment is allowlisted or a safe bin
- Chaining (`;`, `&&`, `||`) rejected in allowlist mode unless every segment qualifies
- `tools.exec.safeBins`: for stdin-only stream filters — NOT for interpreters
- Do not add `python3`, `node`, `ruby`, `bash` to safeBins — use explicit allowlist entries
### Approval behavior
When approvals are required, exec returns immediately with `status: "approval-pending"` and an approval id. Once approved (or denied / timed out), the Gateway emits system events (`Exec finished` / `Exec denied`). A single `Exec running` notice is emitted if the command runs longer than `approvalRunningNoticeMs`. On channels with native approval cards/buttons, rely on that native UI first; only include a manual `/approve` command when the tool result explicitly says chat approvals are unavailable.
### apply_patch subtool
Multi-file patch tool for OpenAI/Codex models. Enabled by default; only configure when you want to disable it or restrict models:
```json5
{
tools: {
exec: {
applyPatch: { // enabled defaults to true
workspaceOnly: true, // default: true
allowModels: ["gpt-5.5"]
}
}
}
}
```
### Gotchas
- `OPENCLAW_SHELL=exec` is set in spawned environments for shell/profile detection
- Sandboxing is OFF by default — `host=auto` resolves to `gateway`
- For long work: start once, rely on automatic completion wake; use `process` for status
- Do not use sleep loops or repeated polling for background work
- Use cron for scheduled/delayed work (not exec sleep patterns)
- On non-Windows: uses `SHELL` env var; if fish, prefers `bash`/`sh`
- On Windows: prefers PowerShell 7 (`pwsh`), fallback to Windows PowerShell 5.1
---
## code_execution
Run sandboxed remote Python analysis on xAI's Responses API.
**Docs:** https://docs.openclaw.ai/tools/code-execution
Different from `exec`: runs remotely in xAI sandbox, not locally.
**Use for:** calculations, tabulation, quick statistics, chart-style analysis, analyzing data from `x_search`/`web_search`.
**Do NOT use for:** local files, your shell, your repo, paired devices (use `exec`).
### Setup
Requires `XAI_API_KEY` or `plugins.entries.xai.config.webSearch.apiKey`.
```json5
{
plugins: {
entries: {
xai: {
config: {
codeExecution: {
enabled: true,
model: "grok-4-1-fast",
maxTurns: 2,
timeoutSeconds: 30
}
}
}
}
}
}
```
Takes single `task` parameter; treat as ephemeral analysis, not persistent notebook.
---
## browser
OpenClaw-managed Chrome/Brave/Edge/Chromium profile isolated from personal browser.
**Docs:** https://docs.openclaw.ai/tools/browser
### Profiles
| Profile | Description |
|---|---|
| `openclaw` | Managed, isolated browser (default) |
| `user` | Built-in Chrome MCP attach for real signed-in Chrome |
| custom | Any named profile you define |
A bundled `browser-automation` skill provides the full operating loop (snapshot→act→resnapshot, stale-ref recovery, login/2FA/captcha blocker reporting).
### Quick start
```bash
openclaw browser --browser-profile openclaw status
openclaw browser --browser-profile openclaw start
openclaw browser --browser-profile openclaw open https://example.com
openclaw browser --browser-profile openclaw snapshot
```
### Configuration
```json5
{
browser: {
enabled: true,
defaultProfile: "openclaw",
color: "#FF4500", // tints the browser UI so you can see which profile is active
headless: false,
noSandbox: false,
attachOnly: false,
executablePath: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
remoteCdpTimeoutMs: 1500,
remoteCdpHandshakeTimeoutMs: 3000,
localLaunchTimeoutMs: 15000, // local managed Chrome discovery timeout
localCdpReadyTimeoutMs: 8000, // local managed post-launch CDP readiness timeout
actionTimeoutMs: 60000, // default browser act timeout
tabCleanup: {
enabled: true,
idleMinutes: 120, // 0 disables idle cleanup
maxTabsPerSession: 8, // 0 disables per-session cap
sweepMinutes: 5
},
ssrfPolicy: {
// dangerouslyAllowPrivateNetwork: true // opt-in only
// hostnameAllowlist: ["*.example.com"]
},
profiles: {
openclaw: { cdpPort: 18800, color: "#FF4500" }, // port derived from gateway.port + 2 (default gateway port = 18789)
work: { cdpPort: 18801, color: "#0066CC" },
user: {
driver: "existing-session",
attachOnly: true,
color: "#00AA00"
},
brave: {
driver: "existing-session",
attachOnly: true,
userDataDir: "~/Library/Application Support/BraveSoftware/Brave-Browser",
color: "#FB542B"
},
remote: { cdpUrl: "http://10.0.0.42:9222", color: "#00AA00" },
browserless: {
cdpUrl: "wss://production-sfo.browserless.io?token=<KEY>",
color: "#00AA00"
},
browserbase: {
cdpUrl: "wss://connect.browserbase.com?apiKey=<KEY>",
color: "#F97316"
}
}
}
}
```
### Setting executable path
```bash
openclaw config set browser.executablePath "/usr/bin/google-chrome"
```
### Plugin control
```json5
// Disable browser plugin (to replace with another)
{
plugins: { entries: { browser: { enabled: false } } }
}
// Restore (if plugins.allow was blocking it)
{
plugins: { allow: ["telegram", "browser"] }
}
```
### Tool parameters
| Action | Description |
|---|---|
| `status` | Check browser status |
| `start` | Launch the browser |
| `stop` | Stop the browser (for attach-only and remote CDP profiles, closes the active control session and releases Playwright/CDP emulation overrides such as viewport, color scheme, and locale — even though no browser process was launched) |
| `profiles` | List profiles |
| `tabs` | List open tabs |
| `open` | Open a URL |
| `focus` | Focus a tab |
| `close` | Close a tab |
| `snapshot` | Capture accessibility tree snapshot |
| `screenshot` | Capture screenshot |
| `navigate` | Navigate to URL |
| `console` | Get console logs |
| `pdf` | Export PDF |
| `upload` | Upload files |
| `dialog` | Handle dialogs |
| `act` | Perform actions (click, type, press, hover, drag, select, fill, resize, wait, evaluate, close) |
### act kinds
| Kind | Description |
|---|---|
| `click` | Click an element |
| `type` | Type text |
| `press` | Press a key |
| `hover` | Hover over element |
| `drag` | Drag from one element to another |
| `select` | Select dropdown option |
| `fill` | Fill an input field |
| `resize` | Resize the browser window |
| `wait` | Wait (use sparingly) |
| `evaluate` | Run JavaScript |
| `close` | Close tab/popup |
### Node browser proxy (zero-config for remote gateways)
- Runs on node host, accessible via proxy without extra browser config
- Configure `nodeHost.browserProxy.allowProfiles` to limit accessible profiles
- Disable on node: `nodeHost.browserProxy.enabled=false`
- Disable on gateway: `gateway.nodes.browser.mode="off"`
### CDP URL shapes supported
- `http(s)://host[:port]` — HTTP discovery via `/json/version`
- `ws://host[:port]/devtools/<kind>/<id>` — direct WebSocket
- `ws://host[:port]` — bare WebSocket root (tries HTTP discovery first, fallback to WebSocket)
### Security
- Browser control is loopback-only
- Shared-secret auth only (gateway token, `x-openclaw-password`, HTTP Basic)
- `browser.ssrfPolicy.dangerouslyAllowPrivateNetwork` is OFF by default
- Remote CDP URLs are SSRF-guarded
- **Tailscale exception:** Tailscale Serve identity headers and `gateway.auth.mode: "trusted-proxy"` do NOT authenticate the standalone loopback browser HTTP API. Only shared-secret auth (gateway token bearer, `x-openclaw-password`, or HTTP Basic) is accepted for this API.
### Existing-session (Chrome DevTools MCP)
```json5
{
browser: {
profiles: {
brave: {
driver: "existing-session",
attachOnly: true,
userDataDir: "~/Library/Application Support/BraveSoftware/Brave-Browser",
color: "#FB542B"
}
}
}
}
```
Enable remote debugging in the browser: chrome/brave/edge → `inspect/#remote-debugging`
### Gotchas
- `plugins.allow` must include `browser` — tool policy runs after load
- Browser version 144+ required for Chromium-based browsers
- `driver: "existing-session"` uses Chrome DevTools MCP — do NOT set `cdpUrl`
- SSRF policy blocks private network by default; use `dangerouslyAllowPrivateNetwork: true` for LAN
- Browser config changes require gateway restart
---
## message (Agent Send)
Send messages across all channels.
**Docs:** https://docs.openclaw.ai/tools/agent-send
The `message` tool sends to channels. `openclaw agent` runs a single agent turn from CLI.
### CLI: openclaw agent
```bash
# Simple turn
openclaw agent --message "What is the weather today?"
# Target agent
openclaw agent --agent ops --message "Summarize logs"
# Target by phone
openclaw agent --to +15555550123 --message "Status update"
# Reuse session
openclaw agent --session-id abc123 --message "Continue"
# Deliver to channel
openclaw agent --to +15555550123 --message "Report ready" --deliver
# Deliver to Slack
openclaw agent --agent ops --message "Generate report" \
--deliver --reply-channel slack --reply-to "#reports"
```
### CLI flags
| Flag | Description |
|---|---|
| `--message <text>` | Message to send (required) |
| `--to <dest>` | Derive session key from target (phone, chat id) |
| `--agent <id>` | Target a configured agent |
| `--session-id <id>` | Reuse existing session |
| `--local` | Force local embedded runtime |
| `--deliver` | Send reply to a chat channel |
| `--channel <name>` | Delivery channel |
| `--reply-to <target>` | Delivery target override |
| `--reply-channel <name>` | Delivery channel override |
| `--reply-account <id>` | Delivery account id override |
| `--thinking <level>` | Set thinking level |
| `--verbose <on\|full\|off>` | Verbose output |
| `--timeout <seconds>` | Agent timeout override |
| `--json` | Structured JSON output |
### message tool parameters (in-agent)
| Parameter | Description |
|---|---|
| `action` | `send`, `poll`, `react`, `delete`, `edit`, `topic-create`, `topic-edit` |
| `target` | Channel/user id or name |
| `message` | Message text |
| `channel` | Channel type (`telegram`, `whatsapp`, `discord`, etc.) |
| `media` | Media URL or local path |
| `buffer` | Base64 payload (data: URL supported) |
| `filePath` | Local file path |
| `caption` | Media caption |
| `replyTo` | Message ID to reply to |
| `asVoice` | Send as voice message (boolean) |
| `asDocument` | Send image/GIF as document (telegram only) |
| `silent` | Send without notification |
| `buttons` | Button rows for inline keyboards |
---
## image_generate
Generate or edit images using configured providers.
**Docs:** https://docs.openclaw.ai/tools/image-generation
### Quick start
```json5
{
agents: {
defaults: {
imageGenerationModel: { primary: "openai/gpt-image-2" }
}
}
}
```
Ask: *"Generate an image of a friendly robot mascot."*
### Supported providers
| Provider | Default model | Edit support | Auth |
|---|---|---|---|
| OpenAI | `gpt-image-2` | Yes (up to 4 images) | `OPENAI_API_KEY` or Codex OAuth |
| OpenRouter | `google/gemini-3.1-flash-image-preview` | Yes (up to 5 images) | `OPENROUTER_API_KEY` |
| Google | `gemini-3.1-flash-image-preview` | Yes | `GEMINI_API_KEY` |
| fal | `fal-ai/flux/dev` | Yes | `FAL_KEY` |
| MiniMax | `image-01` | Yes (1 image, subject ref) | `MINIMAX_API_KEY` |
| ComfyUI | `workflow` | Yes (1 image, workflow) | `COMFY_API_KEY` |
| Vydra | `grok-imagine` | No | `VYDRA_API_KEY` |
| xAI | `grok-imagine-image` | Yes (up to 5 images) | `XAI_API_KEY` |
### Tool parameters
| Parameter | Type | Description |
|---|---|---|
| `prompt` | string (required) | Image description |
| `action` | `generate` \| `list` | `list` to inspect providers |
| `model` | string | Provider/model override (`openai/gpt-image-2`) |
| `image` | string | Single reference image path/URL |
| `images` | string[] | Multiple reference images (up to 5) |
| `size` | string | `1024x1024`, `1536x1024`, `3840x2160`, etc. |
| `aspectRatio` | string | `1:1`, `2:3`, `3:2`, `3:4`, `4:3`, `4:5`, `5:4`, `9:16`, `16:9`, `21:9` |
| `resolution` | string | `1K`, `2K`, `4K` |
| `quality` | string | `low`, `medium`, `high`, `auto` |
| `outputFormat` | string | `png`, `jpeg`, `webp` |
| `count` | number | Number of images (1–4) |
| `timeoutMs` | number | Timeout in ms |
| `filename` | string | Output filename hint |
| `openai` | object | OpenAI-only: `background`, `moderation`, `outputCompression`, `user` |
### Provider selection order
1. `model` parameter in tool call
2. `imageGenerationModel.primary` in config
3. `imageGenerationModel.fallbacks` in order
4. Auto-detection (auth-backed providers)
### Configuration
```json5
{
agents: {
defaults: {
imageGenerationModel: {
primary: "openai/gpt-image-2",
fallbacks: [
"openrouter/google/gemini-3.1-flash-image-preview",
"google/gemini-3.1-flash-image-preview",
"fal/fal-ai/flux/dev"
]
}
}
}
}
```
### List providers at runtime
```
/tool image_generate action=list
```
### OpenAI-specific options
```json
{
"quality": "low",
"outputFormat": "jpeg",
"openai": {
"background": "opaque", // transparent | opaque | auto
"moderation": "low",
"outputCompression": 60, // for JPEG/WebP
"user": "end-user-42"
}
}
```
`background: "transparent"` requires `outputFormat: "png"` or `"webp"`.
### Examples
```
# Generate 4K landscape
/tool image_generate model=openai/gpt-image-2 prompt="editorial poster" size=3840x2160 count=1
# Edit reference image
/tool image_generate model=openai/gpt-image-2 prompt="Replace background with studio" image=/path/to/ref.png size=1024x1536
# Multi-reference edit
/tool image_generate model=openai/gpt-image-2 prompt="Combine character from first, palette from second" images='["/char.png","/palette.jpg"]'
```
### Provider capabilities
| Capability | OpenAI | Google | fal | MiniMax | ComfyUI | xAI |
|---|---|---|---|---|---|---|
| Max generate | 4 | 4 | 4 | 9 | workflow | 4 |
| Edit/reference | Up to 5 | Up to 5 | 1 | 1 | 1 | Up to 5 |
| Size control | Yes (4K) | Yes | Yes | No | No | No |
| Aspect ratio | No | Yes | Yes | Yes | No | Yes |
| Resolution 1K/2K/4K | No | Yes | Yes | No | No | Yes (1K/2K) |
---
## video_generate
Generate videos from text prompts, reference images, or existing videos.
**Docs:** https://docs.openclaw.ai/tools/video-generation
### Supported providers (14 total)
| Provider | Default model | Auth |
|---|---|---|
| Alibaba | `wan2.6-t2v` | `MODELSTUDIO_API_KEY` |
| BytePlus (1.0) | `seedance-1-0-pro-250528` | `BYTEPLUS_API_KEY` |
| BytePlus Seedance 1.5 | `seedance-1-5-pro-251215` | `BYTEPLUS_API_KEY` |
| BytePlus Seedance 2.0 | `dreamina-seedance-2-0-260128` | `BYTEPLUS_API_KEY` |
| ComfyUI | `workflow` | `COMFY_API_KEY` |
| fal | `fal-ai/minimax/video-01-live` | `FAL_KEY` |
| Google | `veo-3.1-fast-generate-preview` | `GEMINI_API_KEY` |
| MiniMax | `MiniMax-Hailuo-2.3` | `MINIMAX_API_KEY` |
| OpenAI | `sora-2` | `OPENAI_API_KEY` |
| Qwen | `wan2.6-t2v` | `QWEN_API_KEY` |
| Runway | `gen4.5` | `RUNWAYML_API_SECRET` |
| Together | `Wan-AI/Wan2.2-T2V-A14B` | `TOGETHER_API_KEY` |
| Vydra | `veo3` | `VYDRA_API_KEY` |
| xAI | `grok-imagine-video` | `XAI_API_KEY` |
### Runtime modes
| Mode | When |
|---|---|
| `generate` | Text-to-video (no reference media) |
| `imageToVideo` | Request includes image reference(s) |
| `videoToVideo` | Request includes video reference(s) |
### Tool parameters
| Parameter | Type | Description |
|---|---|---|
| `prompt` | string (required for generate) | Video description |
| `action` | string | `generate`, `status`, `list` |
| `model` | string | Provider/model override |
| `image` | string | Single reference image |
| `images` | string[] | Multiple reference images (up to 9) |
| `imageRoles` | string[] | Role per image: `first_frame`, `last_frame`, `reference_image` |
| `video` | string | Single reference video |
| `videos` | string[] | Multiple reference videos (up to 4) |
| `videoRoles` | string[] | Role per video: `reference_video` |
| `audioRef` | string | Reference audio |
| `audioRefs` | string[] | Multiple reference audios (up to 3) |
| `audioRoles` | string[] | Role per audio: `reference_audio` |
| `aspectRatio` | string | `1:1`, `2:3`, `3:2`, `4:3`, `9:16`, `16:9`, `21:9`, `adaptive` |
| `resolution` | string | `480P`, `720P`, `768P`, `1080P` |
| `durationSeconds` | number | Target duration (rounded to nearest supported) |
| `size` | string | Size hint |
| `audio` | boolean | Enable generated audio in output |
| `watermark` | boolean | Toggle provider watermarking |
| `filename` | string | Output filename hint |
| `timeoutMs` | number | Request timeout |
| `providerOptions` | object | Provider-specific options (`{"seed": 42, "draft": true}`) |
### Configuration
```json5
{
agents: {
defaults: {
videoGenerationModel: {
primary: "google/veo-3.1-fast-generate-preview",
fallbacks: ["together/Wan-AI/Wan2.2-T2V-A14B"]
}
}
}
}
```
### How video generation works
1. Agent calls `video_generate` → OpenClaw submits request, returns task ID immediately
2. Provider processes in background (30 seconds to 5 minutes)
3. When ready, OpenClaw wakes same session with completion event
4. Agent posts finished video to conversation
### Check status
```bash
openclaw tasks list
openclaw tasks show <taskId>
openclaw tasks cancel <taskId>
```
Or in-agent:
```
video_generate action=status
```
### Task states
1. **queued** — waiting for provider to accept
2. **running** — provider processing
3. **succeeded** — video ready; agent wakes and posts
4. **failed** — provider error or timeout
### List providers
```
video_generate action=list
```
### `adaptive` aspect ratio
Some providers (BytePlus Seedance) use `adaptive` to auto-detect ratio from input image. Not supported by all providers.
---
## music_generate
Generate music or audio using configured providers.
**Docs:** https://docs.openclaw.ai/tools/music-generation
### Supported providers
| Provider | Default model | Reference inputs | Auth |
|---|---|---|---|
| ComfyUI | `workflow` | Up to 1 image | `COMFY_API_KEY` |
| Google | `lyria-3-clip-preview` | Up to 10 images | `GEMINI_API_KEY` |
| MiniMax | `music-2.6` | None | `MINIMAX_API_KEY` |
### Configuration
```json5
{
agents: {
defaults: {
musicGenerationModel: { primary: "google/lyria-3-clip-preview" }
}
}
}
```
Also `google/lyria-3-pro-preview` supports both MP3 and WAV. Works as async background task (same pattern as video_generate).
---
## tts
One-shot text-to-speech conversion.
**Docs:** https://docs.openclaw.ai/tools/tts
### Supported providers
- **ElevenLabs** (`ELEVENLABS_API_KEY` or `XI_API_KEY`)
- **Google Gemini** (`GEMINI_API_KEY`)
- **Gradium** (`GRADIUM_API_KEY`)
- **Local CLI** (no API key — runs a configured local TTS command)
- **Microsoft** (no API key — uses `node-edge-tts`, hosted service, best-effort)
- **MiniMax** (`MINIMAX_API_KEY`; also accepts Token Plan via `MINIMAX_OAUTH_TOKEN`, `MINIMAX_CODE_PLAN_KEY`, `MINIMAX_CODING_API_KEY`)
- **OpenAI** (`OPENAI_API_KEY`)
- **Vydra** (`VYDRA_API_KEY`)
- **xAI** (`XAI_API_KEY`)
- **Xiaomi MiMo** (`XIAOMI_API_KEY`)
### Enabling TTS
Auto-TTS is **OFF by default**. Enable:
```json5
{
messages: {
tts: {
auto: "always", // off | always | inbound | tagged
provider: "elevenlabs"
}
}
}
```
Or toggle in chat: `/tts on`
### `auto` modes
| Value | Description |
|---|---|
| `off` | Disabled |
| `always` | Always generate audio |
| `inbound` | Only after inbound voice message |
| `tagged` | Only when reply includes `[[tts:...]]` directives |
### Config examples
```json5
// OpenAI primary with ElevenLabs fallback
{
messages: {
tts: {
auto: "always",
provider: "openai",
providers: {
openai: {
model: "gpt-4o-mini-tts",
voice: "coral"
},
elevenlabs: {
voiceId: "voice_id",
modelId: "eleven_multilingual_v2",
voiceSettings: {
stability: 0.5,
similarityBoost: 0.75,
speed: 1.0
}
}
}
}
}
}
// Microsoft (no API key)
{
messages: {
tts: {
auto: "always",
provider: "microsoft",
providers: {
microsoft: {
voice: "en-US-MichelleNeural",
lang: "en-US",
outputFormat: "audio-24khz-48kbitrate-mono-mp3",
rate: "+10%",
pitch: "-5%"
}
}
}
}
}
// MiniMax
{
messages: {
tts: {
auto: "always",
provider: "minimax",
providers: {
minimax: {
model: "speech-2.8-hd",
voiceId: "English_expressive_narrator",
speed: 1.0,
vol: 1.0,
pitch: 0
}
}
}
}
}
// Google Gemini
{
messages: {
tts: {
auto: "always",
provider: "google",
providers: {
google: {
model: "gemini-3.1-flash-tts-preview",
voiceName: "Kore"
}
}
}
}
}
// xAI
{
messages: {
tts: {
auto: "always",
provider: "xai",
providers: {
xai: {
voiceId: "eve",
language: "en",
responseFormat: "mp3",
speed: 1.0
}
}
}
}
}
```
### Key config fields
| Field | Description |
|---|---|
| `auto` | `off\|always\|inbound\|tagged` |
| `provider` | Provider id |
| `summaryModel` | Model for auto-summarizing long replies |
| `maxTextLength` | Hard cap for TTS input |
| `timeoutMs` | Request timeout |
| `prefsPath` | Local prefs JSON path |
| `mode` | `final` (default) or `all` (includes tool/block replies) |
### Model-driven directives
Model can emit TTS directives for per-reply overrides:
```
Here you go.
[[tts:voiceId=pMsXgVXv3BLzUgSXRplE model=eleven_v3 speed=1.1]]
[[tts:text]](laughs) Read the song once more.[[/tts:text]]
```
Available directive keys: `provider`, `voice`, `voiceId`, `voiceName`, `model`, `stability`, `similarityBoost`, `style`, `speed`, `useSpeakerBoost`, `vol`, `pitch`, `applyTextNormalization`, `languageCode`, `seed`
Disable all model overrides:
```json5
{
messages: { tts: { modelOverrides: { enabled: false } } }
}
```
Allow provider switching:
```json5
{
messages: { tts: { modelOverrides: { enabled: true, allowProvider: true } } }
}
```
### Output formats by channel
| Channel | Format |
|---|---|
| Telegram / WhatsApp / Matrix | Opus voice message |
| Other channels | MP3 |
| MiniMax | MP3 only |
| Google Gemini | WAV for attachments, PCM for telephony |
| Gradium | WAV/Opus/ulaw |
| xAI | MP3 default (configurable) |
| Microsoft | Configured `outputFormat` (default: mp3) |
### Slash commands
```
/tts off
/tts on
/tts status
/tts provider openai
/tts limit 2000
/tts summary off
/tts audio Hello from OpenClaw
```
On Discord, use `/voice` (Discord has its own `/tts`).
### Auto-TTS behavior
- Skips if reply already has media or `MEDIA:` directive
- Skips very short replies (< 10 chars)
- Summarizes long replies when enabled using `summaryModel`
- Audio attached to reply
---
## web_fetch
Fetch a URL and extract readable content (HTML → markdown/text). Does NOT execute JavaScript.
**Docs:** https://docs.openclaw.ai/tools/web-fetch
### Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| `url` | string (required) | — | HTTP(S) URL to fetch |
| `extractMode` | string | `markdown` | `markdown` or `text` |
| `maxChars` | number | — | Max output characters |
### How it works
1. HTTP GET with Chrome-like User-Agent
2. Runs Readability (main-content extraction)
3. Optional Firecrawl fallback for bot-circumvention
4. 15-minute cache
### Config
```json5
{
tools: {
web: {
fetch: {
enabled: true,
provider: "firecrawl", // optional fallback
maxChars: 50000,
maxCharsCap: 50000,
maxResponseBytes: 2000000,
timeoutSeconds: 30,
cacheTtlMinutes: 15,
maxRedirects: 3,
readability: true,
userAgent: "Mozilla/5.0 ..."
}
}
}
}
```
### Firecrawl fallback
```json5
{
tools: { web: { fetch: { provider: "firecrawl" } } },
plugins: {
entries: {
firecrawl: {
enabled: true,
config: {
webFetch: {
apiKey: "fc-...",
baseUrl: "https://api.firecrawl.dev",
onlyMainContent: true,
maxAgeMs: 86400000,
timeoutSeconds: 60
}
}
}
}
}
}
```
### Limits & safety
- `maxChars` clamped to `maxCharsCap`
- Private/internal hostnames blocked
- Redirects checked and limited
---
## sessions_spawn (Sub-agents)
Spawn background agent runs from an existing agent run.
**Docs:** https://docs.openclaw.ai/tools/subagents
### Slash commands
```
/subagents list
/subagents kill <id|#|all>
/subagents log <id|#> [limit] [tools]
/subagents info <id|#>
/subagents send <id|#> <message>
/subagents steer <id|#> <message>
/subagents spawn <agentId> <task> [--model <model>] [--thinking <level>]
```
### Tool parameters (sessions_spawn)
| Parameter | Type | Description |
|---|---|---|
| `task` | string (required) | Task for the sub-agent |
| `label` | string | Optional label |
| `agentId` | string | Target agent id |
| `model` | string | Override sub-agent model |
| `thinking` | string | Override thinking level |
| `runTimeoutSeconds` | number | Abort after N seconds (0 = no timeout) |
| `thread` | boolean | Request thread binding |
| `mode` | `run\|session` | `run` (default) or `session` (requires `thread: true`) |
| `cleanup` | `delete\|keep` | Archive behavior (default: `keep`) |
| `sandbox` | `inherit\|require` | Sandbox inheritance |
| `context` | `isolated\|fork` | Context mode (default: `isolated`) |
| `runtime` | string | Set to `"acp"` for ACP harness sessions (Codex, Claude Code, Gemini CLI) |
### Context modes
| Mode | When to use |
|---|---|
| `isolated` | Fresh research, independent implementation (default, lower tokens) |
| `fork` | Work depending on current conversation, prior tool results |
Use `fork` sparingly — only when child needs current transcript.
### Configuration
```json5
{
agents: {
defaults: {
subagents: {
maxSpawnDepth: 2, // allow sub-agents to spawn children (default: 1)
maxChildrenPerAgent: 5, // max active children per session (default: 5)
maxConcurrent: 8, // global concurrency lane cap (default: 8)
runTimeoutSeconds: 900, // default timeout when omitted (0 = no timeout)
model: "anthropic/claude-sonnet-4-6", // sub-agent model (per-agent list[] override wins)
archiveAfterMinutes: 60, // auto-archive after N minutes
allowAgents: ["*"], // allowed target agent ids
requireAgentId: false // force explicit agentId
}
}
}
}
```
### Depth levels
| Depth | Session key | Role | Can spawn? |
|---|---|---|---|
| 0 | `agent:<id>:main` | Main agent | Always |
| 1 | `agent:<id>:subagent:<uuid>` | Sub-agent (orchestrator when depth 2 allowed) | Only if `maxSpawnDepth >= 2` |
| 2 | `agent:<id>:subagent:<uuid>:subagent:<uuid>` | Leaf worker | Never |
### Tool policy by depth
- **Depth 1 (orchestrator, when `maxSpawnDepth >= 2`)**: Gets `sessions_spawn`, `subagents`, `sessions_list`, `sessions_history` so it can manage its children
- **Depth 1 (leaf, when `maxSpawnDepth == 1`)**: No session tools (default)
- **Depth 2 (leaf)**: No session tools; `sessions_spawn` always denied
### Announce behavior
Sub-agents report back via announce step:
- Posts result to requester chat channel
- `ANNOUNCE_SKIP` → nothing posted
- `NO_REPLY` / `no_reply` → announce suppressed
- Includes: result, status, runtime/token stats, session key, transcript path
- **Delivery resilience:** Direct `agent` delivery is attempted first with a stable idempotency key; if that fails, falls back to queue routing; if queue routing is unavailable, retries with short exponential backoff before final give-up.
### sessions_history sanitization
`sessions_history` applies safety filtering before returning transcript content:
- Strips thinking tags
- Strips `<relevant-memories>` / `<relevant_memories>` scaffolding blocks
- Strips plain-text tool-call XML payload blocks (`<tool_call>...</tool_call>`, `<function_calls>...</function_calls>`, etc.)
- Strips leaked model control tokens (`<|assistant|>`, `<|...|>`, `<|...|>` variants)
- Strips malformed MiniMax tool-call XML
- Redacts credential/token-like text
- Long blocks may be truncated; very large histories can drop older rows
For the full raw byte-for-byte transcript, inspect the file on disk directly.
### sessions_yield tool
`sessions_yield` ends the current turn and waits for subagent results to be announced back. It is available in the `group:sessions` tool group. Use it after spawning subagents to receive their results as the next message rather than busy-polling.
### Cascade stop
- `/stop` in main chat → stops all depth-1 agents + cascades to depth-2
- `/subagents kill <id>` → stops specific sub-agent + cascades to children
### Thread bindings (Discord)
```json5
{
channels: {
discord: {
threadBindings: {
enabled: true,
idleHours: 1,
maxAgeHours: 24,
spawnSubagentSessions: true
}
}
}
}
```
```
/focus <subagent-label>
/unfocus
/agents
/session idle <duration|off>
/session max-age <duration|off>
```
### Cost
Each sub-agent has its OWN context and token usage. Set cheaper model for sub-agents:
```json5
{
agents: {
defaults: {
subagents: { model: "anthropic/claude-haiku-4-5" }
}
}
}
```
---
## web_search
Search the web using configured provider.
**Docs:** https://docs.openclaw.ai/tools/web
### Providers
| Provider | Result style | API key |
|---|---|---|
| Brave | Structured snippets | `BRAVE_API_KEY` |
| DuckDuckGo | Structured snippets | None (key-free) |
| Exa | Structured + extracted | `EXA_API_KEY` |
| Firecrawl | Structured snippets | `FIRECRAWL_API_KEY` |
| Gemini | AI-synthesized + citations | `GEMINI_API_KEY` |
| Grok (xAI) | AI-synthesized + citations | `XAI_API_KEY` |
| Kimi | AI-synthesized + citations | `KIMI_API_KEY` |
| MiniMax Search | Structured snippets | `MINIMAX_CODE_PLAN_KEY` |
| Ollama | Structured snippets | None (requires `ollama signin`) |
| Perplexity | Structured snippets | `PERPLEXITY_API_KEY` |
| SearXNG | Structured snippets | None (self-hosted) |
| Tavily | Structured snippets | `TAVILY_API_KEY` |
### Auto-detection order
1. Brave (order 10)
2. MiniMax Search (order 15)
3. Gemini (order 20)
4. Grok (order 30)
5. Kimi (order 40)
6. Perplexity (order 50)
7. Firecrawl (order 60)
8. Exa (order 65)
9. Tavily (order 70)
10. DuckDuckGo (order 100, key-free)
11. Ollama Web Search (order 110, key-free)
12. SearXNG (order 200)
### Config
```json5
{
tools: {
web: {
search: {
enabled: true,
provider: "brave", // or omit for auto-detection
maxResults: 5,
timeoutSeconds: 30,
cacheTtlMinutes: 15
}
}
}
}
```
### Store API key
```json5
{
plugins: {
entries: {
brave: {
config: {
webSearch: { apiKey: "YOUR_KEY" }
}
}
}
}
}
```
Or set env var: `export BRAVE_API_KEY="YOUR_KEY"`
### Tool parameters
| Parameter | Description |
|---|---|
| `query` | Search query (required) |
| `count` | Results 1-10 (default: 5) |
| `country` | ISO country code (`US`, `DE`) |
| `language` | ISO 639-1 language (`en`, `de`) |
| `freshness` | `day`, `week`, `month`, `year` |
| `date_after` | YYYY-MM-DD |
| `date_before` | YYYY-MM-DD |
| `domain_filter` | Domain allowlist/denylist (Perplexity only) |
| `max_tokens` | Content budget (Perplexity only) |
### x_search (X/Twitter search via xAI)
```json5
{
plugins: {
entries: {
xai: {
config: {
xSearch: {
enabled: true,
model: "grok-4-1-fast",
inlineCitations: false,
maxTurns: 2,
timeoutSeconds: 30,
cacheTtlMinutes: 15
}
}
}
}
}
}
```
x_search parameters: `query`, `allowed_x_handles`, `excluded_x_handles`, `from_date`, `to_date`, `enable_image_understanding`, `enable_video_understanding`
### Native OpenAI web search
Direct OpenAI Responses models use OpenAI's hosted `web_search` tool automatically when `tools.web.search.enabled: true` and no provider is pinned.
### Native Codex web search
```json5
{
tools: {
web: {
search: {
openaiCodex: {
enabled: true,
mode: "cached",
allowedDomains: ["example.com"],
contextSize: "high",
userLocation: {
country: "US",
city: "New York",
timezone: "America/New_York"
}
}
}
}
}
}
```
---
## Tool Configuration
### Allow and deny lists
```json5
{
tools: {
allow: ["group:fs", "browser", "web_search"],
deny: ["exec"]
}
}
```
Deny always wins over allow.
### Tool profiles
```json5
{
tools: { profile: "coding" } // full | coding | messaging | minimal
}
```
| Profile | Includes |
|---|---|
| `full` | No restriction |
| `coding` | fs, runtime, web, sessions, memory, cron, image, image_generate, music_generate, video_generate |
| `messaging` | group:messaging, sessions_list, sessions_history, sessions_send, session_status |
| `minimal` | session_status only |
### Tool groups
| Group | Tools |
|---|---|
| `group:runtime` | exec, process, code_execution |
| `group:fs` | read, write, edit, apply_patch |
| `group:sessions` | sessions_list, sessions_history, sessions_send, sessions_spawn, sessions_yield, subagents, session_status |
| `group:memory` | memory_search, memory_get |
| `group:web` | web_search, x_search, web_fetch |
| `group:ui` | browser, canvas |
| `group:automation` | cron, gateway |
| `group:messaging` | message |
| `group:nodes` | nodes |
| `group:agents` | agents_list |
| `group:media` | image, image_generate, music_generate, video_generate, tts |
| `group:openclaw` | All built-in tools (excludes plugin tools) |
### Provider-specific tool restrictions
```json5
{
tools: {
profile: "coding",
byProvider: {
"google-antigravity": { profile: "minimal" }
}
}
}
```
---
## gateway tool
Owner-only runtime tool for gateway operations:
| Action | Purpose |
|---|---|
| `config.schema.lookup` | One path-scoped config subtree before edits |
| `config.get` | Current config snapshot + hash |
| `config.patch` | Partial config updates with restart |
| `config.apply` | Full config replacement (use sparingly) |
| `update.run` | Explicit self-update + restart |
⚠️ `config.patch` is preferred over `config.apply`. The gateway tool refuses to change `tools.exec.ask` or `tools.exec.security`.
---
## memory_search / memory_get
Search memory files in MEMORY.md and memory/*.md.
```json5
{
// Configure memory slot
plugins: {
slots: {
memory: "memory-core" // or "memory-lancedb" for long-term memory
}
}
}
```
`memory-lancedb` provides install-on-demand long-term memory with auto-recall/capture.
---
## session_status
Lightweight status readback tool:
- Answers `/status`-style questions about current session
- Can set per-session model override (`model=default` to clear)
- Backfills sparse token/cache counters from latest transcript usage entry
---
## Plugin-provided tools (examples)
| Tool | Plugin | Description |
|---|---|---|
| `web_search` (SearXNG) | `searxng` | Self-hosted meta-search |
| `firecrawl_search` / `firecrawl_scrape` | `firecrawl` | Deep web extraction |
| `tavily_search` / `tavily_extract` | `tavily` | Structured search + URL extraction |
| Music generation tools | `minimax`, `google` | Music generation |
| Diffs | `diffs` | Diff viewer |
| Lobster | `lobster` | Typed workflow runtime |
| LLM Task | `llm-task` | JSON-only LLM structured output |
| Tokenjuice | `tokenjuice` | Compact noisy exec output |
FILE:references/07-plugins.md
# OpenClaw Plugins Reference
Plugins extend OpenClaw with new capabilities: channels, model providers, agent harnesses, tools, skills, speech, realtime transcription, realtime voice, media understanding, image generation, video generation, web fetch, web search, and more.
**Docs:** https://docs.openclaw.ai/plugins
## Table of Contents
- [Plugin Types](#plugin-types)
- [Quick Start](#quick-start)
- [Official Installable Plugins (npm)](#official-installable-plugins-npm)
- [Core Plugins (shipped with OpenClaw)](#core-plugins-shipped-with-openclaw)
- [Plugin Configuration](#plugin-configuration)
- [Plugin Slots (exclusive categories)](#plugin-slots-exclusive-categories)
- [Plugin Discovery Order (first match wins)](#plugin-discovery-order-first-match-wins)
- [CLI Reference](#cli-reference)
- [Plugin States](#plugin-states)
- [Plugin API Overview](#plugin-api-overview)
- [Voice Call Plugin](#voice-call-plugin-openclawvoice-call)
- [Memory Plugins](#memory-plugins)
- [Browser Plugin](#browser-plugin)
- [Bundle Plugins](#bundle-plugins)
- [MCP (Model Context Protocol) Tools](#mcp-model-context-protocol-tools)
- [Web Search Plugins](#web-search-plugins)
- [Image Generation Plugins](#image-generation-plugins)
- [Music Generation Plugins](#music-generation-plugins)
- [Video Generation Plugins](#video-generation-plugins)
- [Channel Plugins](#channel-plugins)
- [Troubleshooting Plugins](#troubleshooting-plugins)
- [Community Plugins](#community-plugins)
- [Building Plugins](#building-plugins)
---
## Plugin Types
| Format | How it works | Examples |
|---|---|---|
| **Native** | `openclaw.plugin.json` + runtime module; executes in-process | Official plugins, community npm packages |
| **Bundle** | Codex/Claude/Cursor-compatible layout; mapped to OpenClaw features | `.codex-plugin/`, `.claude-plugin/`, `.cursor-plugin/` |
---
## Quick Start
```bash
# See what is loaded
openclaw plugins list
# Install from npm
openclaw plugins install @openclaw/voice-call
# Install from local directory or archive
openclaw plugins install ./my-plugin
openclaw plugins install ./my-plugin.tgz
# Restart the gateway
openclaw gateway restart
```
Then configure under `plugins.entries.<id>.config`.
If config is invalid, install fails closed and points to `openclaw doctor --fix`. The only recovery exception is a narrow bundled-plugin reinstall path for plugins that opt into `openclaw.install.allowInvalidConfigRecovery`.
Packaged installs do not eagerly install every bundled plugin's runtime dependency tree. When a bundled OpenClaw-owned plugin is active (from config, legacy channel config, or default-enabled manifest), startup repairs only that plugin's declared runtime dependencies. Explicit disablement (`plugins.entries.<id>.enabled: false`, `plugins.deny`, `plugins.enabled: false`, `channels.<id>.enabled: false`) prevents automatic repair.
### Chat-native control
Enable with `commands.plugins: true`:
```
/plugin install clawhub:@openclaw/voice-call
/plugin show voice-call
/plugin enable voice-call
```
---
## Official Installable Plugins (npm)
| Plugin | Package | Docs |
|---|---|---|
| Matrix | `@openclaw/matrix` | channels/matrix |
| Microsoft Teams | `@openclaw/msteams` | channels/msteams |
| Nostr | `@openclaw/nostr` | channels/nostr |
| Voice Call | `@openclaw/voice-call` | plugins/voice-call |
| Zalo | `@openclaw/zalo` | channels/zalo |
| Zalo Personal | `@openclaw/zalouser` | plugins/zalouser |
---
## Core Plugins (shipped with OpenClaw)
### Model providers (enabled by default)
`anthropic`, `byteplus`, `cloudflare-ai-gateway`, `github-copilot`, `google`, `huggingface`, `kilocode`, `kimi-coding`, `minimax`, `mistral`, `qwen`, `moonshot`, `nvidia`, `openai`, `opencode`, `opencode-go`, `openrouter`, `qianfan`, `synthetic`, `together`, `venice`, `vercel-ai-gateway`, `volcengine`, `xiaomi`, `zai`
### Memory plugins
| Plugin | Description |
|---|---|
| `memory-core` | Default bundled memory search |
| `memory-lancedb` | Long-term memory with auto-recall/capture |
```json5
{
plugins: {
slots: {
memory: "memory-lancedb" // or "memory-core" (default) or "none"
}
}
}
```
### Speech providers (enabled by default)
`elevenlabs`, `microsoft`
### Other core plugins
| Plugin | Description |
|---|---|
| `browser` | Bundled browser plugin — browser tool, `openclaw browser` CLI, browser control service |
| `copilot-proxy` | VS Code Copilot Proxy bridge (disabled by default) |
---
## Plugin Configuration
```json5
{
plugins: {
enabled: true,
allow: ["voice-call"],
deny: ["untrusted-plugin"],
load: { paths: ["~/Projects/oss/voice-call-plugin"] },
entries: {
"voice-call": { enabled: true, config: { provider: "twilio" } }
}
}
}
```
| Field | Description |
|---|---|
| `enabled` | Master toggle (default: `true`) |
| `allow` | Plugin allowlist (optional) |
| `deny` | Plugin denylist (deny wins) |
| `load.paths` | Extra plugin files/directories |
| `slots` | Exclusive slot selectors |
| `entries.<id>` | Per-plugin toggles + config |
Config changes require a gateway restart. If the Gateway is running with config watch + in-process restart enabled (the default `openclaw gateway` path), that restart is usually performed automatically a moment after the config write lands.
There is no supported hot-reload path for native plugin runtime code or lifecycle hooks; restart the Gateway process serving the live channel before expecting updated `register(api)` code, `api.on(...)` hooks, tools, services, or provider/runtime hooks to run.
`openclaw plugins list` is a local CLI/config snapshot. A `loaded` plugin means the plugin is discoverable and loadable from that CLI invocation. It does not prove an already-running remote Gateway child has restarted into the same code. On VPS/container setups with wrapper processes, send restarts to the actual `openclaw gateway run` process or use `openclaw gateway restart`.
---
## Plugin Slots (exclusive categories)
```json5
{
plugins: {
slots: {
memory: "memory-core", // or "none"
contextEngine: "legacy" // or a plugin id
}
}
}
```
| Slot | Controls | Default |
|---|---|---|
| `memory` | Active memory plugin | `memory-core` |
| `contextEngine` | Active context engine | `legacy` (built-in) |
---
## Plugin Discovery Order (first match wins)
1. `plugins.load.paths` — explicit paths
2. `<workspace>/.openclaw/<plugin-root>/*.ts` or `*/index.ts` — workspace plugins (disabled by default)
3. `~/.openclaw/<plugin-root>/*.ts` or `*/index.ts` — global plugins
4. Bundled plugins (shipped with OpenClaw)
### Enablement rules
- `plugins.enabled: false` → disables ALL plugins
- `plugins.deny` → always wins over allow
- `plugins.entries.<id>.enabled: false` → disables that plugin
- Workspace-origin plugins: **disabled by default** (must be explicitly enabled)
- Bundled plugins: default-on set unless overridden
- Exclusive slots can force-enable the selected plugin
- Some bundled opt-in plugins are enabled automatically when config names a plugin-owned surface, such as a provider model ref, channel config, or harness runtime
- **Codex routing note**: `openai-codex/*` model refs belong to the OpenAI plugin; the bundled Codex app-server plugin is selected by `embeddedHarness.runtime: "codex"` or legacy `codex/*` model refs — these keep separate plugin boundaries
---
## CLI Reference
```bash
# Inventory
openclaw plugins list # compact inventory
openclaw plugins list --enabled # only loaded plugins
openclaw plugins list --verbose # per-plugin detail lines
openclaw plugins list --json # machine-readable
openclaw plugins inspect <id> # deep detail
openclaw plugins inspect <id> --json # machine-readable
openclaw plugins inspect --all # fleet-wide table
openclaw plugins info <id> # inspect alias
openclaw plugins doctor # diagnostics
# Install / update / uninstall
openclaw plugins install <package> # ClawHub first, then npm
openclaw plugins install clawhub:<pkg> # ClawHub only
openclaw plugins install <spec> --force # overwrite existing (not supported with --link)
openclaw plugins install <path> # from local path
openclaw plugins install -l <path> # link (dev mode; --force not supported with --link)
openclaw plugins install <plugin> --marketplace <source> # source: name, local path, GitHub shorthand (owner/repo), GitHub URL, or git URL
openclaw plugins install <plugin> --marketplace https://github.com/<owner>/<repo> # GitHub URL form
openclaw plugins install <spec> --pin # record exact npm spec
openclaw plugins install <spec> --dangerously-force-unsafe-install
openclaw plugins update <id-or-npm-spec> # update one
openclaw plugins update <id-or-npm-spec> --dangerously-force-unsafe-install
openclaw plugins update --all # update all
openclaw plugins uninstall <id> # remove
openclaw plugins uninstall <id> --keep-files
# Enable / disable
openclaw plugins enable <id>
openclaw plugins disable <id>
# Marketplace
openclaw plugins marketplace list <source>
openclaw plugins marketplace list <source> --json
```
### Notes on install/update
- `--force` overwrites existing install; **not supported with `--link`** (which reuses source path instead of copying)
- `--pin` is npm-only; not supported with `--marketplace`
- `--dangerously-force-unsafe-install` bypasses code scanner findings but not policy blocks
- When `plugins.allow` is set, `plugins install` adds the new plugin id to the allowlist automatically
- If the installed npm plugin already matches the resolved version and recorded artifact identity, `plugins update` skips the download/reinstall without rewriting config
- Marketplace source formats: Claude known-marketplace name (from `~/.claude/plugins/known_marketplaces.json`), a local marketplace root or `marketplace.json` path, a GitHub shorthand like `owner/repo`, a GitHub repo URL, or a git URL
- `openclaw plugins list` — a `loaded` plugin means the plugin is discoverable and loadable from the config/files seen by that CLI invocation; it does not prove an already-running remote Gateway child has restarted into the same plugin code
- `openclaw plugins inspect <id>` also reports supported or unsupported MCP and LSP server entries for bundle-backed plugins
---
## Plugin States
| State | Meaning |
|---|---|
| **Disabled** | Plugin exists but enablement rules turned it off |
| **Missing** | Config references a plugin id that discovery didn't find |
| **Invalid** | Plugin exists but config doesn't match declared schema |
---
## Plugin API Overview
Native plugins export an entry object with `register(api)`:
```typescript
export default definePluginEntry({
id: "my-plugin",
name: "My Plugin",
register(api) {
api.registerProvider({ /* ... */ });
api.registerTool({ /* ... */ });
api.registerChannel({ /* ... */ });
}
});
```
> **Legacy alias**: Older plugins may still use `activate(api)` instead of `register(api)`. New plugins should use `register`.
**Codex app-server bridge**: Plugins can block native Codex tools through `before_tool_call`, observe results through `after_tool_call`, and participate in Codex `PermissionRequest` approvals.
### Registration methods
| Method | What it registers |
|---|---|
| `registerProvider` | Model provider (LLM) |
| `registerChannel` | Chat channel |
| `registerTool` | Agent tool |
| `registerHook` / `on(...)` | Lifecycle hooks |
| `registerSpeechProvider` | Text-to-speech / STT |
| `registerRealtimeTranscriptionProvider` | Streaming STT |
| `registerRealtimeVoiceProvider` | Duplex realtime voice |
| `registerMediaUnderstandingProvider` | Image/audio analysis |
| `registerImageGenerationProvider` | Image generation |
| `registerMusicGenerationProvider` | Music generation |
| `registerVideoGenerationProvider` | Video generation |
| `registerWebFetchProvider` | Web fetch / scrape |
| `registerWebSearchProvider` | Web search |
| `registerHttpRoute` | HTTP endpoint |
| `registerCliBackend` | CLI inference backend |
| `registerCommand` / `registerCli` | CLI commands |
| `registerContextEngine` | Context engine |
| `registerAgentToolResultMiddleware` | Tool-result middleware |
| `registerService` | Background service |
### Hook guard behavior
| Hook | Behavior |
|---|---|
| `before_tool_call` | `{ block: true }` is terminal (lower-priority handlers skipped); `{ block: false }` is a no-op and does not clear an earlier block |
| `before_install` | `{ block: true }` is terminal; `{ block: false }` is a no-op and does not clear an earlier block |
| `message_sending` | `{ cancel: true }` is terminal; `{ cancel: false }` is a no-op and does not clear an earlier cancel |
---
## Voice Call Plugin (`@openclaw/voice-call`)
```bash
openclaw plugins install @openclaw/voice-call
openclaw gateway restart
```
### Providers
| Provider | Description |
|---|---|
| `twilio` | Programmable Voice + Media Streams |
| `telnyx` | Call Control v2 |
| `plivo` | Voice API + XML transfer + GetInput speech |
| `mock` | Local dev/no network |
### Configuration structure
Full config reference (all major fields shown):
```json5
{
plugins: {
entries: {
"voice-call": {
enabled: true,
config: {
provider: "twilio", // or "telnyx" | "plivo" | "mock"
fromNumber: "+15550001234", // or TWILIO_FROM_NUMBER for Twilio
toNumber: "+15550005678",
twilio: {
accountSid: "ACxxxxxxxx",
authToken: "...",
},
telnyx: {
apiKey: "...",
connectionId: "...",
// Telnyx webhook public key from Mission Control Portal
// (Base64 string; or TELNYX_PUBLIC_KEY env var)
publicKey: "...",
},
plivo: {
authId: "MAxxxxxxxxxxxxxxxxxxxx",
authToken: "...",
},
// Webhook server
serve: {
port: 3334,
path: "/voice/webhook",
},
// Webhook security (recommended for tunnels/proxies)
webhookSecurity: {
allowedHosts: ["voice.example.com"],
trustedProxyIPs: ["100.64.0.1"],
},
// Public exposure (pick one)
// publicUrl: "https://example.ngrok.app/voice/webhook",
// tunnel: { provider: "ngrok" },
// tailscale: { mode: "funnel", path: "/voice/webhook" }
outbound: {
defaultMode: "notify", // notify | conversation
},
// Inbound call policy (default: disabled)
inboundPolicy: "allowlist", // disabled | allowlist
allowFrom: ["+15550005678"],
inboundGreeting: "Hello! How can I help?",
// Stale call reaper (default: 0 = disabled)
maxDurationSeconds: 300,
staleCallReaperSeconds: 360, // should be > maxDurationSeconds
// Streaming STT (realtime transcription)
// Do NOT combine with realtime.enabled: true
streaming: {
enabled: true,
provider: "openai", // optional; first registered realtime transcription provider when unset
streamPath: "/voice/stream",
providers: {
openai: {
apiKey: "sk-...", // optional if OPENAI_API_KEY is set
model: "gpt-4o-transcribe",
silenceDurationMs: 800,
vadThreshold: 0.5,
},
},
// Streaming security
preStartTimeoutMs: 5000,
maxPendingConnections: 32,
maxPendingConnectionsPerIp: 4,
maxConnections: 128,
},
// Realtime voice (full duplex, bidirectional audio)
// Do NOT combine with streaming.enabled: true
realtime: {
enabled: false, // default; enable for live voice conversations
provider: "google", // optional; first registered realtime voice provider when unset
toolPolicy: "safe-read-only", // safe-read-only | owner | none
providers: {
google: {
model: "gemini-2.5-flash-native-audio-preview-12-2025",
voice: "Kore",
},
},
},
},
},
},
},
}
```
### Realtime voice
`realtime` selects a full duplex realtime voice provider. Supported providers: `google` (default example), `openai`.
- `realtime.enabled: false` is the default. Enable for live voice-to-voice conversations.
- `realtime.provider` is optional. If unset, uses the first registered realtime voice provider.
- `realtime.toolPolicy` controls the `openclaw_agent_consult` tool:
- `safe-read-only`: expose consult tool, limit agent to `read`, `web_search`, `web_fetch`, `memory_search`, `memory_get`
- `owner`: expose consult tool with normal agent tool policy
- `none`: do not expose the consult tool
- **Cannot be combined with `streaming.enabled: true`**
**Google Gemini Live (default example):**
```json5
realtime: {
enabled: true,
provider: "google",
toolPolicy: "safe-read-only",
providers: {
google: {
apiKey: "GEMINI_API_KEY",
model: "gemini-2.5-flash-native-audio-preview-12-2025",
voice: "Kore",
},
},
}
```
**OpenAI alternative:**
```json5
realtime: {
enabled: true,
provider: "openai",
providers: {
openai: {
apiKey: "OPENAI_API_KEY",
},
},
}
```
### Streaming transcription (STT)
`streaming` selects a realtime transcription provider. Cannot be combined with `realtime.enabled: true`.
- `streaming.provider` is optional. If unset, uses the first registered realtime transcription provider.
- Supported streaming providers include: `openai`, `xai`, `deepgram`, `elevenlabs`, `mistral`
- Streaming security: `preStartTimeoutMs` (closes sockets without valid `start` frame), `maxPendingConnections` (total unauthenticated pre-start), `maxPendingConnectionsPerIp` (per source IP), `maxConnections` (total open media sockets)
**OpenAI defaults:** model `gpt-4o-transcribe`, `silenceDurationMs: 800`, `vadThreshold: 0.5`
**xAI defaults:** endpoint `wss://api.x.ai/v1/stt`, `encoding: "mulaw"`, `sampleRate: 8000`, `endpointingMs: 800`, `interimResults: true`
```json5
// OpenAI example
streaming: {
enabled: true,
provider: "openai",
streamPath: "/voice/stream",
providers: {
openai: {
apiKey: "sk-...",
model: "gpt-4o-transcribe",
silenceDurationMs: 800,
vadThreshold: 0.5,
},
},
}
// xAI alternative
streaming: {
enabled: true,
provider: "xai",
streamPath: "/voice/stream",
providers: {
xai: {
apiKey: "XAI_API_KEY",
endpointingMs: 800,
language: "en",
},
},
}
```
### TTS for calls
Voice Call uses `messages.tts` for streaming speech. Override just for calls:
```json5
{
plugins: { entries: { "voice-call": { config: {
tts: {
provider: "elevenlabs",
providers: {
elevenlabs: {
voiceId: "pMsXgVXv3BLzUgSXRplE",
modelId: "eleven_multilingual_v2",
},
},
},
}}}}
}
```
### Webhook security notes
- Twilio/Telnyx/Plivo require **publicly reachable** webhook URL
- `mock` is local dev provider (no network calls)
- Legacy config (`provider: "log"`, `twilio.from`) → run `openclaw doctor --fix`
- Telnyx requires `telnyx.publicKey` (or `TELNYX_PUBLIC_KEY`) unless `skipSignatureVerification: true` (local testing only)
- `tunnel.allowNgrokFreeTierLoopbackBypass: true` — allows Twilio webhooks with invalid signatures ONLY when `tunnel.provider="ngrok"` and `serve.bind` is loopback. Local dev only.
- Ngrok free tier URLs drift; use stable domain or Tailscale funnel for production
> **Note**: Microsoft speech is ignored for voice calls (telephony audio needs PCM).
### Voice Call CLI
```bash
openclaw voicecall setup # check plugin setup (provider, creds, webhook)
openclaw voicecall setup --json # machine-readable
openclaw voicecall smoke # dry-run smoke test
openclaw voicecall smoke --to "+15555550123" # dry run to specific number
openclaw voicecall smoke --to "+15555550123" --yes # actually place a short call
```
`setup` checks: plugin enabled, provider+credentials present, webhook exposure configured, only one audio mode active. Fails if webhook resolves to loopback/private network.
### Stale call reaper
Use `staleCallReaperSeconds` to end calls that never receive a terminal webhook. Default: `0` (disabled).
- Recommended range: `120`–`300` seconds for notify-style flows
- Keep `staleCallReaperSeconds` **higher than `maxDurationSeconds`**
---
## Memory Plugins
### memory-core (default)
Built-in memory search. Uses `memory_search` and `memory_get` tools.
```json5
{
plugins: { slots: { memory: "memory-core" } }
}
```
### memory-lancedb
Long-term memory with auto-recall and capture. Install on demand:
```json5
{
plugins: { slots: { memory: "memory-lancedb" } }
}
```
Supports semantic vector search with embedding providers:
- Ollama (`nomic-embed-text`)
- Mistral (`mistral-embed`)
- OpenAI embeddings
- Amazon Bedrock embeddings (Titan, Nova, Cohere, TwelveLabs)
---
## Browser Plugin
Built-in browser plugin. Provides:
- `browser` agent tool
- `openclaw browser` CLI
- `browser.request` gateway method
- Default browser control service (loopback)
### Disable (to replace with custom)
```json5
{
plugins: { entries: { browser: { enabled: false } } }
}
```
### Ensure it's in allowlist
```json5
{
plugins: { allow: ["telegram", "browser"] }
}
```
---
## Bundle Plugins
Bundles are Codex/Claude/Cursor-compatible layouts:
- `.codex-plugin/`
- `.claude-plugin/`
- `.cursor-plugin/`
Supported bundle capabilities:
- Bundle skills
- Claude command-skills
- Claude `settings.json` defaults
- Claude `.lsp.json` and `lspServers` defaults
- Cursor command-skills
- Compatible Codex hook directories
Compatible bundles participate in the `plugins list/inspect/enable/disable` flow.
---
## MCP (Model Context Protocol) Tools
Bundle-MCP tools appear in `openclaw plugins list`. To hide MCP tools while keeping profile built-ins:
```json5
{
tools: { deny: ["bundle-mcp"] }
}
```
The `minimal` profile does not include bundle-MCP tools. `coding` and `messaging` profiles allow configured bundle-MCP tools by default.
---
## Web Search Plugins
### Brave Search
```json5
{
plugins: {
entries: {
brave: {
config: {
webSearch: {
apiKey: "BRAVE_API_KEY", // or BRAVE_API_KEY env var
mode: "search" // or "llm-context"
}
}
}
}
}
}
```
`llm-context` mode returns optimized content for LLMs but rejects some filters (freshness, ui_lang, date_after/before).
### Exa Search
```json5
{
plugins: {
entries: {
exa: {
config: {
webSearch: {
apiKey: "EXA_API_KEY" // or EXA_API_KEY env var
}
}
}
}
}
}
```
Supports neural + keyword search, date filters, content extraction (highlights, text, summaries).
### Firecrawl
```json5
{
plugins: {
entries: {
firecrawl: {
enabled: true,
config: {
webSearch: {
apiKey: "fc-...", // or FIRECRAWL_API_KEY
baseUrl: "https://api.firecrawl.dev"
},
webFetch: {
apiKey: "fc-...",
baseUrl: "https://api.firecrawl.dev",
onlyMainContent: true,
maxAgeMs: 86400000,
timeoutSeconds: 60
}
}
}
}
}
}
```
⚠️ `baseUrl` must be `https://api.firecrawl.dev` — other hosts blocked.
### SearXNG (self-hosted)
```json5
{
plugins: {
entries: {
searxng: {
config: {
webSearch: {
baseUrl: "http://localhost:8888" // or SEARXNG_BASE_URL
}
}
}
}
}
}
```
SearXNG `http://` only for trusted private-network/loopback; public endpoints must use `https://`.
### Tavily
```json5
{
plugins: {
entries: {
tavily: {
config: {
webSearch: {
apiKey: "TAVILY_API_KEY"
}
}
}
}
}
}
```
Also provides `tavily_search` and `tavily_extract` tools.
### Perplexity
```json5
{
plugins: {
entries: {
perplexity: {
config: {
webSearch: {
apiKey: "pplx-..." // or PERPLEXITY_API_KEY; sk-or- routes through OpenRouter
}
}
}
}
}
}
```
### Gemini Search (Google Grounding)
```json5
{
plugins: {
entries: {
google: {
config: {
webSearch: {
apiKey: "GEMINI_API_KEY"
}
}
}
}
}
}
```
Returns AI-synthesized answers with citations.
### Grok Search (xAI)
```json5
{
plugins: {
entries: {
xai: {
config: {
webSearch: {
apiKey: "xai-..." // or XAI_API_KEY
}
}
}
}
}
}
```
Returns AI-synthesized answers with citations.
### Kimi Search (Moonshot)
```json5
{
plugins: {
entries: {
moonshot: {
config: {
webSearch: {
apiKey: "KIMI_API_KEY",
baseUrl: "https://api.moonshot.ai/v1", // or .cn
model: "kimi-k2.6"
}
}
}
}
}
}
```
### MiniMax Search
```json5
{
plugins: {
entries: {
minimax: {
config: {
webSearch: {
region: "global" // or "cn"
}
}
}
}
}
}
```
Uses `MINIMAX_CODE_PLAN_KEY` or `MINIMAX_CODING_API_KEY`.
### Ollama Web Search
```json5
{
tools: { web: { search: { provider: "ollama" } } }
}
```
Requires `ollama signin`. Key-free.
---
## Image Generation Plugins
### fal
```json5
{
agents: {
defaults: {
imageGenerationModel: { primary: "fal/fal-ai/flux/dev" }
}
}
}
```
Auth: `FAL_KEY`. Supports text-to-image and editing (1 reference image).
### ComfyUI
For local ComfyUI workflows:
```json5
{
plugins: {
entries: {
comfy: {
config: {
apiKey: "COMFY_API_KEY", // or COMFY_CLOUD_API_KEY for cloud
baseUrl: "http://localhost:8188"
}
}
}
}
}
```
Model ref: `comfy/workflow` (workflow-defined outputs).
---
## Music Generation Plugins
### Google Lyria
```json5
{
agents: {
defaults: {
musicGenerationModel: { primary: "google/lyria-3-clip-preview" }
}
}
}
```
- Also: `google/lyria-3-pro-preview`
- Controls: `lyrics`, `instrumental`
- Output: `mp3` (default), `wav` (pro only)
- Reference inputs: up to 10 images
### MiniMax Music
```json5
{
agents: {
defaults: {
musicGenerationModel: { primary: "minimax/music-2.5+" }
}
}
}
```
- Also: `minimax/music-2.6`, `minimax/music-2.5`, `minimax/music-2.0`
- Controls: `lyrics`, `instrumental`, `durationSeconds`
- Output: `mp3`
---
## Video Generation Plugins
### BytePlus (Seedance)
```json5
{
agents: {
defaults: {
videoGenerationModel: { primary: "byteplus/seedance-1-5-pro-251215" }
}
}
}
```
- Auth: `BYTEPLUS_API_KEY`
- Supports first_frame / last_frame image roles
- `adaptive` aspect ratio supported
### Runway
```json5
{
agents: {
defaults: {
videoGenerationModel: { primary: "runway/gen4.5" }
}
}
}
```
- Auth: `RUNWAYML_API_SECRET`
- Supports text-to-video, image-to-video, video-to-video
### fal Video
```json5
{
agents: {
defaults: {
videoGenerationModel: { primary: "fal/fal-ai/minimax/video-01-live" }
}
}
}
```
---
## Channel Plugins
### Telegram (built-in)
Built into OpenClaw core. Configure:
```json5
{
channels: {
telegram: {
enabled: true,
token: "TELEGRAM_BOT_TOKEN",
// ... other settings
}
}
}
```
### WhatsApp (built-in)
```json5
{
channels: {
whatsapp: {
enabled: true
// ... provider config
}
}
}
```
### Discord (built-in)
```json5
{
channels: {
discord: {
enabled: true,
token: "DISCORD_BOT_TOKEN",
threadBindings: {
enabled: true,
idleHours: 1,
maxAgeHours: 24
}
}
}
}
```
### Slack (built-in)
```json5
{
channels: {
slack: {
enabled: true,
token: "SLACK_BOT_TOKEN"
}
}
}
```
### Matrix (`@openclaw/matrix`)
```bash
openclaw plugins install @openclaw/matrix
```
### Microsoft Teams (`@openclaw/msteams`)
```bash
openclaw plugins install @openclaw/msteams
```
---
## Troubleshooting Plugins
### Plugin appears in list but hooks don't run
1. Run `openclaw gateway status --deep --require-rpc` to confirm active gateway URL/process
2. Restart the live gateway after plugin install/config/code changes
3. Use `openclaw plugins inspect <id> --json` to confirm hook registrations
4. Non-bundled conversation hooks (`llm_input`, `llm_output`, `agent_end`) need:
```json5
{ "plugins": { "entries": { "<id>": { "hooks": { "allowConversationAccess": true } } } } }
```
5. For model switching, prefer `before_model_resolve` hook (runs before model resolution)
### Plugin is "missing"
Config references a plugin id that discovery didn't find. Install it:
```bash
openclaw plugins install <package>
```
### Plugin is "invalid"
Plugin's config doesn't match declared schema. Check with:
```bash
openclaw plugins inspect <id> --json
openclaw doctor --fix
```
### Wrong plugin code in live chat
On VPS/container setups, PID 1 may be a supervisor. Signal the child `openclaw gateway run` process, or use:
```bash
openclaw gateway restart
```
---
## Community Plugins
See https://docs.openclaw.ai/plugins/community for third-party listings.
### Claude Max API Proxy
Community proxy for Claude subscription credentials:
- https://docs.openclaw.ai/providers/claude-max-api-proxy
- Verify Anthropic policy/terms before use
---
## Building Plugins
### Minimal native plugin structure
```typescript
// index.ts
import { definePluginEntry } from "@openclaw/sdk";
export default definePluginEntry({
id: "my-plugin",
name: "My Plugin",
version: "1.0.0",
register(api) {
// Register a tool
api.registerTool({
name: "my_tool",
description: "Does something useful",
schema: {
type: "object",
properties: {
input: { type: "string", description: "Input text" }
},
required: ["input"]
},
async execute({ input }) {
return `Processed: input`;
}
});
// Register a hook
api.registerHook("before_tool_call", async (event) => {
// Can return { block: true } to prevent tool call
return {};
});
// Register a web search provider
api.registerWebSearchProvider({
id: "my-search",
async search({ query, count }) {
// Return search results
return { results: [] };
}
});
}
});
```
### openclaw.plugin.json
```json
{
"id": "my-plugin",
"name": "My Plugin",
"version": "1.0.0",
"main": "dist/index.js"
}
```
### Plugin locations for dev
```json5
{
plugins: {
load: { paths: ["~/Projects/my-plugin"] }
}
}
```
Or install with link (dev mode):
```bash
openclaw plugins install -l ./my-plugin
```
### Optional channel setup surface
For channel plugins that aren't guaranteed to be installed when onboarding/setup runs, use `createOptionalChannelSetupSurface(...)` from `openclaw/plugin-sdk/channel-setup`. It produces a setup adapter + wizard pair that advertises the install requirement and fails closed on real config writes until the plugin is installed.
### Key references
- Building Plugins: https://docs.openclaw.ai/plugins/building-plugins
- Plugin SDK Overview: https://docs.openclaw.ai/plugins/sdk-overview
- Plugin Bundles: https://docs.openclaw.ai/plugins/bundles
- Plugin Manifest: https://docs.openclaw.ai/plugins/manifest
- Plugin Architecture: https://docs.openclaw.ai/plugins/architecture
- Community Plugins: https://docs.openclaw.ai/plugins/community
FILE:references/08-automation.md
# OpenClaw Automation Reference
## Table of Contents
- [Overview](#overview)
- [Scheduled Tasks (Cron)](#scheduled-tasks-cron)
- [Background Tasks](#background-tasks)
- [Task Flow](#task-flow)
- [Hooks](#hooks)
- [Standing Orders](#standing-orders)
- [Program: Weekly Status Report](#program-weekly-status-report)
- [Program: Content & Social Media](#program-content--social-media)
- [Program: Financial Processing](#program-financial-processing)
- [Program: System Monitoring](#program-system-monitoring)
- [Heartbeat](#heartbeat)
- [How They Work Together](#how-they-work-together)
## Overview
OpenClaw runs background work through five mechanisms: **Scheduled Tasks (Cron)**, **Background Tasks (ledger)**, **Task Flow**, **Hooks**, and **Standing Orders**. A **Heartbeat** also provides periodic main-session turns.
### Quick Decision Guide
| Use case | Recommended | Why |
|---|---|---|
| Send daily report at 9 AM sharp | Scheduled Tasks (Cron) | Exact timing, isolated execution |
| Remind me in 20 minutes | Scheduled Tasks (Cron) | One-shot with `--at` |
| Run weekly deep analysis | Scheduled Tasks (Cron) | Standalone, can use different model |
| Check inbox every 30 min | Heartbeat | Batches checks, context-aware |
| Monitor calendar for upcoming events | Heartbeat | Natural fit for periodic awareness |
| Inspect status of a subagent or ACP run | Background Tasks | Tasks ledger tracks all detached work |
| Audit what ran and when | Background Tasks | `openclaw tasks list` and `openclaw tasks audit` |
| Multi-step research then summarize | Task Flow | Durable orchestration with revision tracking |
| Run a script on session reset | Hooks | Event-driven, fires on lifecycle events |
| Execute code on every tool call | Plugin hooks | In-process hooks can intercept tool calls |
| Always check compliance before replying | Standing Orders | Injected into every session automatically |
### Cron vs Heartbeat Comparison
| Dimension | Scheduled Tasks (Cron) | Heartbeat |
|---|---|---|
| Timing | Exact (cron expressions, one-shot) | Approximate (default every 30 min) |
| Session context | Fresh (isolated) or shared | Full main-session context |
| Task records | Always created | Never created |
| Delivery | Channel, webhook, or silent | Inline in main session |
| Best for | Reports, reminders, background jobs | Inbox checks, calendar, notifications |
---
## Scheduled Tasks (Cron)
Cron is the Gateway's built-in scheduler. It persists jobs, wakes the agent at the right time, and delivers output to a chat channel or webhook endpoint.
### How Cron Works
- Runs **inside the Gateway** process (not inside the model)
- Job definitions persist at `~/.openclaw/cron/jobs.json`
- Runtime execution state persists in `~/.openclaw/cron/jobs-state.json` (sidecar file; older OpenClaw versions can read `jobs.json` but may treat jobs as fresh because runtime fields now live in `jobs-state.json`)
- All cron executions create [background task](/automation/tasks) records
- One-shot jobs (`--at`) auto-delete after success by default
- Isolated cron runs best-effort close tracked browser tabs/processes for their `cron:<jobId>` session when the run completes
- Isolated cron runs also dispose any bundled MCP runtime instances created for the job through the shared runtime-cleanup path
- Isolated cron runs guard against stale acknowledgement replies: if the first result is just an interim status update (`on it`, `pulling everything together`, and similar hints) and no descendant subagent run is still responsible for the final answer, OpenClaw re-prompts once for the actual result before delivery
- Task reconciliation for cron is runtime-owned: an active cron task stays live while the cron runtime still tracks the job as running, even if an old child session row exists. Once the runtime stops owning the job and the 5-minute grace window expires, maintenance marks the task `lost`
- When isolated cron runs orchestrate subagents, delivery prefers final descendant output over stale parent interim text. If descendants are still running, OpenClaw suppresses partial parent updates instead of announcing them
- For text-only Discord announce targets, OpenClaw sends the canonical final assistant text once instead of replaying both streamed/intermediate payloads and the final answer. Media and structured Discord payloads are still delivered as separate payloads so attachments/components are not dropped
### Schedule Types
| Kind | CLI flag | Description |
|---|---|---|
| `at` | `--at` | One-shot timestamp (ISO 8601 or relative like `20m`) |
| `every` | `--every` | Fixed interval |
| `cron` | `--cron` | 5-field or 6-field cron expression with optional `--tz` |
- Timestamps without timezone are treated as UTC
- Add `--tz America/New_York` for local wall-clock scheduling
- Recurring top-of-hour expressions are automatically staggered by up to 5 minutes — use `--exact` to force precise timing or `--stagger 30s` for explicit window
#### GOTCHA: Day-of-Month AND Day-of-Week Use OR Logic
```
# Intended: "9 AM on the 15th, only if it's a Monday"
# Actual: "9 AM on every 15th, AND 9 AM on every Monday"
0 9 15 * 1
```
This fires ~5-6 times/month instead of 0-1. Use Croner's `+` modifier (`0 9 15 * +1`) or guard the other condition in the job's prompt.
### Execution Styles
| Style | `--session` value | Runs in | Best for |
|---|---|---|---|
| Main session | `main` | Next heartbeat turn | Reminders, system events |
| Isolated | `isolated` | Dedicated `cron:<jobId>` | Reports, background chores |
| Current session | `current` | Bound at creation time | Context-aware recurring work |
| Custom session | `session:custom-id` | Persistent named session | Workflows that build on history |
- **Main session** jobs enqueue a system event and optionally wake the heartbeat (`--wake now` or `--wake next-heartbeat`)
- **Isolated** jobs run a dedicated agent turn with a fresh session. "Fresh session" means a new transcript/session id for each run. Safe preferences (thinking/fast/verbose, labels, explicit user-selected model/auth overrides) may carry, but ambient conversation context is NOT inherited: channel/group routing, send/queue policy, elevation, origin, ACP runtime binding
- **Custom sessions** (`session:xxx`) persist context across runs
#### Payload Options for Isolated Jobs
- `--message`: prompt text (required for isolated)
- `--model` / `--thinking`: model and thinking level overrides. If the requested model is not allowed, cron logs a warning and falls back to the job's agent/default model. A plain model override with no explicit per-job fallback list no longer appends the agent primary as a hidden extra retry target.
- `--light-context`: skip workspace bootstrap file injection
- `--tools exec,read`: restrict which tools the job can use
#### Model Selection Precedence (Isolated Jobs)
1. Gmail hook model override (when applicable)
2. Per-job payload `model`
3. Stored cron session model override
4. Agent/default model selection
If the selected model config has `params.fastMode`, isolated cron uses that by default. A stored session `fastMode` override still wins over config in either direction.
If an isolated run hits a live model-switch handoff, cron retries with the switched provider/model and persists that live selection before retrying. When the switch also carries a new auth profile, cron persists that auth profile override for the active run too. Retries are bounded: after the initial attempt plus 2 switch retries, cron aborts instead of looping.
### Delivery and Output
| Mode | What happens |
|---|---|
| `announce` | Fallback-deliver final text to the target if the agent did not send |
| `webhook` | POST finished event payload to a URL |
| `none` | No runner fallback delivery |
- Use `--announce --channel telegram --to "-1001234567890"` for channel delivery
- For Telegram forum topics: `-1001234567890:topic:123`
- Slack/Discord/Mattermost targets should use explicit prefixes (`channel:<id>`, `user:<id>`)
- If a chat route is available, the agent can use the `message` tool even when the job uses `--no-deliver`
- Failure notifications: `cron.failureDestination` sets global default, `job.delivery.failureDestination` overrides per job; if neither is set and the job already delivers via `announce`, failure notifications fall back to that primary announce target
- `delivery.failureDestination` is only supported on `sessionTarget="isolated"` jobs unless the primary delivery mode is `webhook`
- If the isolated run returns only `NO_REPLY` / `no_reply`, OpenClaw suppresses delivery
### CLI Examples
**One-shot reminder (main session):**
```bash
openclaw cron add \
--name "Calendar check" \
--at "20m" \
--session main \
--system-event "Next heartbeat: check calendar." \
--wake now
```
**Recurring isolated job with delivery:**
```bash
openclaw cron add \
--name "Morning brief" \
--cron "0 7 * * *" \
--tz "America/Los_Angeles" \
--session isolated \
--message "Summarize overnight updates." \
--announce \
--channel slack \
--to "channel:C1234567890"
```
**Isolated job with model and thinking override:**
```bash
openclaw cron add \
--name "Deep analysis" \
--cron "0 6 * * 1" \
--tz "America/Los_Angeles" \
--session isolated \
--message "Weekly deep analysis of project progress." \
--model "opus" \
--thinking high \
--announce
```
**One-shot reminder with delete-after-run:**
```bash
openclaw cron add \
--name "Reminder" \
--at "2026-02-01T16:00:00Z" \
--session main \
--system-event "Reminder: check the cron docs draft" \
--wake now \
--delete-after-run
```
### Webhooks
Enable in config:
```json5
{
hooks: {
enabled: true,
token: "shared-secret",
path: "/hooks",
},
}
```
**Authentication**: Every request must include:
- `Authorization: Bearer <token>` (recommended)
- `x-openclaw-token: <token>`
Query-string tokens are rejected.
**POST /hooks/wake** — enqueue a system event for main session:
```bash
curl -X POST http://127.0.0.1:18789/hooks/wake \
-H 'Authorization: Bearer SECRET' \
-H 'Content-Type: application/json' \
-d '{"text":"New email received","mode":"now"}'
```
- `text` (required): event description
- `mode` (optional): `now` (default) or `next-heartbeat`
**POST /hooks/agent** — run an isolated agent turn:
```bash
curl -X POST http://127.0.0.1:18789/hooks/agent \
-H 'Authorization: Bearer SECRET' \
-H 'Content-Type: application/json' \
-d '{"message":"Summarize inbox","name":"Email","model":"openai/gpt-5.4"}'
```
Fields: `message` (required), `name`, `agentId`, `wakeMode`, `deliver`, `channel`, `to`, `model`, `thinking`, `timeoutSeconds`.
**POST /hooks/<name>** — mapped custom hooks via `hooks.mappings` in config.
**Security guidelines:**
- Keep hook endpoints behind loopback, tailnet, or trusted reverse proxy
- Use a dedicated hook token; do not reuse gateway auth tokens
- Keep `hooks.path` on a dedicated subpath; `/` is rejected
- Set `hooks.allowedAgentIds` to limit explicit `agentId` routing
- Keep `hooks.allowRequestSessionKey=false` unless you require caller-selected sessions
- If you enable `hooks.allowRequestSessionKey`, also set `hooks.allowedSessionKeyPrefixes` to constrain allowed session key shapes
- Hook payloads are wrapped with safety boundaries by default
### Gmail PubSub Integration
Wire Gmail inbox triggers to OpenClaw via Google PubSub.
**Wizard setup (recommended):**
```bash
openclaw webhooks gmail setup --account [email protected]
```
When `hooks.enabled=true` and `hooks.gmail.account` is set, the Gateway starts `gog gmail watch serve` on boot. Set `OPENCLAW_SKIP_GMAIL_WATCHER=1` to opt out.
**Gmail model override:**
```json5
{
hooks: {
gmail: {
model: "openrouter/meta-llama/llama-3.3-70b-instruct:free",
thinking: "off",
},
},
}
```
### Managing Cron Jobs
```bash
# List all jobs
openclaw cron list
# Show one job
openclaw cron show <jobId>
# Edit a job
openclaw cron edit <jobId> --message "Updated prompt" --model "opus"
# Force run a job now
openclaw cron run <jobId>
# Run only if due
openclaw cron run <jobId> --due
# View run history
openclaw cron runs --id <jobId> --limit 50
# Delete a job
openclaw cron remove <jobId>
# Agent selection (multi-agent setups)
openclaw cron add --name "Ops sweep" --cron "0 6 * * *" --session isolated --message "Check ops queue" --agent ops
openclaw cron edit <jobId> --clear-agent
```
### Cron Configuration
```json5
{
cron: {
enabled: true,
store: "~/.openclaw/cron/jobs.json",
maxConcurrentRuns: 1,
retry: {
maxAttempts: 3,
backoffMs: [60000, 120000, 300000],
retryOn: ["rate_limit", "overloaded", "network", "server_error"],
},
webhookToken: "replace-with-dedicated-webhook-token",
sessionRetention: "24h",
runLog: { maxBytes: "2mb", keepLines: 2000 },
},
}
```
- Disable cron: `cron.enabled: false` or `OPENCLAW_SKIP_CRON=1`
- **One-shot retry**: transient errors retry up to 3 times with exponential backoff
- **Recurring retry**: exponential backoff (30s to 60m) between retries; resets after success
- **Maintenance**: `cron.sessionRetention` (default `24h`) prunes isolated run-session entries
### Cron Troubleshooting
```bash
openclaw status
openclaw gateway status
openclaw cron status
openclaw cron list
openclaw cron runs --id <jobId> --limit 20
openclaw system heartbeat last
openclaw logs --follow
openclaw doctor
```
**Cron not firing:**
- Check `cron.enabled` and `OPENCLAW_SKIP_CRON` env var
- Confirm the Gateway is running continuously
- For `cron` schedules, verify timezone (`--tz`) vs host timezone
**Cron fired but no delivery:**
- Delivery mode `none` means no runner fallback send is expected
- Delivery target missing/invalid (`channel`/`to`) means outbound was skipped
- If the isolated run returns only `NO_REPLY` / `no_reply`, OpenClaw suppresses delivery
**Timezone gotchas:**
- Cron without `--tz` uses the gateway host timezone
- `at` schedules without timezone are treated as UTC
---
## Background Tasks
Background tasks track work that runs **outside your main conversation session**: ACP runs, subagent spawns, isolated cron job executions, and CLI-initiated operations.
Tasks are **records**, not schedulers. They tell you what happened, when, and whether it succeeded.
Completion is push-driven: detached work can notify directly or wake the requester session/heartbeat when it finishes, so status polling loops are usually the wrong shape.
**What does NOT create tasks:**
- Heartbeat turns
- Normal interactive chat turns
- Direct `/command` responses
### What Creates a Task
| Source | Runtime type | Default notify policy |
|---|---|---|
| ACP background runs | `acp` | `done_only` |
| Subagent orchestration | `subagent` | `done_only` |
| Cron jobs (all types) | `cron` | `silent` |
| CLI operations | `cli` | `silent` |
| Agent media jobs (video_generate, music_generate) | `cli` | `silent` |
> **video_generate guardrail**: While a session-backed `video_generate` task is still active, repeated `video_generate` calls in that same session return the active task status instead of starting a second concurrent generation.
> **Async completion**: If `tools.media.asyncCompletion.directSend` is enabled, `music_generate` and `video_generate` async completions try direct channel delivery first before falling back to the requester-session wake path.
### Task Lifecycle
```
queued → running → succeeded | failed | timed_out | cancelled | lost
```
| Status | What it means |
|---|---|
| `queued` | Created, waiting for agent to start |
| `running` | Agent turn is actively executing |
| `succeeded` | Completed successfully |
| `failed` | Completed with an error |
| `timed_out` | Exceeded configured timeout |
| `cancelled` | Stopped by operator via `openclaw tasks cancel` |
| `lost` | Runtime lost authoritative backing state after 5-minute grace period |
`lost` is runtime-aware: ACP tasks check child session metadata; subagent tasks check child session in target agent store; cron tasks check cron runtime ownership; CLI tasks use child session for isolated runs but live run context for chat-backed CLI tasks (lingering session rows do not keep them alive).
### Delivery and Notifications
**Direct delivery**: If the task has a channel target (`requesterOrigin`), completion goes straight to that channel. For subagent completions, OpenClaw preserves bound thread/topic routing when available and can fill a missing `to`/account from the requester session's stored routing.
**Session-queued delivery**: If direct delivery fails, the update is queued as a system event in the requester's session and surfaces on the next heartbeat.
Task completion triggers an immediate heartbeat wake so you see the result quickly.
### Notification Policies
| Policy | What is delivered |
|---|---|
| `done_only` (default) | Only terminal state |
| `state_changes` | Every state transition and progress update |
| `silent` | Nothing at all |
Change the policy while a task is running:
```bash
openclaw tasks notify <lookup> state_changes
```
### CLI Reference
```bash
# List all tasks (newest first)
openclaw tasks list
# Filter by runtime or status
openclaw tasks list --runtime acp
openclaw tasks list --status running
# Show details for a specific task
openclaw tasks show <lookup>
# Cancel a running task (kills the child session)
openclaw tasks cancel <lookup>
# Change notification policy
openclaw tasks notify <lookup> state_changes
# Run a health audit
openclaw tasks audit
# Preview or apply maintenance
openclaw tasks maintenance
openclaw tasks maintenance --apply
# Inspect TaskFlow state
openclaw tasks flow list
openclaw tasks flow show <lookup>
openclaw tasks flow cancel <lookup>
```
### `openclaw tasks audit` Findings
| Finding | Severity | Trigger |
|---|---|---|
| `stale_queued` | warn | Queued for more than 10 minutes |
| `stale_running` | error | Running for more than 30 minutes |
| `lost` | error | Runtime-backed task ownership disappeared |
| `delivery_failed` | warn | Delivery failed and notify policy is not `silent` |
| `missing_cleanup` | warn | Terminal task with no cleanup timestamp |
| `inconsistent_timestamps` | warn | Timeline violation |
### Chat Task Board (`/tasks`)
Use `/tasks` in any chat session to see background tasks linked to that session. Falls back to agent-local task counts when no linked tasks are visible.
### Storage and Maintenance
Task records persist in SQLite at: `$OPENCLAW_STATE_DIR/tasks/runs.sqlite`
A sweeper runs every **60 seconds** and handles:
1. **Reconciliation** — checks whether active tasks still have authoritative runtime backing
2. **Cleanup stamping** — sets a `cleanupAfter` timestamp on terminal tasks (endedAt + 7 days)
3. **Pruning** — deletes records past their `cleanupAfter` date
**Retention**: terminal task records are kept for **7 days**, then automatically pruned.
---
## Task Flow
Task Flow is the flow orchestration substrate that sits above background tasks. It manages durable multi-step flows with their own state, revision tracking, and sync semantics.
### When to Use Task Flow
| Scenario | Use |
|---|---|
| Single background job | Plain task |
| Multi-step pipeline (A then B then C) | Task Flow (managed) |
| Observe externally created tasks | Task Flow (mirrored) |
| One-shot reminder | Cron job |
### Reliable Scheduled Workflow Pattern
For recurring workflows (e.g., market intelligence briefings):
1. Use Scheduled Tasks for timing
2. Use a persistent cron session for context building
3. Use Lobster for deterministic steps, approval gates, and resume tokens
4. Use Task Flow to track the multi-step run across child tasks, waits, retries, and gateway restarts
```bash
openclaw cron add \
--name "Market intelligence brief" \
--cron "0 7 * * 1-5" \
--tz "America/New_York" \
--session session:market-intel \
--message "Run the market-intel Lobster workflow. Verify source freshness before summarizing." \
--announce \
--channel slack \
--to "channel:C1234567890"
```
**Use `session:<id>`** (not `isolated`) when the recurring workflow needs deliberate history.
### Sync Modes
**Managed mode**: Task Flow owns the lifecycle end-to-end. Creates tasks as flow steps, drives them to completion, advances flow state automatically.
```
Flow: weekly-report
Step 1: gather-data → task created → succeeded
Step 2: generate-report → task created → succeeded
Step 3: deliver → task created → running
```
**Mirrored mode**: Task Flow observes externally created tasks and keeps flow state in sync without taking ownership of task creation.
### Durable State and Revision Tracking
Each flow persists its own state and tracks revisions so progress survives gateway restarts. Revision tracking enables conflict detection when multiple sources attempt to advance the same flow concurrently.
### Cancel Behavior
`openclaw tasks flow cancel` sets a sticky cancel intent on the flow. Active tasks within the flow are cancelled, and no new steps are started. The cancel intent persists across restarts, so a cancelled flow stays cancelled even if the gateway restarts before all child tasks have terminated.
### CLI Commands
```bash
# List active and recent flows
openclaw tasks flow list
# Show details for a specific flow
openclaw tasks flow show <lookup>
# Cancel a running flow and its active tasks
openclaw tasks flow cancel <lookup>
```
---
## Hooks
Hooks are small scripts that run when something happens inside the Gateway. There are two kinds:
1. **Internal hooks** (this section): run inside the Gateway when agent events fire
2. **Webhooks**: external HTTP endpoints that let other systems trigger work in OpenClaw
### Event Types
| Event | When it fires |
|---|---|
| `command:new` | `/new` command issued |
| `command:reset` | `/reset` command issued |
| `command:stop` | `/stop` command issued |
| `command` | Any command event (general listener) |
| `session:compact:before` | Before compaction summarizes history |
| `session:compact:after` | After compaction completes |
| `session:patch` | When session properties are modified |
| `agent:bootstrap` | Before workspace bootstrap files are injected |
| `gateway:startup` | After channels start and hooks are loaded |
| `message:received` | Inbound message from any channel |
| `message:transcribed` | After audio transcription completes |
| `message:preprocessed` | After all media and link understanding completes |
| `message:sent` | Outbound message delivered |
### Hook Structure
```
my-hook/
├── HOOK.md # Metadata + documentation
└── handler.ts # Handler implementation
```
**HOOK.md format:**
```markdown
---
name: my-hook
description: "Short description of what this hook does"
metadata:
{ "openclaw": { "emoji": "🔗", "events": ["command:new"], "requires": { "bins": ["node"] } } }
---
# My Hook
Detailed documentation goes here.
```
**Metadata fields (`metadata.openclaw`):**
| Field | Description |
|---|---|
| `emoji` | Display emoji for CLI |
| `events` | Array of events to listen for |
| `export` | Named export to use (defaults to `"default"`) |
| `os` | Required platforms (e.g., `["darwin", "linux"]`) |
| `requires` | Required `bins`, `anyBins`, `env`, or `config` paths |
| `always` | Bypass eligibility checks (boolean) |
| `install` | Installation methods |
**Handler implementation:**
```typescript
const handler = async (event) => {
if (event.type !== "command" || event.action !== "new") {
return;
}
console.log(`[my-hook] New command triggered`);
// Your logic here
// Optionally send message to user
event.messages.push("Hook executed!");
};
export default handler;
```
Each event includes: `type`, `action`, `sessionKey`, `timestamp`, `messages` (push to send to user), and `context` (event-specific data).
### Event Context Highlights
- **Command events** (`command:new`, `command:reset`): `context.sessionEntry`, `context.previousSessionEntry`, `context.commandSource`, `context.workspaceDir`, `context.cfg`
- **Message events** (`message:received`): `context.from`, `context.content`, `context.channelId`, `context.metadata` (provider-specific data including `senderId`, `senderName`, `guildId`)
- **Message events** (`message:sent`): `context.to`, `context.content`, `context.success`, `context.channelId`
- **Message events** (`message:transcribed`): `context.transcript`, `context.from`, `context.channelId`, `context.mediaPath`
- **Message events** (`message:preprocessed`): `context.bodyForAgent` (final enriched body), `context.from`, `context.channelId`
- **Session patch events** (`session:patch`): `context.sessionEntry`, `context.patch` (only changed fields), `context.cfg`. Only privileged clients can trigger patch events.
- **Bootstrap events** (`agent:bootstrap`): `context.bootstrapFiles` (mutable array), `context.agentId`
- **Compaction events**: `session:compact:before` includes `messageCount`, `tokenCount`; `session:compact:after` adds `compactedCount`, `summaryLength`, `tokensBefore`, `tokensAfter`
Agent and tool plugin hook contexts can also include `trace`, a read-only W3C-compatible diagnostic trace context that plugins may pass into structured logs for OTEL correlation.
### Hook Discovery
Hooks are discovered in order of increasing override precedence:
1. **Bundled hooks**: shipped with OpenClaw
2. **Plugin hooks**: hooks bundled inside installed plugins
3. **Managed hooks**: `~/.openclaw/hooks/` (user-installed, shared across workspaces)
4. **Workspace hooks**: `<workspace>/hooks/` (per-agent, disabled by default)
Workspace hooks can add new hook names but **cannot override** bundled, managed, or plugin-provided hooks with the same name.
The Gateway skips internal hook discovery on startup until internal hooks are configured. Enable a bundled or managed hook with `openclaw hooks enable <name>`, install a hook pack, or set `hooks.internal.enabled=true` to opt in.
**Hook packs**: npm packages that export hooks via `openclaw.hooks` in `package.json`. Install with:
```bash
openclaw plugins install <path-or-spec>
```
Npm specs are registry-only (package name + optional exact version or dist-tag). Git/URL/file specs and semver ranges are rejected.
### Bundled Hooks
| Hook | Events | What it does |
|---|---|---|
| session-memory | `command:new`, `command:reset` | Saves session context to `<workspace>/memory/` |
| bootstrap-extra-files | `agent:bootstrap` | Injects additional bootstrap files from glob patterns |
| command-logger | `command` | Logs all commands to `~/.openclaw/logs/commands.log` |
| boot-md | `gateway:startup` | Runs `BOOT.md` when the gateway starts |
```bash
openclaw hooks enable session-memory
```
**session-memory**: Extracts the last 15 user/assistant messages, generates a descriptive filename slug via LLM, and saves to `<workspace>/memory/YYYY-MM-DD-slug.md`.
**bootstrap-extra-files config:**
```json
{
"hooks": {
"internal": {
"entries": {
"bootstrap-extra-files": {
"enabled": true,
"paths": ["packages/*/AGENTS.md", "packages/*/TOOLS.md"]
}
}
}
}
}
```
Only recognized bootstrap basenames are loaded: `AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, `BOOTSTRAP.md`, `MEMORY.md`.
### Configuration
```json
{
"hooks": {
"internal": {
"enabled": true,
"entries": {
"session-memory": { "enabled": true },
"command-logger": { "enabled": false }
}
}
}
}
```
Per-hook environment variables:
```json
{
"hooks": {
"internal": {
"entries": {
"my-hook": {
"enabled": true,
"env": { "MY_CUSTOM_VAR": "value" }
}
}
}
}
}
```
Extra hook directories:
```json
{
"hooks": {
"internal": {
"load": {
"extraDirs": ["/path/to/more/hooks"]
}
}
}
}
```
### CLI Reference
```bash
# List all hooks
openclaw hooks list
# Show detailed info about a hook
openclaw hooks info <hook-name>
# Show eligibility summary
openclaw hooks check
# Enable/disable
openclaw hooks enable <hook-name>
openclaw hooks disable <hook-name>
```
### Hook Best Practices
- **Keep handlers fast**: Hooks run during command processing. Fire-and-forget heavy work with `void processInBackground(event)`
- **Handle errors gracefully**: Wrap risky operations in try/catch; do not throw
- **Filter events early**: Return immediately if the event type/action is not relevant
- **Use specific event keys**: Prefer `"events": ["command:new"]` over `"events": ["command"]`
### Troubleshooting Hooks
```bash
# Verify directory structure
ls -la ~/.openclaw/hooks/my-hook/
# Should show: HOOK.md, handler.ts
# List all discovered hooks
openclaw hooks list
# Check eligibility
openclaw hooks info my-hook
```
1. Verify the hook is enabled: `openclaw hooks list`
2. Check eligibility for missing binaries, env vars, or OS compatibility: `openclaw hooks info <name>`
3. Check gateway logs: `./scripts/clawlog.sh | grep hook`
4. Restart the gateway — hooks only reload on Gateway restart
---
## Standing Orders
Standing orders grant your agent **permanent operating authority** for defined programs. They are defined in workspace files and injected into every session automatically.
### Why Standing Orders?
**Without standing orders:**
- You must prompt the agent for every task
- The agent sits idle between requests
- Routine work gets forgotten or delayed
- You become the bottleneck
**With standing orders:**
- The agent executes autonomously within defined boundaries
- Routine work happens on schedule without prompting
- You only get involved for exceptions and approvals
- The agent fills idle time productively
### How They Work
Standing orders live in workspace files — ideally `AGENTS.md` (auto-injected every session). Each program specifies:
1. **Scope** — what the agent is authorized to do
2. **Triggers** — when to execute (schedule, event, or condition)
3. **Approval gates** — what requires human sign-off before acting
4. **Escalation rules** — when to stop and ask for help
**Tip**: Put standing orders in `AGENTS.md` to guarantee they're loaded every session. Bootstrap files auto-injected: `AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, `BOOTSTRAP.md`, `MEMORY.md` — but not arbitrary files in subdirectories.
### Anatomy of a Standing Order
```markdown
## Program: Weekly Status Report
**Authority:** Compile data, generate report, deliver to stakeholders
**Trigger:** Every Friday at 4 PM (enforced via cron job)
**Approval gate:** None for standard reports. Flag anomalies for human review.
**Escalation:** If data source is unavailable or metrics look unusual (>2σ from norm)
### Execution Steps
1. Pull metrics from configured sources
2. Compare to prior week and targets
3. Generate report in Reports/weekly/YYYY-MM-DD.md
4. Deliver summary via configured channel
5. Log completion to Agent/Logs/
### What NOT to Do
- Do not send reports to external parties
- Do not modify source data
- Do not skip delivery if metrics look bad — report accurately
```
### Standing Orders + Cron Jobs
Standing orders define **what** the agent is authorized to do. Cron jobs define **when** it happens.
```
Standing Order: "You own the daily inbox triage"
↓
Cron Job (8 AM daily): "Execute inbox triage per standing orders"
↓
Agent: Reads standing orders → executes steps → reports results
```
The cron job prompt should reference the standing order rather than duplicating it:
```bash
openclaw cron add \
--name daily-inbox-triage \
--cron "0 8 * * 1-5" \
--tz America/New_York \
--timeout-seconds 300 \
--announce \
--channel bluebubbles \
--to "+1XXXXXXXXXX" \
--message "Execute daily inbox triage per standing orders. Check mail for new alerts. Parse, categorize, and persist each item. Report summary to owner. Escalate unknowns."
```
### Examples
**Content & Social Media (Weekly Cycle):**
```markdown
## Program: Content & Social Media
**Authority:** Draft content, schedule posts, compile engagement reports
**Approval gate:** All posts require owner review for first 30 days, then standing approval
**Trigger:** Weekly cycle (Monday review → mid-week drafts → Friday brief)
### Weekly Cycle
- **Monday:** Review platform metrics and audience engagement
- **Tuesday–Thursday:** Draft social posts, create blog content
- **Friday:** Compile weekly marketing brief → deliver to owner
```
**Finance Operations (Event-Triggered):**
```markdown
## Program: Financial Processing
**Authority:** Process transaction data, generate reports, send summaries
**Approval gate:** None for analysis. Recommendations require owner approval.
**Trigger:** New data file detected OR scheduled monthly cycle
### Escalation Rules
- Single item > $500: immediate alert
- Category > budget by 20%: flag in report
- Unrecognizable transaction: ask owner for categorization
- Failed processing after 2 retries: report failure, do not guess
```
**System Monitoring (Continuous):**
```markdown
## Program: System Monitoring
**Authority:** Check system health, restart services, send alerts
**Approval gate:** Restart services automatically. Escalate if restart fails twice.
**Trigger:** Every heartbeat cycle
### Response Matrix
| Condition | Action | Escalate? |
| ---------------- | ------------------------ | ------------------------ |
| Service down | Restart automatically | Only if restart fails 2x |
| Disk space < 10% | Alert owner | Yes |
```
### The Execute-Verify-Report Pattern
Every task in a standing order should follow this loop:
1. **Execute** — Do the actual work
2. **Verify** — Confirm the result is correct (file exists, message delivered, data parsed)
3. **Report** — Tell the owner what was done and what was verified
```markdown
### Execution Rules
- Every task follows Execute-Verify-Report. No exceptions.
- "I'll do that" is not execution. Do it, then report.
- "Done" without verification is not acceptable. Prove it.
- If execution fails: retry once with adjusted approach.
- If still fails: report failure with diagnosis. Never silently fail.
- Never retry indefinitely — 3 attempts max, then escalate.
```
### Best Practices
**Do:**
- Start with narrow authority and expand as trust builds
- Define explicit approval gates for high-risk actions
- Include "What NOT to do" sections
- Combine with cron jobs for reliable time-based execution
- Review agent logs weekly to verify standing orders are being followed
**Avoid:**
- Grant broad authority on day one ("do whatever you think is best")
- Skip escalation rules
- Mix concerns in a single program — separate programs for separate domains
- Forget to enforce with cron jobs — standing orders without triggers become suggestions
---
## Heartbeat
Heartbeat is a periodic main-session turn (default every 30 minutes). It batches multiple checks (inbox, calendar, notifications) in one agent turn with full session context.
**Key characteristics:**
- Heartbeat turns do **not** create task records
- Uses `HEARTBEAT.md` for a small checklist, or a `tasks:` block for due-only periodic checks
- Empty heartbeat files skip as `empty-heartbeat-file`
- Due-only task mode skips as `no-tasks-due`
- `showOk`, `showAlerts`, `useIndicator` all off → skips as `alerts-disabled`
- Default interval: `30m` (or `1h` when Anthropic OAuth/token auth is detected, including Claude CLI reuse). Set `0m` to disable heartbeats entirely; this also removes `HEARTBEAT.md` from bootstrap context.
- `target`: `none` (default) | `last` (last contact). `directPolicy: "allow"` (default) or `"block"` for DM targets.
- `lightContext: true` — only inject `HEARTBEAT.md` from bootstrap files (skip AGENTS.md, SOUL.md, etc.)
- `isolatedSession: true` — fresh session each run (no conversation history)
- `activeHours: { start: "08:00", end: "24:00" }` — restrict to local time window
- `includeReasoning: true` — deliver separate `Reasoning:` message when available
- Response contract: `HEARTBEAT_OK` at start/end → ack, stripped if remaining content ≤ `ackMaxChars` (default: 300). In middle → not treated specially. Outside heartbeats, stray `HEARTBEAT_OK` at start/end is stripped and logged; message-only `HEARTBEAT_OK` is dropped.
See [Heartbeat configuration](/gateway/heartbeat) for full reference.
---
## How They Work Together
- **Cron** handles precise schedules (daily reports, weekly reviews) and one-shot reminders. All cron executions create task records.
- **Heartbeat** handles routine monitoring (inbox, calendar, notifications) in one batched turn every 30 minutes.
- **Hooks** react to specific events (session resets, compaction, message flow) with custom scripts.
- **Standing orders** give the agent persistent context and authority boundaries.
- **Task Flow** coordinates multi-step flows above individual tasks.
- **Tasks** automatically track all detached work so you can inspect and audit it.
FILE:references/09-installation.md
# OpenClaw Installation & Setup Reference
## Table of Contents
- [Getting Started](#getting-started)
- [Updating](#updating)
- [Docker](#docker)
- [Nix](#nix)
- [Raspberry Pi](#raspberry-pi)
- [Uninstall](#uninstall)
- [Onboarding (CLI Wizard)](#onboarding-cli-wizard)
- [VPS / Linux Server Deployment](#vps--linux-server-deployment)
- [Migration Guide](#migration-guide)
- [Platform Notes](#platform-notes)
- [Control UI (Custom Build)](#control-ui-custom-build)
## Getting Started
### Requirements
- **Node.js** — Node 24 recommended (Node 22.14+ also supported)
- **API key** from a model provider (Anthropic, OpenAI, Google, etc.)
Check Node version: `node --version`
### Quick Setup
**macOS / Linux:**
```bash
curl -fsSL https://openclaw.ai/install.sh | bash
```
**Windows (PowerShell):**
```powershell
iwr -useb https://openclaw.ai/install.ps1 | iex
```
**Run onboarding:**
```bash
openclaw onboard --install-daemon
```
**Verify install:**
```bash
openclaw --version # confirm the CLI is available
openclaw doctor # check for config issues
openclaw gateway status # verify the Gateway is running
```
**Open dashboard:**
```bash
openclaw dashboard
```
### Alternative Install Methods
**Local prefix installer (`install-cli.sh`)** — keeps OpenClaw and Node under `~/.openclaw` without a system-wide Node install:
```bash
curl -fsSL https://openclaw.ai/install-cli.sh | bash
```
**Install from GitHub main branch:**
```bash
npm install -g github:openclaw/openclaw#main
```
**pnpm** — requires explicit build approval after first install:
```bash
pnpm add -g openclaw@latest
pnpm approve-builds -g # required: pnpm requires explicit approval for packages with build scripts
openclaw onboard --install-daemon
```
**From source:**
```bash
git clone https://github.com/openclaw/openclaw.git
cd openclaw
pnpm install && pnpm build && pnpm ui:build
pnpm link --global
openclaw onboard --install-daemon
```
**Troubleshooting sharp build errors (npm):** If `sharp` fails due to a globally installed libvips:
```bash
SHARP_IGNORE_GLOBAL_LIBVIPS=1 npm install -g openclaw@latest
```
---
## Updating
### Recommended: `openclaw update`
```bash
openclaw update
```
The fastest way to update. Detects your install type (npm or git), fetches the latest version, runs `openclaw doctor`, and restarts the gateway.
**Switch channels or target a specific version:**
```bash
openclaw update --channel beta
openclaw update --tag main
openclaw update --dry-run # preview without applying
```
`--channel beta` prefers beta but falls back to stable/latest when the beta tag is missing or older. Use `--tag beta` if you want the raw npm beta dist-tag for a one-off package update.
### Alternative: Re-run the installer
```bash
curl -fsSL https://openclaw.ai/install.sh | bash
```
Add `--no-onboard` to skip onboarding. For source installs: `--install-method git --no-onboard`.
### Alternative: Manual npm/pnpm/bun
```bash
npm i -g openclaw@latest
pnpm add -g openclaw@latest # also run: pnpm approve-builds -g
bun add -g openclaw@latest # Bun is supported for CLI; for the Gateway runtime, Node is recommended
```
Tip: `npm view openclaw version` shows the current published version.
### Root-Owned Global npm Installs
Some Linux npm setups install global packages under root-owned directories. OpenClaw supports that layout: the installed package is treated as read-only at runtime, and bundled plugin runtime dependencies are staged into a writable runtime directory.
For hardened systemd units:
```ini
Environment=OPENCLAW_PLUGIN_STAGE_DIR=/var/lib/openclaw/plugin-runtime-deps
ReadWritePaths=/var/lib/openclaw /home/openclaw/.openclaw /tmp
```
If `OPENCLAW_PLUGIN_STAGE_DIR` is not set, OpenClaw uses `$STATE_DIRECTORY` when systemd provides it, then falls back to `~/.openclaw/plugin-runtime-deps`.
### Auto-Updater
Off by default. Enable in `~/.openclaw/openclaw.json`:
```json5
{
update: {
channel: "stable",
checkOnStart: false, // set false to disable startup update hint
auto: {
enabled: true,
stableDelayHours: 6,
stableJitterHours: 12,
betaCheckIntervalHours: 1,
},
},
}
```
**Bundled plugin runtime dependencies**: Packaged installs keep bundled plugin runtime dependencies out of the read-only package tree. On startup and during `openclaw doctor --fix`, OpenClaw repairs runtime dependencies only for bundled plugins that are active in config, active through legacy channel config, or enabled by their bundled manifest default. Explicit disablement wins — a disabled plugin or channel does not get its runtime dependencies repaired just because it exists in the package. External plugins and custom load paths still use `openclaw plugins install` or `openclaw plugins update`. The `OPENCLAW_PLUGIN_STAGE_DIR` env var controls where staged deps land.
OpenClaw treats packaged global installs as read-only at runtime, even when writable by the current user. This keeps `openclaw update` from racing with a running gateway or local agent repairing plugin dependencies during the same install.
| Channel | Behavior |
|---|---|
| `stable` | Waits `stableDelayHours`, then applies with deterministic jitter |
| `beta` | Checks every `betaCheckIntervalHours` (default: hourly) and applies immediately |
| `dev` | No automatic apply. Use `openclaw update` manually |
### After Updating
```bash
# Run doctor (migrates config, audits DM policies, checks gateway health)
openclaw doctor
# Restart the gateway
openclaw gateway restart
# Verify
openclaw health
```
### Rollback
**Pin a version (npm):**
```bash
npm i -g openclaw@<version>
openclaw doctor
openclaw gateway restart
```
**Pin a commit (source):**
```bash
git fetch origin
git checkout "$(git rev-list -n 1 --before=\"2026-01-01\" origin/main)"
pnpm install && pnpm build
openclaw gateway restart
```
---
## Docker
Docker is **optional**. Use it only if you want a containerized gateway or to validate the Docker flow.
**Is Docker right for me?**
- **Yes**: you want an isolated, throwaway gateway environment or to run OpenClaw on a host without local installs
- **No**: you are running on your own machine and just want the fastest dev loop
- **Note**: the default sandbox backend uses Docker when sandboxing is enabled, but sandboxing is off by default and does **not** require the full gateway to run in Docker. SSH and OpenShell sandbox backends are also available. See [Sandboxing](/gateway/sandboxing).
- If running on a VPS/public host, review [Security hardening for network exposure](/gateway/security), especially Docker `DOCKER-USER` firewall policy
### Prerequisites
- Docker Desktop (or Docker Engine) + Docker Compose v2
- At least 2 GB RAM for image build (`pnpm install` may be OOM-killed on 1 GB hosts)
- Enough disk for images and logs
### Containerized Gateway Setup
```bash
# Step 1: Build the image
./scripts/docker/setup.sh
# Or use a pre-built image
export OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:latest"
./scripts/docker/setup.sh
```
Pre-built image tags: `main`, `latest`, `<version>` (e.g. `2026.2.26`)
During setup, onboarding runs automatically (prompts for API keys, generates gateway token, starts gateway via Docker Compose).
**Open the Control UI:**
```bash
docker compose run --rm openclaw-cli dashboard --no-open
```
**Configure channels:**
```bash
# WhatsApp (QR)
docker compose run --rm openclaw-cli channels login
# Telegram
docker compose run --rm openclaw-cli channels add --channel telegram --token "<token>"
# Discord
docker compose run --rm openclaw-cli channels add --channel discord --token "<token>"
```
### Manual Docker Flow
```bash
docker build -t openclaw:local -f Dockerfile .
docker compose run --rm --no-deps --entrypoint node openclaw-gateway \
dist/index.js onboard --mode local --no-install-daemon
docker compose run --rm --no-deps --entrypoint node openclaw-gateway \
dist/index.js config set --batch-json '[{"path":"gateway.mode","value":"local"},{"path":"gateway.bind","value":"lan"},{"path":"gateway.controlUi.allowedOrigins","value":["http://localhost:18789","http://127.0.0.1:18789"]}]'
docker compose up -d openclaw-gateway
```
### Environment Variables
| Variable | Purpose |
|---|---|
| `OPENCLAW_IMAGE` | Use a remote image instead of building locally |
| `OPENCLAW_DOCKER_APT_PACKAGES` | Install extra apt packages during build (space-separated) |
| `OPENCLAW_EXTENSIONS` | Pre-install plugin deps at build time (space-separated names) |
| `OPENCLAW_EXTRA_MOUNTS` | Extra host bind mounts (comma-separated `source:target[:opts]`) |
| `OPENCLAW_HOME_VOLUME` | Persist `/home/node` in a named Docker volume |
| `OPENCLAW_SANDBOX` | Opt in to sandbox bootstrap (`1`, `true`, `yes`, `on`) |
| `OPENCLAW_DOCKER_SOCKET` | Override Docker socket path |
### Health Checks
```bash
# Container probe endpoints (no auth required)
curl -fsS http://127.0.0.1:18789/healthz # liveness
curl -fsS http://127.0.0.1:18789/readyz # readiness
# Authenticated deep health snapshot
docker compose exec openclaw-gateway node dist/index.js health --token "$OPENCLAW_GATEWAY_TOKEN"
```
The Docker image includes a built-in `HEALTHCHECK` that pings `/healthz`. If checks keep failing, Docker marks the container as `unhealthy` and orchestration systems can restart or replace it.
**Shared-network security note**: `openclaw-cli` uses `network_mode: "service:openclaw-gateway"` so CLI commands can reach the gateway over `127.0.0.1`. Treat this as a shared trust boundary. The compose config drops `NET_RAW`/`NET_ADMIN` and enables `no-new-privileges` on `openclaw-cli`.
Note: `openclaw-cli` is a post-start tool (it shares the gateway's network namespace). Before `docker compose up -d openclaw-gateway`, run onboarding and setup-time config writes through `openclaw-gateway` with `--no-deps --entrypoint node`.
### LAN vs Loopback
`scripts/docker/setup.sh` defaults `OPENCLAW_GATEWAY_BIND=lan`.
- `lan` (default): host browser and host CLI can reach the published gateway port
- `loopback`: only processes inside the container network namespace can reach the gateway directly
Use bind mode values: `lan` / `loopback` / `custom` / `tailnet` / `auto` — NOT host aliases like `0.0.0.0` or `127.0.0.1`.
### Storage and Persistence
Docker Compose bind-mounts:
- `OPENCLAW_CONFIG_DIR` → `/home/node/.openclaw`
- `OPENCLAW_WORKSPACE_DIR` → `/home/node/.openclaw/workspace`
**Disk growth hotspots:** watch `media/`, session JSONL files, `cron/runs/*.jsonl`, and rolling file logs under `/tmp/openclaw/`.
### Shell Helpers (ClawDock)
```bash
mkdir -p ~/.clawdock && curl -sL https://raw.githubusercontent.com/openclaw/openclaw/main/scripts/clawdock/clawdock-helpers.sh -o ~/.clawdock/clawdock-helpers.sh
echo 'source ~/.clawdock/clawdock-helpers.sh' >> ~/.zshrc && source ~/.zshrc
```
Use `clawdock-start`, `clawdock-stop`, `clawdock-dashboard`, etc. Run `clawdock-help` for all commands.
### Agent Sandbox
When `agents.defaults.sandbox` is enabled with the Docker backend:
```json5
{
agents: {
defaults: {
sandbox: {
mode: "non-main", // off | non-main | all
scope: "agent", // session | agent | shared
},
},
},
}
```
Build the default sandbox image:
```bash
scripts/sandbox-setup.sh
```
### Docker Troubleshooting
- **Image missing**: build with `scripts/sandbox-setup.sh` or set `agents.defaults.sandbox.docker.image`
- **Permission errors in sandbox**: set `docker.user` to a UID:GID matching workspace ownership
- **Custom tools not found**: set `docker.env.PATH` or add a script under `/etc/profile.d/` in Dockerfile
- **OOM-killed during build (exit 137)**: needs at least 2 GB RAM
- **Unauthorized in Control UI**: `docker compose run --rm openclaw-cli devices list` and approve
- **Gateway target shows ws://172.x.x.x**: reset `gateway.mode=local` and `gateway.bind=lan`
---
## Nix
Install OpenClaw declaratively with **[nix-openclaw](https://github.com/openclaw/nix-openclaw)** — a batteries-included Home Manager module.
**What you get:**
- Gateway + macOS app + tools — all pinned
- Launchd service that survives reboots
- Plugin system with declarative config
- Instant rollback: `home-manager switch --rollback`
### Quick Start
1. Install Determinate Nix
2. Create a local flake (from the nix-openclaw repo templates)
3. Configure secrets (plain files at `~/.secrets/` work)
4. Fill in template placeholders and run `home-manager switch`
5. Verify the launchd service is running
### Nix Mode Runtime Behavior
When `OPENCLAW_NIX_MODE=1` is set (automatic with nix-openclaw), OpenClaw enters a deterministic mode that disables auto-install flows.
```bash
export OPENCLAW_NIX_MODE=1
```
On macOS, enable via defaults instead:
```bash
defaults write ai.openclaw.mac openclaw.nixMode -bool true
```
**What changes in Nix mode:**
- Auto-install and self-mutation flows are disabled
- Missing dependencies surface Nix-specific remediation messages
- UI surfaces a read-only Nix mode banner
### Config and State Paths
| Variable | Default |
|---|---|
| `OPENCLAW_HOME` | `HOME` / `USERPROFILE` / `os.homedir()` |
| `OPENCLAW_STATE_DIR` | `~/.openclaw` |
| `OPENCLAW_CONFIG_PATH` | `$OPENCLAW_STATE_DIR/openclaw.json` |
**Service PATH discovery**: The launchd/systemd gateway service auto-discovers Nix-profile binaries so plugins and tools that shell out to `nix`-installed executables work without manual PATH setup. When `NIX_PROFILES` is set, every entry is added to the service PATH in right-to-left precedence (matches Nix shell precedence — rightmost wins). When `NIX_PROFILES` is unset, `~/.nix-profile/bin` is added as a fallback. This applies to both macOS launchd and Linux systemd service environments.
---
## Raspberry Pi
Run a persistent, always-on OpenClaw Gateway on a Raspberry Pi (models run in the cloud via API, so even a modest Pi handles the workload).
### Prerequisites
- Raspberry Pi 4 or 5 with 2 GB+ RAM (4 GB recommended)
- MicroSD card (16 GB+) or USB SSD (better performance)
- 64-bit Raspberry Pi OS (**do not use 32-bit**)
- About 30 minutes
### Setup Steps
1. **Flash OS**: Raspberry Pi OS Lite (64-bit) using Raspberry Pi Imager with SSH enabled
2. **Connect via SSH**: `ssh user@gateway-host`
3. **Update system**: `sudo apt update && sudo apt upgrade -y && sudo apt install -y git curl build-essential`
4. **Set timezone**: `sudo timedatectl set-timezone America/Chicago`
5. **Install Node.js 24**: `curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash - && sudo apt install -y nodejs`
**Add swap (important for 2 GB or less):**
```bash
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
```
6. **Install OpenClaw**: `curl -fsSL https://openclaw.ai/install.sh | bash`
7. **Run onboarding**: `openclaw onboard --install-daemon`
8. **Verify**: `openclaw status && systemctl --user status openclaw-gateway.service`
**Access the Control UI** (from your laptop):
```bash
# Get dashboard URL from the Pi
ssh user@gateway-host 'openclaw dashboard --no-open'
# Create SSH tunnel in another terminal
ssh -N -L 18789:127.0.0.1:18789 user@gateway-host
```
### Performance Tips
- **Use a USB SSD** — SD cards are slow and wear out
- **Enable module compile cache**:
```bash
grep -q 'NODE_COMPILE_CACHE=/var/tmp/openclaw-compile-cache' ~/.bashrc || cat >> ~/.bashrc <<'EOF'
export NODE_COMPILE_CACHE=/var/tmp/openclaw-compile-cache
mkdir -p /var/tmp/openclaw-compile-cache
export OPENCLAW_NO_RESPAWN=1
EOF
source ~/.bashrc
```
- **Reduce memory usage**:
```bash
echo 'gpu_mem=16' | sudo tee -a /boot/config.txt
sudo systemctl disable bluetooth
```
### Raspberry Pi Troubleshooting
- **Out of memory**: verify swap with `free -h`, disable unused services
- **Slow performance**: use USB SSD, check CPU throttling with `vcgencmd get_throttled` (should be `0x0`)
- **Service won't start**: check logs with `journalctl --user -u openclaw-gateway.service --no-pager -n 100` and run `openclaw doctor --non-interactive`. For headless Pi, verify lingering is enabled: `sudo loginctl enable-linger "$(whoami)"`
- **ARM binary issues**: verify `uname -m` shows `aarch64`
- **WiFi drops**: `sudo iwconfig wlan0 power off`
---
## Uninstall
### Easy Path (CLI Still Installed)
**Recommended:**
```bash
openclaw uninstall
```
**Non-interactive:**
```bash
openclaw uninstall --all --yes --non-interactive
npx -y openclaw uninstall --all --yes --non-interactive
```
**Manual steps:**
1. Stop the gateway: `openclaw gateway stop`
2. Uninstall the gateway service: `openclaw gateway uninstall`
3. Delete state + config: `rm -rf "-$HOME/.openclaw"`
4. Delete workspace (optional): `rm -rf ~/.openclaw/workspace`
5. Remove CLI: `npm rm -g openclaw` (or `pnpm remove -g` / `bun remove -g`)
6. If macOS app: `rm -rf /Applications/OpenClaw.app`
### Manual Service Removal (CLI Not Installed)
If you used profiles (`--profile`/`OPENCLAW_PROFILE`), repeat state deletion for each dir (defaults to `~/.openclaw-<profile>`). If `OPENCLAW_CONFIG_PATH` is set to a custom location outside the state dir, delete that file too. For source checkouts: uninstall the gateway service **before** deleting the repo.
**macOS (launchd):**
Default label is `ai.openclaw.gateway` (or `ai.openclaw.<profile>` with named profiles; legacy `com.openclaw.*` may still exist — remove those too):
```bash
launchctl bootout gui/$UID/ai.openclaw.gateway
rm -f ~/Library/LaunchAgents/ai.openclaw.gateway.plist
```
**Linux (systemd user unit):**
Default unit: `openclaw-gateway.service` (or `openclaw-gateway-<profile>.service`):
```bash
systemctl --user disable --now openclaw-gateway.service
rm -f ~/.config/systemd/user/openclaw-gateway.service
systemctl --user daemon-reload
```
**Windows (Scheduled Task):**
```powershell
schtasks /Delete /F /TN "OpenClaw Gateway"
Remove-Item -Force "$env:USERPROFILE\.openclaw\gateway.cmd"
```
---
## Onboarding (CLI Wizard)
CLI onboarding is the **recommended** way to set up OpenClaw on macOS, Linux, or Windows (via WSL2).
```bash
openclaw onboard
```
**To reconfigure later:**
```bash
openclaw configure
openclaw agents add <name>
```
### QuickStart vs Advanced
**QuickStart (defaults):**
- Local gateway (loopback)
- Workspace default (or existing workspace)
- Gateway port **18789**
- Gateway auth **Token** (auto-generated)
- Tool policy default: `tools.profile: "coding"` (when unset)
- DM isolation default: `session.dmScope: "per-channel-peer"` when unset
- Tailscale exposure **Off**
- Telegram + WhatsApp DMs default to **allowlist**
**Advanced (full control):**
- Exposes every step (mode, workspace, gateway, channels, daemon, skills)
**Note**: `--json` does not imply non-interactive mode. For scripts, use `--non-interactive`.
Onboarding includes a **web search** step where you can pick a provider (Brave, DuckDuckGo, Exa, Firecrawl, Gemini, Grok, Kimi, MiniMax Search, Ollama Web Search, Perplexity, SearXNG, or Tavily). Configure later with `openclaw configure --section web`.
### What Onboarding Configures (Local Mode)
1. **Model/Auth** — choose any supported provider/auth flow (API key, OAuth, or provider-specific manual auth), including Custom Provider (OpenAI-compatible, Anthropic-compatible, or Unknown auto-detect). Security note: if this agent will run tools or process webhook/hooks content, prefer the strongest latest-generation model and keep tool policy strict. Weaker/older tiers are easier to prompt-inject. For non-interactive runs, `--secret-input-mode ref` stores env-backed refs instead of plaintext API key values. For Anthropic, interactive onboarding offers **Anthropic Claude CLI** as the preferred local path.
2. **Workspace** — Location for agent files (default `~/.openclaw/workspace`). Seeds bootstrap files.
3. **Gateway** — Port, bind address, auth mode, Tailscale exposure. In interactive token mode, choose default plaintext token storage or opt into SecretRef. Non-interactive: `--gateway-token-ref-env <ENV_VAR>`.
4. **Channels** — built-in and bundled chat channels: BlueBubbles, Discord, Feishu, Google Chat, Mattermost, Microsoft Teams, QQ Bot, Signal, Slack, Telegram, WhatsApp, and more
5. **Daemon** — installs LaunchAgent (macOS), systemd user unit (Linux/WSL2), or native Windows Scheduled Task with per-user Startup-folder fallback. If token auth is SecretRef-managed, daemon validates it but does not persist the resolved token. If token SecretRef is unresolved, daemon install is blocked with actionable guidance. If both `gateway.auth.token` and `gateway.auth.password` are configured and `gateway.auth.mode` is unset, daemon install is blocked until mode is set.
6. **Health check** — starts the Gateway and verifies it's running
7. **Skills** — installs recommended skills and optional dependencies
Re-running onboarding does **NOT** wipe anything unless you explicitly choose **Reset** (or pass `--reset`). CLI `--reset` defaults to config, credentials, and sessions; use `--reset-scope full` to include workspace. If the config is invalid or contains legacy keys, onboarding asks you to run `openclaw doctor` first.
**Remote mode** only configures the local client to connect to a Gateway elsewhere. It does **not** install or change anything on the remote host.
### Non-Interactive Mode
```bash
openclaw onboard --non-interactive \
--mode local \
--auth-choice apiKey \
--anthropic-api-key "$ANTHROPIC_API_KEY" \
--gateway-port 18789 \
--gateway-bind loopback \
--install-daemon \
--daemon-runtime node \
--skip-skills
```
Add `--json` for machine-readable summary.
**Gateway token SecretRef in non-interactive mode:**
```bash
export OPENCLAW_GATEWAY_TOKEN="your-token"
openclaw onboard --non-interactive \
--mode local \
--auth-choice skip \
--gateway-auth token \
--gateway-token-ref-env OPENCLAW_GATEWAY_TOKEN
```
### Add Another Agent
```bash
openclaw agents add work \
--workspace ~/.openclaw/workspace-work \
--model openai/gpt-5.4 \
--bind whatsapp:biz \
--non-interactive \
--json
```
### What the Wizard Writes
Typical fields in `~/.openclaw/openclaw.json`:
- `agents.defaults.workspace`
- `agents.defaults.model` / `models.providers`
- `tools.profile`
- `gateway.*` (mode, bind, auth, tailscale)
- `session.dmScope`
- Channel tokens (`channels.telegram.botToken`, etc.)
- `wizard.lastRunAt`, `wizard.lastRunVersion`, etc.
### Onboarding Reference Details
**Existing config detection:**
- If `~/.openclaw/openclaw.json` exists, choose **Keep / Modify / Reset**
- `--reset` defaults to `config+creds+sessions`; use `--reset-scope full` to also remove workspace
- Reset uses `trash` (never `rm`) and offers scopes: Config only | Config + credentials + sessions | Full reset
**Auth profiles** live at `~/.openclaw/agents/<agentId>/agent/auth-profiles.json`
**Non-interactive secret reference:**
- `--secret-input-mode ref` stores env-backed refs instead of plaintext API key values
- For Anthropic, interactive onboarding offers **Anthropic Claude CLI** as the preferred local path
**Daemon install notes:**
- If token auth requires a token and `gateway.auth.token` is SecretRef-managed, daemon install validates it but does not persist the resolved token
- If both `gateway.auth.token` and `gateway.auth.password` are configured and `gateway.auth.mode` is unset, daemon install is blocked until mode is set explicitly
- Native Windows: Scheduled Task first, with a per-user Startup-folder login item fallback if task creation is denied
**Signal setup (signal-cli):**
- Onboarding can install `signal-cli` from GitHub releases
- JVM builds require **Java 21**; native builds are used when available
- Windows uses WSL2 for the Linux flow
---
## VPS / Linux Server Deployment
### Provider Options
- **Railway**: one-click, browser setup
- **Northflank**: one-click, browser setup
- **DigitalOcean**: simple paid VPS
- **Oracle Cloud**: always Free ARM tier
- **Fly.io**: Fly Machines (see below)
- **Hetzner**: Docker on Hetzner VPS (see below)
- **GCP**: Compute Engine
- **Azure**: Linux VM
- **Raspberry Pi**: ARM self-hosted
- **Render**: managed cloud hosting
- **Kubernetes**: K8s deployment
- **Podman**: rootless container alternative to Docker
- **Ansible**: automated fleet provisioning
### How Cloud Setups Work
- The **Gateway runs on the VPS** and owns state + workspace
- You connect from your laptop or phone via the **Control UI** or **Tailscale/SSH**
- Treat the VPS as the source of truth and **back up** the state + workspace regularly
- Secure default: keep the Gateway on loopback and access it via SSH tunnel or Tailscale Serve
### Startup Tuning for Small VMs and ARM Hosts
```bash
grep -q 'NODE_COMPILE_CACHE=/var/tmp/openclaw-compile-cache' ~/.bashrc || cat >> ~/.bashrc <<'EOF'
export NODE_COMPILE_CACHE=/var/tmp/openclaw-compile-cache
mkdir -p /var/tmp/openclaw-compile-cache
export OPENCLAW_NO_RESPAWN=1
EOF
source ~/.bashrc
```
**systemd tuning:**
```bash
systemctl --user edit openclaw-gateway.service
```
```ini
[Service]
Environment=OPENCLAW_NO_RESPAWN=1
Environment=NODE_COMPILE_CACHE=/var/tmp/openclaw-compile-cache
Restart=always
RestartSec=2
TimeoutStartSec=90
```
### Hetzner (Docker, Production Guide)
**Goal**: Run a persistent OpenClaw Gateway on a Hetzner VPS using Docker.
**Quick path:**
1. Provision Hetzner VPS
2. Install Docker: `apt-get update && apt-get install -y git curl ca-certificates && curl -fsSL https://get.docker.com | sh`
3. Clone repo: `git clone https://github.com/openclaw/openclaw.git && cd openclaw`
4. Create persistent host directories: `mkdir -p /root/.openclaw/workspace && chown -R 1000:1000 /root/.openclaw`
5. Configure `.env` and `docker-compose.yml`
6. `docker compose up -d`
**Environment variables (`.env`):**
```bash
OPENCLAW_IMAGE=openclaw:latest
OPENCLAW_GATEWAY_TOKEN=
OPENCLAW_GATEWAY_BIND=lan
OPENCLAW_GATEWAY_PORT=18789
OPENCLAW_CONFIG_DIR=/root/.openclaw
OPENCLAW_WORKSPACE_DIR=/root/.openclaw/workspace
GOG_KEYRING_PASSWORD=
XDG_CONFIG_HOME=/home/node/.openclaw
```
**docker-compose.yml snippet:**
```yaml
services:
openclaw-gateway:
image: OPENCLAW_IMAGE
build: .
restart: unless-stopped
volumes:
- OPENCLAW_CONFIG_DIR:/home/node/.openclaw
- OPENCLAW_WORKSPACE_DIR:/home/node/.openclaw/workspace
ports:
- "127.0.0.1:OPENCLAW_GATEWAY_PORT:18789"
command: ["node", "dist/index.js", "gateway", "--bind", "OPENCLAW_GATEWAY_BIND", "--port", "OPENCLAW_GATEWAY_PORT", "--allow-unconfigured"]
```
**Access (SSH tunnel from laptop):**
```bash
ssh -N -L 18789:127.0.0.1:18789 root@YOUR_VPS_IP
# Then open http://127.0.0.1:18789/
```
**Terraform community resource:**
- Infrastructure: [openclaw-terraform-hetzner](https://github.com/andreesg/openclaw-terraform-hetzner)
- Docker config: [openclaw-docker-config](https://github.com/andreesg/openclaw-docker-config)
### Fly.io Deployment
**Prerequisites:**
- `flyctl` CLI installed
- Fly.io account
- Model auth API key
- Channel credentials
**Quick path:**
```bash
git clone https://github.com/openclaw/openclaw.git
cd openclaw
fly apps create my-openclaw
fly volumes create openclaw_data --size 1 --region iad
```
**fly.toml (key settings):**
```toml
app = "my-openclaw"
primary_region = "iad"
[build]
dockerfile = "Dockerfile"
[env]
NODE_ENV = "production"
OPENCLAW_STATE_DIR = "/data"
NODE_OPTIONS = "--max-old-space-size=1536"
[processes]
app = "node dist/index.js gateway --allow-unconfigured --port 3000 --bind lan"
[http_service]
internal_port = 3000
force_https = true
auto_stop_machines = false
min_machines_running = 1
[[vm]]
size = "shared-cpu-2x"
memory = "2048mb"
[mounts]
source = "openclaw_data"
destination = "/data"
```
**Set secrets:**
```bash
fly secrets set OPENCLAW_GATEWAY_TOKEN=$(openssl rand -hex 32)
fly secrets set ANTHROPIC_API_KEY=sk-ant-...
fly secrets set DISCORD_BOT_TOKEN=MTQ...
```
**Deploy:**
```bash
fly deploy
fly status
fly logs
```
**Fly.io Troubleshooting:**
- "App is not listening on expected address": add `--bind lan` to process command
- Health checks failing: ensure `internal_port` matches gateway port
- OOM issues: increase memory to `2048mb`
- Gateway lock issues: `fly ssh console --command "rm -f /data/gateway.*.lock"` then restart
- Config not being read: verify `/data/openclaw.json` exists with `gateway.mode="local"`
**Cost**: ~$10-15/month with recommended config (shared-cpu-2x, 2GB RAM)
**Private Deployment (Hardened):**
```bash
# Release public IPs
fly ips release <public-ipv4> -a my-openclaw
fly ips release <public-ipv6> -a my-openclaw
# Deploy with private config
fly deploy -c fly.private.toml
# Access via proxy
fly proxy 3000:3000 -a my-openclaw
```
---
## Migration Guide
### What Gets Migrated
When you copy the **state directory** (`~/.openclaw/` by default) and your **workspace**, you preserve:
- **Config** — `openclaw.json` and all gateway settings
- **Auth** — per-agent `auth-profiles.json` (API keys + OAuth), plus credentials
- **Sessions** — conversation history and agent state
- **Channel state** — WhatsApp login, Telegram session, etc.
- **Workspace files** — `MEMORY.md`, `USER.md`, skills, and prompts
### Migration Steps
1. **Stop and back up**: `openclaw gateway stop && cd ~ && tar -czf openclaw-state.tgz .openclaw`
2. **Install on new machine**: `curl -fsSL https://openclaw.ai/install.sh | bash`
3. **Copy and extract**: `cd ~ && tar -xzf openclaw-state.tgz`
4. **Run doctor**: `openclaw doctor && openclaw gateway restart && openclaw status`
### Common Pitfalls
- **Profile/state-dir mismatch**: use the same `--profile` or `OPENCLAW_STATE_DIR` you migrated
- **Copying only `openclaw.json`**: Always migrate the entire state directory — model auth profiles are in `agents/<agentId>/agent/auth-profiles.json`
- **Permissions**: ensure state directory and workspace are owned by the user running the gateway
- **Remote mode**: migrate the gateway host itself, not your local laptop
- **Secrets in backups**: store backups encrypted
### Verification Checklist
- `openclaw status` shows the gateway running
- Channels are still connected (no re-pairing needed)
- Dashboard opens and shows existing sessions
- Workspace files (memory, configs) are present
- To return to latest after rollback: `git checkout main && git pull`
---
## Platform Notes
### macOS
**Install**: `curl -fsSL https://openclaw.ai/install.sh | bash`
The macOS **menu-bar app** owns permissions, manages/attaches to the Gateway locally (launchd), and exposes macOS capabilities to the agent as a node.
**What it does:**
- Shows native notifications and status in the menu bar
- Owns TCC prompts (Notifications, Accessibility, Screen Recording, Microphone, Speech Recognition, Automation/AppleScript)
- Runs or connects to the Gateway (local or remote)
- Exposes macOS-only tools (Canvas, Camera, Screen Recording, `system.run`)
- Optionally hosts **PeekabooBridge** for UI automation
- Installs the global CLI (`openclaw`) on request via npm, pnpm, or bun (app prefers npm → pnpm → bun; Node remains the recommended Gateway runtime)
**Local vs Remote mode:**
- **Local**: app attaches to a running local Gateway or enables the launchd service via `openclaw gateway install`. Does NOT spawn the Gateway as a child process.
- **Remote**: app connects to a Gateway over SSH/Tailscale and starts the local **node host service** (so the remote Gateway can reach this Mac). Gateway discovery prefers Tailscale MagicDNS names over raw tailnet IPs for more reliable recovery when IPs change.
**Launchd control:**
```bash
launchctl kickstart -k gui/$UID/ai.openclaw.gateway
launchctl bootout gui/$UID/ai.openclaw.gateway
```
**State dir placement**: Avoid iCloud or other cloud-synced folders. Use `OPENCLAW_STATE_DIR=~/.openclaw`.
**Exec approvals (system.run)**: Controlled by Settings → Exec approvals. Stored at `~/.openclaw/exec-approvals.json`. `system.run` environment overrides are filtered (drops `PATH`, `DYLD_*`, `LD_*`, `NODE_OPTIONS`, `PYTHON*`, `PERL*`, `RUBYOPT`, `SHELLOPTS`, `PS4`). For shell wrappers (`bash|sh|zsh ... -c/-lc`), environment overrides are reduced to a small allowlist (`TERM`, `LANG`, `LC_*`, `COLORTERM`, `NO_COLOR`, `FORCE_COLOR`). For allow-always in allowlist mode, known dispatch wrappers (`env`, `nice`, `nohup`, `stdbuf`, `timeout`) persist inner executable paths instead of wrapper paths.
**Deep links** (`openclaw://agent`):
```bash
open 'openclaw://agent?message=Hello%20from%20deep%20link'
```
### Windows
OpenClaw supports both **native Windows** and **WSL2**. WSL2 is the more stable path and recommended for the full experience.
**Native Windows status**: Core CLI and Gateway work natively. `openclaw onboard --non-interactive --install-daemon` tries Windows Scheduled Tasks first; if denied, falls back to a per-user Startup-folder login item. If `schtasks` itself hangs, OpenClaw aborts that path quickly and falls back instead of hanging forever. WSL2 is still recommended for the full experience.
**WSL2 Setup:**
1. `wsl --install` (or `wsl --install -d Ubuntu-24.04`)
2. Enable systemd: add `[boot]` `systemd=true` to `/etc/wsl.conf`, then `wsl --shutdown`
3. Inside WSL: `curl -fsSL https://openclaw.ai/install.sh | bash`
**Gateway auto-start before Windows login:**
```bash
# Inside WSL
sudo loginctl enable-linger "$(whoami)"
openclaw gateway install
# In PowerShell as Administrator
schtasks /create /tn "WSL Boot" /tr "wsl.exe -d Ubuntu --exec /bin/true" /sc onstart /ru SYSTEM
```
**Expose WSL services over LAN (portproxy):**
```powershell
$Distro = "Ubuntu-24.04"
$ListenPort = 2222
$TargetPort = 22
$WslIp = (wsl -d $Distro -- hostname -I).Trim().Split(" ")[0]
netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=$ListenPort connectaddress=$WslIp connectport=$TargetPort
```
### Linux
**Node is the recommended runtime.** Bun is supported for the global CLI install path. For the Gateway runtime, Node remains the recommended daemon runtime.
**Beginner quick path (VPS):**
1. `npm i -g openclaw@latest`
2. `openclaw onboard --install-daemon`
3. From laptop: `ssh -N -L 18789:127.0.0.1:18789 <user>@<host>`
4. Open `http://127.0.0.1:18789/` and authenticate
**Gateway service (systemd user unit):**
```ini
[Unit]
Description=OpenClaw Gateway
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=/usr/local/bin/openclaw gateway --port 18789
Restart=always
RestartSec=5
TimeoutStopSec=30
TimeoutStartSec=30
SuccessExitStatus=0 143
KillMode=control-group
[Install]
WantedBy=default.target
```
```bash
systemctl --user enable --now openclaw-gateway.service
```
**Linux OOM protection**: For eligible child process spawns, OpenClaw uses a `/bin/sh` wrapper that raises the child's `oom_score_adj` to `1000`. Skip with `OPENCLAW_CHILD_OOM_SCORE_ADJ=0`.
### iOS
- Status: **internal preview** (not publicly distributed yet)
- Connects to a Gateway over WebSocket (LAN or tailnet)
- Exposes node capabilities: Canvas, Screen snapshot, Camera capture, Location, Talk mode, Voice wake
**Quick start:**
1. Start the Gateway: `openclaw gateway --port 18789`
2. Open iOS app → Settings → pick discovered gateway (or manual host/port)
3. Approve pairing: `openclaw devices list && openclaw devices approve <requestId>`
4. Verify: `openclaw nodes status`
**Relay-backed push**: Official builds use an external push relay instead of publishing raw APNs token to the gateway. Set `gateway.push.apns.relay.baseUrl` in config.
**Common errors:**
- `NODE_BACKGROUND_UNAVAILABLE`: bring iOS app to foreground
- `A2UI_HOST_NOT_CONFIGURED`: check `canvasHost` in gateway configuration
- Pairing prompt never appears: run `openclaw devices list` and approve manually
### Android
- **Not publicly released yet**; source available in repo under `apps/android`
- Build: `./gradlew :app:assemblePlayDebug` (requires Java 17 and Android SDK)
- Role: companion node app (Android does not host the Gateway)
**Connection:**
- Android connects directly to the Gateway WebSocket and uses device pairing (`role: node`)
- The app keeps its gateway connection alive via a **foreground service** (persistent notification)
- For Tailscale/public hosts: requires secure endpoint (`wss://` or Tailscale Serve) — a plain `gateway.bind: "tailnet"` setup is not enough for first-time remote Android pairing unless TLS is terminated separately
- Cleartext `ws://` supported on private LAN addresses, `.local` hosts, `localhost`, `127.0.0.1`, and the Android emulator bridge (`10.0.2.2`)
- After first successful pairing, Android auto-reconnects on launch (manual endpoint if enabled, otherwise last discovered gateway)
- Optional node auto-approval for controlled subnets: `gateway.nodes.pairing.autoApproveCidrs: ["192.168.1.0/24"]` (disabled by default)
**Android notification forwarding config:**
```json5
{
notifications: {
allowPackages: ["com.slack", "com.whatsapp"],
denyPackages: ["com.android.systemui"],
quietHours: {
start: "22:00",
end: "07:00",
},
rateLimit: 5, // requests per minute
},
}
```
**Android command families** (availability depends on device + permissions):
- `device.status`, `device.info`, `device.permissions`, `device.health`
- `notifications.list`, `notifications.actions`
- `photos.latest`
- `contacts.search`, `contacts.add`
- `calendar.events`, `calendar.add`
- `callLog.search`
- `sms.search`
- `motion.activity`, `motion.pedometer`
---
## Control UI (Custom Build)
If you maintain a localized or customized dashboard build, point `gateway.controlUi.root` to a directory that contains your built static assets and `index.html`:
```json5
{
gateway: {
controlUi: {
enabled: true,
root: "/path/to/your/custom-dashboard/dist",
},
},
}
```
This allows you to serve a custom Control UI from the Gateway without modifying the OpenClaw package itself.
FILE:references/10-security-and-misc.md
# OpenClaw Security, Help, Nodes & Diagnostics Reference
## Table of Contents
- [Security](#security)
- [FAQ](#faq)
- [Troubleshooting](#troubleshooting)
- [Debugging](#debugging)
- [Environment Variables](#environment-variables)
- [Scripts Reference](#scripts-reference)
- [Nodes](#nodes)
- [Diagnostics Export](#diagnostics-export)
- [Diagnostics Flags](#diagnostics-flags)
- [CI Pipeline](#ci-pipeline)
- [RPC Adapters](#rpc-adapters)
---
## Security
### Security Model
**Personal assistant trust model**: This guidance assumes one trusted operator boundary per gateway (single-user, personal-assistant model). OpenClaw is **NOT** a hostile multi-tenant security boundary for multiple adversarial users sharing one agent or gateway.
- Supported: one user/trust boundary per gateway (prefer one OS user/host/VPS per boundary)
- Not supported: one shared gateway/agent used by mutually untrusted or adversarial users
- If adversarial-user isolation is required: split by trust boundary (separate gateway + credentials, separate OS users/hosts)
### Quick Check
```bash
openclaw security audit
openclaw security audit --deep
openclaw security audit --fix
openclaw security audit --json
```
See also: [Formal Verification (Security Models)](/security/formal-verification).
`security audit --fix` flips common open group policies to allowlists, restores `logging.redactSensitive: "tools"`, tightens state/config/include-file permissions, and uses Windows ACL resets instead of POSIX `chmod` when running on Windows.
### Deployment and Host Trust
- If someone can modify Gateway host state/config (`~/.openclaw`), treat them as a trusted operator
- Running one Gateway for multiple mutually untrusted/adversarial operators is **not recommended**
- For mixed-trust teams: split trust boundaries with separate gateways (or at minimum separate OS users/hosts)
- Inside one Gateway instance, authenticated operator access is a trusted control-plane role, not a per-user tenant role
- Session identifiers (`sessionKey`, session IDs, labels) are **routing selectors, not authorization tokens**
- If several people can message one tool-enabled agent, each can steer that same permission set
### Shared Slack Workspace: Real Risk
If "everyone in Slack can message the bot", risks include:
- Any allowed sender can induce tool calls (`exec`, browser, network/file tools) within the agent's policy
- Prompt/content injection from one sender can cause actions affecting shared state
- Any allowed sender can potentially drive exfiltration via tool usage
Use separate agents/gateways with minimal tools for team workflows.
### Company-Shared Agent: Acceptable Pattern
This is acceptable when everyone using that agent is in the same trust boundary (for example, one company team) and the agent is strictly business-scoped:
- Run it on a dedicated machine/VM/container
- Use a dedicated OS user + dedicated browser/profile/accounts for that runtime
- Do not sign that runtime into personal Apple/Google accounts or personal password-manager/browser profiles
If you mix personal and company identities on the same runtime, you collapse the separation and increase personal-data exposure risk.
### Gateway and Node Trust Concept
Treat Gateway and node as one operator trust domain, with different roles:
- **Gateway** is the control plane and policy surface (`gateway.auth`, tool policy, routing)
- **Node** is the remote execution surface paired to that Gateway (commands, device actions, host-local capabilities)
- A caller authenticated to the Gateway is trusted at Gateway scope; after pairing, node actions are trusted operator actions on that node
- `sessionKey` is routing/context selection, not per-user auth
- Exec approvals (allowlist + ask) are operator-intent guardrails, not hostile multi-tenant isolation
- OpenClaw's product default for trusted single-operator setups is that host exec on `gateway`/`node` is allowed without approval prompts (`security="full"`, `ask="off"` unless you tighten it) — that default is intentional UX, not a vulnerability
- Exec approvals bind exact request context and best-effort direct local file operands; they do not semantically model every runtime/interpreter loader path — use sandboxing and host isolation for strong boundaries
If you need hostile-user isolation, split trust boundaries by OS user/host and run separate gateways.
### Trust Boundary Matrix
| Boundary or control | What it means | Common misread |
|---|---|---|
| `gateway.auth` (token/password/trusted-proxy/device auth) | Authenticates callers to gateway APIs | "Needs per-message signatures on every frame" |
| `sessionKey` | Routing key for context/session selection | "Session key is a user auth boundary" |
| Prompt/content guardrails | Reduce model abuse risk | "Prompt injection alone proves auth bypass" |
| `canvas.eval` / browser evaluate | Intentional operator capability when enabled | "Any JS eval primitive is automatically a vuln" |
| Local TUI `!` shell | Explicit operator-triggered local execution | "Local shell convenience command is remote injection" |
| Node pairing and node commands | Operator-level remote execution on paired devices | "Remote device control should be untrusted by default" |
### NOT Vulnerabilities by Design
These patterns are commonly reported but are closed as no-action:
- Prompt-injection-only chains without a policy, auth, or sandbox bypass
- Claims that assume hostile multi-tenant operation on one shared host
- Claims that classify normal operator read-path access (e.g. `sessions.list` / `sessions.preview` / `chat.history`) as IDOR in a shared-gateway setup
- Localhost-only deployment findings (e.g., HSTS on a loopback-only gateway)
- Discord inbound webhook signature findings for inbound paths that do not exist in this repo
- Reports that treat node pairing metadata as a hidden second per-command approval layer for `system.run`, when the real execution boundary is still the gateway's global node command policy plus the node's own exec approvals
- Reports that treat configured `gateway.nodes.pairing.autoApproveCidrs` as a vulnerability by itself — disabled by default, requires explicit CIDR/IP entries, only applies to first-time `role: node` pairing with no requested scopes, does not auto-approve operator/browser/Control UI/WebChat/role upgrades/scope upgrades/metadata changes/public-key changes/same-host loopback trusted-proxy paths
- "Missing per-user authorization" findings that treat `sessionKey` as an auth token
### Hardened Baseline (60 Seconds)
```json5
{
gateway: {
mode: "local",
bind: "loopback",
auth: { mode: "token", token: "replace-with-long-random-token" },
},
session: {
dmScope: "per-channel-peer",
},
tools: {
profile: "messaging",
deny: ["group:automation", "group:runtime", "group:fs", "sessions_spawn", "sessions_send"],
fs: { workspaceOnly: true },
exec: { security: "deny", ask: "always" },
elevated: { enabled: false },
},
channels: {
whatsapp: { dmPolicy: "pairing", groups: { "*": { requireMention: true } } },
},
}
```
### Shared Inbox Quick Rule
If more than one person can DM your bot:
- Set `session.dmScope: "per-channel-peer"` (or `"per-account-channel-peer"` for multi-account channels)
- Keep `dmPolicy: "pairing"` or strict allowlists
- Never combine shared DMs with broad tool access
### Context Visibility Model
- **Trigger authorization**: who can trigger the agent (`dmPolicy`, `groupPolicy`, allowlists, mention gates)
- **Context visibility**: what supplemental context is injected into model input
Settings:
- `contextVisibility: "all"` (default): keeps supplemental context as received
- `contextVisibility: "allowlist"`: filters supplemental context to senders allowed by active allowlist checks
- `contextVisibility: "allowlist_quote"`: like `allowlist`, but still keeps one explicit quoted reply
Advisory triage guidance: claims that only show "model can see quoted or historical text from non-allowlisted senders" are hardening findings addressable with `contextVisibility`, not auth or sandbox boundary bypasses by themselves. To be security-impacting, reports still need a demonstrated trust-boundary bypass (auth, policy, sandbox, approval, or another documented boundary).
### What the Security Audit Checks
- **Inbound access** (DM policies, group policies, allowlists): can strangers trigger the bot?
- **Tool blast radius** (elevated tools + open rooms): could prompt injection turn into shell/file/network actions?
- **Exec approval drift** (`security=full` — broad posture warning, not proof of a bug; `autoAllowSkills`, interpreter allowlists without `strictInlineEval`)
- **Network exposure** (Gateway bind/auth, Tailscale Serve/Funnel, weak/short auth tokens)
- **Browser control exposure** (remote nodes, relay ports, remote CDP endpoints)
- **Local disk hygiene** (permissions, symlinks, config includes, "synced folder" paths like iCloud/CloudStorage)
- **Plugins** (plugins load without an explicit allowlist)
- **Policy drift/misconfig** (sandbox docker settings configured but sandbox mode off; ineffective `gateway.nodes.denyCommands` patterns — matching is exact command-name only (e.g. `system.run`) and does not inspect shell text; dangerous `gateway.nodes.allowCommands` entries; global `tools.profile="minimal"` overridden by per-agent profiles; plugin-owned tools reachable under permissive tool policy)
- **Runtime expectation drift** (e.g. assuming implicit exec still means `sandbox` when `tools.exec.host` now defaults to `auto`, or explicitly setting `tools.exec.host="sandbox"` while sandbox mode is off)
- **Model hygiene** (warn when configured models look legacy)
With `--deep`, OpenClaw also attempts a best-effort live Gateway probe.
### Security Audit Checklist (Priority Order)
1. **Anything "open" + tools enabled**: lock down DMs/groups first, then tighten tool policy/sandboxing
2. **Public network exposure** (LAN bind, Funnel, missing auth): fix immediately
3. **Browser control remote exposure**: treat like operator access (tailnet-only, pair nodes deliberately)
4. **Permissions**: state/config/credentials/auth not group/world-readable
5. **Plugins**: only load what you explicitly trust
6. **Model choice**: prefer modern, instruction-hardened models for any bot with tools
### Security Audit Glossary
Each audit finding is keyed by a structured `checkId` (e.g., `gateway.bind_no_auth` or `tools.exec.security_full_configured`). Common severity classes:
- `fs.*` — filesystem permissions on state, config, credentials, auth profiles
- `gateway.*` — bind mode, auth, Tailscale, Control UI, trusted-proxy setup
- `hooks.*`, `browser.*`, `sandbox.*`, `tools.exec.*` — per-surface hardening
- `plugins.*`, `skills.*` — plugin/skill supply chain and scan findings
- `security.exposure.*` — cross-cutting checks where access policy meets tool blast radius
See the full catalog with severity levels, fix keys, and auto-fix support at the Security audit checks page.
### Credential Storage Map
| Credential | Location |
|---|---|
| WhatsApp | `~/.openclaw/credentials/whatsapp/<accountId>/creds.json` |
| Telegram bot token | config/env or `channels.telegram.tokenFile` |
| Discord bot token | config/env or SecretRef |
| Slack tokens | config/env (`channels.slack.*`) |
| Pairing allowlists (default account) | `~/.openclaw/credentials/<channel>-allowFrom.json` |
| Pairing allowlists (non-default accounts) | `~/.openclaw/credentials/<channel>-<accountId>-allowFrom.json` |
| Model auth profiles | `~/.openclaw/agents/<agentId>/agent/auth-profiles.json` |
| File-backed secrets payload (optional) | `~/.openclaw/secrets.json` |
| Legacy OAuth import | `~/.openclaw/credentials/oauth.json` |
### Control UI over HTTP
The Control UI needs a **secure context** (HTTPS or localhost).
`gateway.controlUi.allowInsecureAuth`:
- Allows Control UI auth without device identity when page is loaded over non-secure HTTP on localhost
- Does NOT bypass pairing checks
- Does NOT relax remote device identity requirements
`gateway.controlUi.dangerouslyDisableDeviceAuth`:
- Disables device identity checks entirely
- Severe security downgrade — use only for emergency debugging
Separate from those dangerous flags, successful `gateway.auth.mode: "trusted-proxy"` can admit **operator** Control UI sessions without device identity. That is an intentional auth-mode behavior, not an `allowInsecureAuth` shortcut, and it still does not extend to node-role Control UI sessions.
### Insecure/Dangerous Flags
`openclaw security audit` raises `config.insecure_or_dangerous_flags` when these are enabled:
- `gateway.controlUi.allowInsecureAuth=true`
- `gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback=true`
- `gateway.controlUi.dangerouslyDisableDeviceAuth=true`
- `hooks.gmail.allowUnsafeExternalContent=true`
- `hooks.mappings[<index>].allowUnsafeExternalContent=true`
- `tools.exec.applyPatch.workspaceOnly=false`
- `plugins.entries.acpx.config.permissionMode=approve-all`
All `dangerous*` / `dangerously*` keys in the config schema:
Control UI and browser:
- `gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback`
- `gateway.controlUi.dangerouslyDisableDeviceAuth`
- `browser.ssrfPolicy.dangerouslyAllowPrivateNetwork`
Channel name-matching (bundled and plugin channels; also available per `accounts.<accountId>` where applicable):
- `channels.discord.dangerouslyAllowNameMatching`
- `channels.slack.dangerouslyAllowNameMatching`
- `channels.googlechat.dangerouslyAllowNameMatching`
- `channels.msteams.dangerouslyAllowNameMatching`
- `channels.synology-chat.dangerouslyAllowNameMatching` (plugin channel)
- `channels.synology-chat.dangerouslyAllowInheritedWebhookPath` (plugin channel)
- `channels.zalouser.dangerouslyAllowNameMatching` (plugin channel)
- `channels.irc.dangerouslyAllowNameMatching` (plugin channel)
- `channels.mattermost.dangerouslyAllowNameMatching` (plugin channel)
Network exposure:
- `channels.telegram.network.dangerouslyAllowPrivateNetwork` (also per account)
Sandbox Docker (defaults + per-agent):
- `agents.defaults.sandbox.docker.dangerouslyAllowReservedContainerTargets`
- `agents.defaults.sandbox.docker.dangerouslyAllowExternalBindSources`
- `agents.defaults.sandbox.docker.dangerouslyAllowContainerNamespaceJoin`
### Reverse Proxy Configuration
If running the Gateway behind a reverse proxy (nginx, Caddy, Traefik), configure `gateway.trustedProxies` for proper forwarded-client IP handling.
When the Gateway detects proxy headers from an address **not** in `trustedProxies`, it will **not** treat connections as local clients. This prevents authentication bypass where proxied connections would appear to come from localhost.
`gateway.trustedProxies` also feeds `gateway.auth.mode: "trusted-proxy"`, but that auth mode is stricter: it **fails closed on loopback-source proxies** — same-host loopback reverse proxies do NOT satisfy trusted-proxy auth.
---
## FAQ
### First 60 Seconds if Something is Broken
1. **Quick status (first check)**: `openclaw status` — fast local summary
2. **Pasteable report (safe to share)**: `openclaw status --all`
3. **Daemon + port state**: `openclaw gateway status`
4. **Deep probes**: `openclaw status --deep` — runs live gateway health probe
5. **Tail the latest log**: `openclaw logs --follow`
6. **Run the doctor**: `openclaw doctor`
7. **Gateway snapshot**: `openclaw health --json` / `openclaw health --verbose`
```bash
openclaw status
openclaw status --all
openclaw gateway status
openclaw status --deep
openclaw logs --follow
openclaw doctor
openclaw health --json
openclaw health --verbose
```
### What is OpenClaw?
**In one paragraph:** OpenClaw is a personal AI assistant you run on your own devices. It replies on the messaging surfaces you already use (WhatsApp, Telegram, Slack, Mattermost, Discord, Google Chat, Signal, iMessage, WebChat) and can also do voice + a live Canvas on supported platforms. The Gateway is the always-on control plane.
**Value proposition:** OpenClaw is a local-first control plane that lets you run a capable assistant on your own hardware, reachable from the chat apps you already use, with stateful sessions, memory, and tools — without handing control of your workflows to a hosted SaaS. Run local models so all data can stay on your device, or use Anthropic/OpenAI/OpenRouter with per-agent routing and failover.
### Skills and Automation
**How to customize skills without keeping the repo dirty:**
Use managed overrides in `~/.openclaw/skills/<name>/SKILL.md` or add a folder via `skills.load.extraDirs` in `~/.openclaw/openclaw.json`. Precedence: `<workspace>/skills` → `<workspace>/.agents/skills` → `~/.agents/skills` → `~/.openclaw/skills` → bundled → `skills.load.extraDirs`.
**How to use different models for different tasks:**
- **Cron jobs**: isolated jobs can set a `model` override per job
- **Sub-agents**: route tasks to separate agents with different default models
- **On-demand switch**: use `/model` to switch the current session model
**The bot freezes while doing heavy work:**
Use **sub-agents** for long or parallel tasks. Set cheaper model for sub-agents via `agents.defaults.subagents.model`.
**How do Discord thread-bound subagent sessions work?**
Use thread bindings. Bind a Discord thread to a subagent or session target so follow-up messages stay on that bound session:
- Spawn with `sessions_spawn` using `thread: true` (and optionally `mode: "session"` for persistent follow-up)
- Or manually bind with `/focus <target>`; use `/agents` to inspect binding state
- Use `/session idle <duration|off>` and `/session max-age <duration|off>` to control auto-unfocus
- Use `/unfocus` to detach the thread
Required config: `session.threadBindings.enabled`, `session.threadBindings.idleHours`, `session.threadBindings.maxAgeHours`. Discord overrides: `channels.discord.threadBindings.enabled`, `channels.discord.threadBindings.idleHours`, `channels.discord.threadBindings.maxAgeHours`. Auto-bind on spawn: `channels.discord.threadBindings.spawnSubagentSessions: true`.
**A subagent finished, but the completion went to the wrong place or never posted:**
Check the resolved requester route first:
- Completion-mode subagent delivery prefers any bound thread or conversation route when one exists
- If the completion origin only carries a channel, OpenClaw falls back to the requester session's stored route (`lastChannel` / `lastTo` / `lastAccountId`)
- If neither a bound route nor a usable stored route exists, delivery can fail and result falls back to queued session delivery
- If the child's last visible assistant reply is exactly `NO_REPLY` / `no_reply` or `ANNOUNCE_SKIP`, OpenClaw intentionally suppresses the announce
Debug: `openclaw tasks show <runId-or-sessionKey>`
**Why did an isolated cron run switch models or retry once?**
That is usually the live model-switch path, not duplicate scheduling. Isolated cron can persist a runtime model handoff and retry when the active run throws `LiveSessionModelSwitchError`. The retry keeps the switched provider/model. After the initial attempt plus 2 switch retries, cron aborts instead of looping forever. Model selection order: Gmail hook override → per-job `model` → stored cron-session model override → normal agent/default selection.
**Can OpenClaw run macOS-only skills from Linux?**
Not directly (macOS skills are gated by `metadata.openclaw.os`). Three supported patterns:
1. **Run the Gateway on a Mac (simplest)**: connect from Linux in remote mode or over Tailscale
2. **Use a macOS node (no SSH)**: pair a macOS node (menubar app) and set Node Run Commands to "Always Ask" or "Always Allow"
3. **Proxy macOS binaries over SSH**: create SSH wrappers for required CLI binaries, override the skill metadata to allow Linux
**Cron or reminders do not fire:**
- Confirm cron is enabled (`cron.enabled`) and `OPENCLAW_SKIP_CRON` is not set
- Check the Gateway is running 24/7 (no sleep/restarts)
- Verify timezone settings for the job (`--tz` vs host timezone)
**Cron fired, but nothing was sent to the channel:**
- `--no-deliver` / `delivery.mode: "none"` means no runner fallback send
- Missing or invalid announce target means outbound was skipped
- A silent isolated result (`NO_REPLY` / `no_reply` only) is treated as intentionally non-deliverable
- Channel auth failures (`unauthorized`, `Forbidden`) mean delivery was tried but credentials blocked it
**Install skills on Linux:**
```bash
openclaw skills search "calendar"
openclaw skills install <skill-slug>
openclaw skills update --all
openclaw skills list --eligible
```
### Where Things Live on Disk
| Item | Location |
|---|---|
| Config | `~/.openclaw/openclaw.json` |
| State directory | `~/.openclaw/` |
| Agent dirs | `~/.openclaw/agents/<agentId>/` |
| Auth profiles | `~/.openclaw/agents/<agentId>/agent/auth-profiles.json` |
| Sessions | `~/.openclaw/agents/<agentId>/sessions/` |
| WhatsApp credentials | `~/.openclaw/credentials/whatsapp/<accountId>/creds.json` |
| Task records | `$OPENCLAW_STATE_DIR/tasks/runs.sqlite` |
| Cron jobs | `~/.openclaw/cron/jobs.json` |
| Cron state | `~/.openclaw/cron/jobs-state.json` |
| Media | `~/.openclaw/media/` |
| Logs | `/tmp/openclaw/openclaw-YYYY-MM-DD.log` |
---
## Troubleshooting
### First 60 Seconds Triage
Run this exact ladder in order:
```bash
openclaw status
openclaw status --all
openclaw gateway probe
openclaw gateway status
openclaw doctor
openclaw channels status --probe
openclaw logs --follow
```
**Good output indicators:**
- `openclaw status` → shows configured channels and no obvious auth errors
- `openclaw status --all` → full report, present and shareable
- `openclaw gateway probe` → `Reachable: yes`, `Capability: ...` line. Note: `Read probe: limited - missing scope: operator.read` is degraded diagnostics, not a connect failure. Use `--require-rpc` if you need read-scope RPC proof.
- `openclaw gateway status` → `Runtime: running`, `Connectivity probe: ok`, and a `Capability: ...` line
- `openclaw doctor` → no blocking config/service errors
- `openclaw channels status --probe` → live per-account transport state plus probe/audit results such as `works` or `audit ok`; if gateway is unreachable, falls back to config-only summaries
### Decision Tree
| Symptom | Section |
|---|---|
| No replies | No replies section |
| Dashboard / Control UI won't connect | Control UI section |
| Gateway won't start | Gateway section |
| Channel connects but messages don't flow | Channel flow section |
| Cron or heartbeat didn't fire | Automation section |
| Node is paired but tool fails | Node tools section |
| Browser tool fails | Browser section |
### No Replies
```bash
openclaw status
openclaw gateway status
openclaw channels status --probe
openclaw pairing list --channel <channel>
openclaw logs --follow
```
**Common log signatures:**
- `drop guild message (mention required` → mention gating blocked message
- `pairing request` → sender is unapproved and waiting for DM pairing approval
- `blocked` / `allowlist` in channel logs → sender, room, or group is filtered
### Dashboard / Control UI Won't Connect
**Common log signatures:**
- `device identity required` → non-secure context or missing device auth
- `origin not allowed` → browser `Origin` not in `gateway.controlUi.allowedOrigins`
- `AUTH_TOKEN_MISMATCH` with `canRetryWithDeviceToken=true` → one trusted device-token retry may occur automatically; cached-token retry reuses the cached scope set
- `too many failed authentication attempts (retry later)` → repeated failures from that `Origin` temporarily locked out; another localhost origin uses a separate bucket
- `gateway connect failed:` → UI targeting wrong URL/port
### Gateway Won't Start
**Common log signatures:**
- `Gateway start blocked: set gateway.mode=local` or `existing config is missing gateway.mode` → gateway mode is remote or missing, needs repair
- `refusing to bind gateway ... without auth` → non-loopback bind without valid auth
- `another gateway instance is already listening` or `EADDRINUSE` → port already taken
### Channel Connects but Messages Don't Flow
**Common log signatures:**
- `mention required` → group mention gating blocked processing
- `pairing` / `pending` → DM sender not approved yet
- `not_in_channel`, `missing_scope`, `Forbidden`, `401/403` → channel permission token issue
### Cron or Heartbeat Didn't Fire
```bash
openclaw cron status
openclaw cron list
openclaw cron runs --id <jobId> --limit 20
```
**Common log signatures:**
- `cron: scheduler disabled; jobs will not run automatically` → cron is disabled
- `heartbeat skipped` with `reason=quiet-hours` → outside configured active hours
- `heartbeat skipped` with `reason=empty-heartbeat-file` → HEARTBEAT.md only contains blank/header-only scaffolding
- `heartbeat skipped` with `reason=no-tasks-due` → task mode active but no tasks are due
- `requests-in-flight` → main lane busy; heartbeat wake was deferred
### Node Paired but Tool Fails
```bash
openclaw nodes status
openclaw nodes describe --node <idOrNameOrIp>
```
**Common log signatures:**
- `NODE_BACKGROUND_UNAVAILABLE` → bring node app to foreground
- `*_PERMISSION_REQUIRED` → OS permission was denied/missing
- `SYSTEM_RUN_DENIED: approval required` → exec approval is pending
- `SYSTEM_RUN_DENIED: allowlist miss` → command not on exec allowlist
### Exec Suddenly Asks for Approval
**What changed:**
- If `tools.exec.host` is unset, the default is `auto`
- `host=auto` resolves to `sandbox` when sandbox runtime is active, `gateway` otherwise
- The no-prompt behavior comes from `security=full` plus `ask=off` on gateway/node
**Restore default no-approval behavior:**
```bash
openclaw config set tools.exec.host gateway
openclaw config set tools.exec.security full
openclaw config set tools.exec.ask off
openclaw gateway restart
```
### Browser Tool Fails
```bash
openclaw browser status
openclaw doctor
```
**Common log signatures:**
- `unknown command "browser"` → `plugins.allow` is set and does not include `browser`
- `Failed to start Chrome CDP on port` → local browser launch failed
- `browser.executablePath not found` → configured binary path is wrong
- `No Chrome tabs found for profile="user"` → Chrome MCP attach profile has no open tabs
### Anthropic Long Context 429
If you see `HTTP 429: rate_limit_error: Extra usage is required for long context requests`, see the gateway troubleshooting doc at `/gateway/troubleshooting#anthropic-429-extra-usage-required-for-long-context`.
### Local OpenAI-Compatible Backend Fails in OpenClaw
If your local backend answers small direct probes but fails on OpenClaw agent turns:
1. If error mentions `messages[].content` expecting a string: set `models.providers.<provider>.models[].compat.requiresStringContent: true`
2. If backend fails only on OpenClaw agent turns: set `models.providers.<provider>.models[].compat.supportsTools: false`
3. Set `compat.requiresStringContent: true` for string-only Chat Completions backends
4. If tiny direct requests keep passing while OpenClaw agent turns crash: treat as upstream server/model limitation
Additional notes:
- `compat.requiresStringContent: true` handles backends that reject structured Chat Completions content parts
- `compat.supportsTools: false` handles models/backends that cannot handle OpenClaw's tool schema surface reliably
### Plugin Install Fails with Missing openclaw extensions
```json
{
"name": "@openclaw/my-plugin",
"version": "1.2.3",
"openclaw": {
"extensions": ["./dist/index.js"]
}
}
```
The plugin package must add `openclaw.extensions` to `package.json`.
---
## Debugging
### Runtime Debug Overrides (`/debug`)
`/debug` is disabled by default; enable with `commands.debug: true`.
```
/debug show
/debug set messages.responsePrefix="[openclaw]"
/debug unset messages.responsePrefix
/debug reset
```
`/debug reset` clears all overrides and returns to the on-disk config.
### Session Trace Output (`/trace`)
Use `/trace` when you want to see plugin-owned trace/debug lines in one session without turning on full verbose mode.
```text
/trace
/trace on
/trace off
```
### Dev Profile + Dev Gateway (`--dev`)
Two `--dev` flags:
- **Global `--dev` (profile)**: isolates state under `~/.openclaw-dev`, defaults gateway port to `19001`
- **`gateway --dev`**: auto-creates a default config + workspace when missing
**Note:** `--dev` is a global profile flag that gets "eaten by some runners" before they can pass it down. Use the env var form instead when needed:
```bash
OPENCLAW_PROFILE=dev openclaw gateway --dev --reset
```
```bash
pnpm gateway:dev
OPENCLAW_PROFILE=dev openclaw tui
```
**Dev bootstrap behavior (`gateway --dev`):**
- Writes a minimal config if missing
- Sets `agent.workspace` to the dev workspace
- Seeds workspace files if missing
- Default identity: **C3‑PO** (protocol droid)
- Skips channel providers (`OPENCLAW_SKIP_CHANNELS=1`)
**Reset:**
```bash
pnpm gateway:dev:reset
# Or:
OPENCLAW_PROFILE=dev openclaw gateway --dev --reset
```
### Raw Stream Logging
Enable raw assistant stream logging:
```bash
pnpm gateway:watch --raw-stream
pnpm gateway:watch --raw-stream --raw-stream-path ~/.openclaw/logs/raw-stream.jsonl
```
Or via env vars:
```bash
OPENCLAW_RAW_STREAM=1
OPENCLAW_RAW_STREAM_PATH=~/.openclaw/logs/raw-stream.jsonl
```
Default file: `~/.openclaw/logs/raw-stream.jsonl`
**Safety notes**: Raw stream logs can include full prompts, tool output, and user data. Keep logs local and delete after debugging.
### Raw Chunk Logging (pi-mono)
For capturing raw OpenAI-compatible chunks before they are parsed into blocks (pi-mono specific):
```bash
PI_RAW_STREAM=1 openclaw gateway run
PI_RAW_STREAM=1 PI_RAW_STREAM_PATH=~/.openclaw/logs/pi-raw.jsonl openclaw gateway run
```
- `PI_RAW_STREAM=1`: enable raw chunk capture
- `PI_RAW_STREAM_PATH`: custom output path for raw chunks (JSONL format)
This captures chunks at a lower level than `OPENCLAW_RAW_STREAM`, before the pi-mono parser converts them into blocks.
### Gateway Watch Mode
```bash
pnpm gateway:watch
```
This maps to:
```bash
node scripts/watch-node.mjs gateway --force
```
The watcher restarts on build-relevant files under `src/`, extension source files, `tsconfig.json`, `package.json`, and `tsdown.config.ts`.
**Notes:**
- Re-running the same watch command for the same repo/flag set now **replaces** the older watcher instead of leaving duplicate watcher parents behind
- Extension metadata changes restart the gateway without forcing a tsdown rebuild
- Extension metadata changes restart the gateway without forcing a tsdown rebuild
### Temporary CLI Debug Timing
OpenClaw keeps `src/cli/debug-timing.ts` for local investigation. Enable with:
```bash
OPENCLAW_DEBUG_TIMING=1 pnpm openclaw models list --all --provider moonshot
```
Or JSON output:
```bash
OPENCLAW_DEBUG_TIMING=json pnpm openclaw models list --all --provider moonshot \
2> .artifacts/models-list-timing.jsonl
```
**Clean up before landing:**
```bash
rg 'createCliDebugTiming|debug:[a-z0-9_-]+:' src/commands src/cli \
--glob '!src/cli/debug-timing.*' \
--glob '!*.test.ts'
```
---
## Environment Variables
### Precedence (Highest → Lowest)
1. **Process environment** (what the Gateway process already has)
2. **`.env` in the current working directory** (does not override)
3. **Global `.env`** at `~/.openclaw/.env` (does not override)
4. **Config `env` block** in `~/.openclaw/openclaw.json` (applied only if missing)
5. **Optional login-shell import** (`env.shellEnv.enabled` or `OPENCLAW_LOAD_SHELL_ENV=1`)
**Ubuntu compatibility fallback:** On Ubuntu fresh installs using the default state dir, OpenClaw also treats `~/.config/openclaw/gateway.env` as a compatibility fallback after the global `.env`. If both files exist and disagree, OpenClaw keeps `~/.openclaw/.env` and prints a warning.
**Ubuntu compatibility fallback:** On Ubuntu fresh installs that use the default state dir, OpenClaw also treats `~/.config/openclaw/gateway.env` as a compatibility fallback after the global `.env`. If both files exist and disagree, OpenClaw keeps `~/.openclaw/.env` and prints a warning.
### Config `env` Block
```json5
{
env: {
OPENROUTER_API_KEY: "sk-or-...",
vars: {
GROQ_API_KEY: "gsk-...",
},
},
}
```
### Shell Env Import
```json5
{
env: {
shellEnv: {
enabled: true,
timeoutMs: 15000,
},
},
}
```
Env var equivalents:
- `OPENCLAW_LOAD_SHELL_ENV=1`
- `OPENCLAW_SHELL_ENV_TIMEOUT_MS=15000`
### Runtime-Injected Env Vars
- `OPENCLAW_SHELL=exec`: set for commands run through the `exec` tool
- `OPENCLAW_SHELL=acp`: set for ACP runtime backend process spawns
- `OPENCLAW_SHELL=acp-client`: set for `openclaw acp client` when it spawns the ACP bridge process
- `OPENCLAW_SHELL=tui-local`: set for local TUI `!` shell commands
### UI Env Vars
- `OPENCLAW_THEME=light`: force the light TUI palette
- `OPENCLAW_THEME=dark`: force the dark TUI palette
- `COLORFGBG`: auto-pick TUI palette from background color hint
### Env Var Substitution in Config
```json5
{
models: {
providers: {
"vercel-gateway": {
apiKey: "VERCEL_GATEWAY_API_KEY",
},
},
},
}
```
### Path-Related Env Vars
| Variable | Purpose |
|---|---|
| `OPENCLAW_HOME` | Override the home directory used for all internal path resolution |
| `OPENCLAW_STATE_DIR` | Override the state directory (default `~/.openclaw`) |
| `OPENCLAW_CONFIG_PATH` | Override the config file path (default `~/.openclaw/openclaw.json`) |
| `OPENCLAW_ALLOW_INSECURE_PRIVATE_WS` | Client-side break-glass override: allows plaintext `ws://` to trusted private-network IPs (no config equivalent) |
| `OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1` | Client-side break-glass override that allows plaintext `ws://` to trusted private-network IPs (no `openclaw.json` equivalent) |
### Logging
| Variable | Purpose |
|---|---|
| `OPENCLAW_LOG_LEVEL` | Override log level for both file and console (e.g. `debug`, `trace`) |
### APNs Environment Variables (iOS Push Relay)
For configuring Apple Push Notification Service (APNs) for local/manual iOS builds (without the hosted relay):
| Variable | Purpose |
|---|---|
| `OPENCLAW_APNS_RELAY_BASE_URL` | Override relay base URL (temporary env override) |
| `OPENCLAW_APNS_RELAY_TIMEOUT_MS` | Override relay timeout in milliseconds |
| `OPENCLAW_APNS_RELAY_ALLOW_HTTP` | Allow HTTP relay URL (loopback-only development escape hatch) |
| `OPENCLAW_APNS_TEAM_ID` | Apple Developer Team ID for direct APNs |
| `OPENCLAW_APNS_KEY_ID` | APNs Key ID |
| `OPENCLAW_APNS_PRIVATE_KEY_P8` | APNs private key content (inline p8 content) |
| `OPENCLAW_APNS_PRIVATE_KEY_PATH` | Path to APNs private key `.p8` file (preferred over inline key) |
### OPENCLAW_CHILD_OOM_SCORE_ADJ
On Linux, OpenClaw biases child processes (supervisor children, PTY shells, MCP stdio servers, Chrome processes) to be killed before the Gateway under OOM pressure. The child's `oom_score_adj` is raised to `1000` via a short `/bin/sh` wrapper before exec.
To disable this behavior for specific child processes, set in the child env:
```bash
OPENCLAW_CHILD_OOM_SCORE_ADJ=0 # or: false, no, off
```
This env var is only checked in the child wrapper; the Gateway itself keeps its normal OOM score.
### nvm Users: web_fetch TLS Failures
If Node.js was installed via **nvm**, the built-in `fetch()` may be missing modern root CAs, causing `web_fetch` to fail with `"fetch failed"` on most HTTPS sites.
On Linux, OpenClaw automatically detects nvm and applies the fix:
- `openclaw gateway install` writes `NODE_EXTRA_CA_CERTS` into the systemd service environment
- The `openclaw` CLI entrypoint re-execs itself with `NODE_EXTRA_CA_CERTS` set before Node startup
**Manual fix:**
```bash
export NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt
openclaw gateway run
```
---
## Scripts Reference
The `scripts/` directory contains helper scripts for local workflows and ops tasks.
**Conventions:**
- Scripts are **optional** unless referenced in docs or release checklists
- Prefer CLI surfaces when they exist
- Assume scripts are host-specific; read them before running on a new machine
**Auth monitoring**: Covered in Authentication docs. Scripts under `scripts/` are optional extras for systemd/Termux phone workflows.
**GitHub read helper** (`scripts/gh-read`):
- Uses GitHub App installation token for repo-scoped read calls
- Required env: `OPENCLAW_GH_READ_APP_ID`, `OPENCLAW_GH_READ_PRIVATE_KEY_FILE`
- Optional: `OPENCLAW_GH_READ_INSTALLATION_ID`, `OPENCLAW_GH_READ_PERMISSIONS`
```bash
scripts/gh-read pr view 123
scripts/gh-read run list -R openclaw/openclaw
scripts/gh-read api repos/openclaw/openclaw/pulls/123
```
---
## Nodes
A **node** is a companion device (macOS/iOS/Android/headless) that connects to the Gateway **WebSocket** with `role: "node"` and exposes a command surface via `node.invoke`.
- Nodes are **peripherals**, not gateways — they don't run the gateway service
- Telegram/WhatsApp/etc. messages land on the **gateway**, not on nodes
### Pairing and Status
```bash
openclaw devices list
openclaw devices approve <requestId>
openclaw devices reject <requestId>
openclaw nodes status
openclaw nodes describe --node <idOrNameOrIp>
```
If a node retries with changed auth details (role/scopes/public key), the prior pending request is superseded and a new `requestId` is created. Re-run `openclaw devices list` before approving.
Notes:
- The device pairing record is the durable approved-role contract. Token rotation stays inside that contract; it cannot upgrade a paired node into a different role that pairing approval never granted.
- `node.pair.*` (CLI: `openclaw nodes pending/approve/reject/rename`) is a separate gateway-owned node pairing store; it does **not** gate the WS `connect` handshake.
**Approval scope:**
- Commandless request: `operator.pairing`
- Non-exec node commands: `operator.pairing` + `operator.write`
- `system.run` / `system.run.prepare` / `system.which`: `operator.pairing` + `operator.admin`
### Remote Node Host (system.run)
Use a **node host** when your Gateway runs on one machine and you want commands to execute on another.
**What runs where:**
- **Gateway host**: receives messages, runs the model, routes tool calls
- **Node host**: executes `system.run`/`system.which` on the node machine
- **Approvals**: enforced on the node host via `~/.openclaw/exec-approvals.json`
**Start a node host (foreground):**
```bash
openclaw node run --host <gateway-host> --port 18789 --display-name "Build Node"
```
**Remote gateway via SSH tunnel (loopback bind):**
```bash
# Terminal A: forward local 18790 → gateway 127.0.0.1:18789
ssh -N -L 18790:127.0.0.1:18789 user@gateway-host
# Terminal B: export the gateway token and connect through the tunnel
export OPENCLAW_GATEWAY_TOKEN="<gateway-token>"
openclaw node run --host 127.0.0.1 --port 18790 --display-name "Build Node"
```
**Auth resolution (node host):**
- `OPENCLAW_GATEWAY_TOKEN` / `OPENCLAW_GATEWAY_PASSWORD` env vars (preferred)
- `gateway.auth.token` / `gateway.auth.password` config fallback
- In local mode, node host intentionally ignores `gateway.remote.token` / `gateway.remote.password`
- In remote mode, `gateway.remote.token` / `gateway.remote.password` are eligible per remote precedence rules
- If active local `gateway.auth.*` SecretRefs are configured but unresolved, node-host auth fails closed
- Node-host auth resolution only honors `OPENCLAW_GATEWAY_*` env vars
**Start a node host (service):**
```bash
openclaw node install --host <gateway-host> --port 18789 --display-name "Build Node"
openclaw node restart
```
**Pair and name:**
```bash
openclaw devices list
openclaw devices approve <requestId>
openclaw nodes status
```
**Naming:**
- `--display-name` on `openclaw node run` / `openclaw node install` (persists in `~/.openclaw/node.json`)
- `openclaw nodes rename --node <id|name|ip> --name "Build Node"` (gateway override)
**Allowlist exec commands (on gateway):**
```bash
openclaw approvals allowlist add --node <id|name|ip> "/usr/bin/uname"
openclaw approvals allowlist add --node <id|name|ip> "/usr/bin/sw_vers"
```
Approvals live on the node host at `~/.openclaw/exec-approvals.json`.
**Configure exec to use the node:**
```bash
openclaw config set tools.exec.host node
openclaw config set tools.exec.security allowlist
openclaw config set tools.exec.node "<id-or-name>"
```
Or per session:
```
/exec host=node security=allowlist node=<id-or-name>
```
**Note**: `host=auto` will not implicitly choose the node. Use explicit `host=node` or set `tools.exec.host=node`.
### Invoking Commands
```bash
# Low-level raw RPC
openclaw nodes invoke --node <idOrNameOrIp> --command canvas.eval --params '{"javaScript":"location.href"}'
```
### Screenshots (Canvas Snapshots)
```bash
openclaw nodes canvas snapshot --node <idOrNameOrIp> --format png
openclaw nodes canvas snapshot --node <idOrNameOrIp> --format jpg --max-width 1200 --quality 0.9
```
### Canvas Controls
```bash
openclaw nodes canvas present --node <idOrNameOrIp> --target https://example.com
openclaw nodes canvas hide --node <idOrNameOrIp>
openclaw nodes canvas navigate https://example.com --node <idOrNameOrIp>
openclaw nodes canvas eval --node <idOrNameOrIp> --js "document.title"
```
### A2UI (Canvas)
```bash
openclaw nodes canvas a2ui push --node <idOrNameOrIp> --text "Hello"
openclaw nodes canvas a2ui push --node <idOrNameOrIp> --jsonl ./payload.jsonl
openclaw nodes canvas a2ui reset --node <idOrNameOrIp>
```
Only A2UI v0.8 JSONL is supported (v0.9/createSurface is rejected).
### Photos + Videos (Node Camera)
```bash
# List cameras
openclaw nodes camera list --node <idOrNameOrIp>
# Photos (jpg)
openclaw nodes camera snap --node <idOrNameOrIp> # default: both facings
openclaw nodes camera snap --node <idOrNameOrIp> --facing front
# Video clips (mp4)
openclaw nodes camera clip --node <idOrNameOrIp> --duration 10s
openclaw nodes camera clip --node <idOrNameOrIp> --duration 3000 --no-audio
```
**Notes:**
- Node must be **foregrounded** for `canvas.*` and `camera.*`
- Clip duration is clamped (`<= 60s`)
### Screen Recordings
```bash
openclaw nodes screen record --node <idOrNameOrIp> --duration 10s --fps 10
openclaw nodes screen record --node <idOrNameOrIp> --duration 10s --fps 10 --no-audio
```
- Screen recordings are clamped to `<= 60s`
- Use `--screen <index>` to select a display when multiple screens are available
### Location
```bash
openclaw nodes location get --node <idOrNameOrIp>
openclaw nodes location get --node <idOrNameOrIp> --accuracy precise --max-age 15000 --location-timeout 10000
```
- Location is **off by default**
- Response includes lat/lon, accuracy (meters), and timestamp
### SMS (Android Nodes)
```bash
openclaw nodes invoke --node <idOrNameOrIp> --command sms.send --params '{"to":"+15555550123","message":"Hello from OpenClaw"}'
```
Wi-Fi-only devices without telephony will not advertise `sms.send`.
### Android Device + Personal Data Commands
Available families (availability depends on device + permissions):
- `device.status`, `device.info`, `device.permissions`, `device.health`
- `notifications.list`, `notifications.actions`
- `photos.latest`
- `contacts.search`, `contacts.add`
- `calendar.events`, `calendar.add`
- `callLog.search`
- `sms.search`
- `motion.activity`, `motion.pedometer`
```bash
openclaw nodes invoke --node <idOrNameOrIp> --command device.status --params '{}'
openclaw nodes invoke --node <idOrNameOrIp> --command notifications.list --params '{}'
openclaw nodes invoke --node <idOrNameOrIp> --command photos.latest --params '{"limit":1}'
```
### System Commands (Node Host / Mac Node)
```bash
# Send notification
openclaw nodes notify --node <idOrNameOrIp> --title "Ping" --body "Gateway ready"
# Check which binary
openclaw nodes invoke --node <idOrNameOrIp> --command system.which --params '{"name":"git"}'
```
**Notes on `system.run`:**
- `nodes invoke` does not expose `system.run` or `system.run.prepare` — those stay on the exec path only
- `system.run` environment overrides are filtered (drops `PATH`, `DYLD_*`, `LD_*`, `NODE_OPTIONS`, `PYTHON*`, `PERL*`, `RUBYOPT`, `SHELLOPTS`, `PS4`)
- For shell wrappers (`bash|sh|zsh ... -c/-lc`), request-scoped env overrides are reduced to a small allowlist (`TERM`, `LANG`, `LC_*`, `COLORTERM`, `NO_COLOR`, `FORCE_COLOR`)
- For approval-backed `host=node` runs, the gateway binds execution to the prepared canonical `systemRunPlan`. If a later caller mutates command/cwd or session metadata before the approved run is forwarded, the gateway rejects as approval mismatch
- On Windows node hosts, shell-wrapper forms like `cmd.exe /c ...` are treated as allowlist misses in allowlist mode unless approved via ask flow
- `system.notify` respects notification permission state
- Supports `--priority <passive|active|timeSensitive>` and `--delivery <system|overlay|auto>`
### Exec Node Binding
```bash
# Global default
openclaw config set tools.exec.node "node-id-or-name"
# Per-agent override
openclaw config set agents.list[0].tools.exec.node "node-id-or-name"
# Unset to allow any node
openclaw config unset tools.exec.node
```
### Headless Node Host (Cross-Platform)
```bash
openclaw node run --host <gateway-host> --port 18789
```
- Pairing is still required
- Node host stores its info in `~/.openclaw/node.json`
- Exec approvals enforced locally via `~/.openclaw/exec-approvals.json`
- Add `--tls` / `--tls-fingerprint` when the Gateway WS uses TLS
### Mac Node Mode
The macOS menubar app connects to the Gateway WS server as a node. In remote mode, the app opens an SSH tunnel for the Gateway port and connects to `localhost`.
---
## Diagnostics Export
OpenClaw can create a local diagnostics zip safe to attach to bug reports:
```bash
openclaw gateway diagnostics export
openclaw gateway diagnostics export --output openclaw-diagnostics.zip
openclaw gateway diagnostics export --json
```
The zip includes: `summary.md`, `diagnostics.json`, `manifest.json`, sanitized config shape, redacted log summaries, best-effort gateway status/health snapshots, and `stability/latest.json` (newest persisted stability bundle). The export is useful even when the gateway is unhealthy.
**Privacy model**: The export omits chat text, prompts, credentials, API keys, tokens, raw request/response bodies, account/message/session IDs, hostnames, and local usernames. It keeps operational metadata (subsystem names, plugin/provider/channel IDs, status codes, durations, byte counts, queue state, memory readings).
**Stability recorder** (enabled by default): records bounded, payload-free stability stream. Inspect with `openclaw gateway stability` or `--type payload.large` or `--json`. After fatal exit/restart failure: `openclaw gateway stability --bundle latest`. Create zip from latest bundle: `openclaw gateway stability --bundle latest --export`. Persisted bundles live under `~/.openclaw/logs/stability/`. Disable with `diagnostics.enabled: false`.
## Diagnostics Flags
Diagnostics flags enable targeted debug logs without turning on verbose logging everywhere. Flags are opt-in and case-insensitive.
Wildcards supported:
- `telegram.*` matches `telegram.http`
- `*` enables all flags
### Enable via Config
```json
{
"diagnostics": {
"flags": ["telegram.http"]
}
}
```
Multiple flags:
```json
{
"diagnostics": {
"flags": ["telegram.http", "gateway.*"]
}
}
```
Restart the gateway after changing flags.
### Env Override (One-off)
```bash
OPENCLAW_DIAGNOSTICS=telegram.http,telegram.payload
```
Disable all flags:
```bash
OPENCLAW_DIAGNOSTICS=0
```
### Where Logs Go
Default: `/tmp/openclaw/openclaw-YYYY-MM-DD.log`
If you set `logging.file`, use that path instead. Logs are JSONL (one JSON object per line). Redaction still applies based on `logging.redactSensitive`.
### Extract Logs
```bash
# Pick the latest log file
ls -t /tmp/openclaw/openclaw-*.log | head -n 1
# Filter for Telegram HTTP diagnostics
rg "telegram http error" /tmp/openclaw/openclaw-*.log
# Tail while reproducing
tail -f /tmp/openclaw/openclaw-$(date +%F).log | rg "telegram http error"
# For remote gateways
openclaw logs --follow
```
**Notes:**
- If `logging.level` is set higher than `warn`, these logs may be suppressed
- Flags are safe to leave enabled; they only affect log volume for the specific subsystem
---
## CI Pipeline
The CI runs on every push to `main` and every pull request. It uses smart scoping to skip expensive jobs when only unrelated areas changed.
Additional workflows:
- **QA Lab**: dedicated lanes outside main smart-scoped workflow. `Parity gate` runs on matching PR changes; `QA-Lab - All Lanes` runs nightly on `main`. Live jobs use `qa-live-shared` environment.
- **Duplicate PRs After Merge**: manual maintainer workflow for post-land duplicate cleanup. Defaults to dry-run; `apply=true` to close.
- **Docs Agent**: event-driven Codex lane for keeping docs aligned with landed changes. Triggered by successful non-bot push CI on `main`; skips when main has moved on or another run created in last hour.
- **Test Performance Agent**: event-driven Codex lane for slow tests. Skips if another run already ran that UTC day. Makes small coverage-preserving performance fixes, reruns full-suite report, rejects changes that reduce passing baseline.
### Job Overview
| Job | Purpose | When it runs |
|---|---|---|
| `preflight` | Detect docs-only changes, changed scopes, changed extensions, build CI manifest | Always on non-draft pushes and PRs |
| `security-scm-fast` | Private key detection and workflow audit via `zizmor` | Always |
| `security-dependency-audit` | Dependency-free production lockfile audit against npm advisories | Always |
| `security-fast` | Required aggregate for the fast security jobs | Always |
| `build-artifacts` | Build `dist/`, Control UI, built-artifact checks, and reusable downstream artifacts | Node-relevant changes |
| `checks-fast-core` | Fast Linux correctness lanes (bundled/plugin-contract/protocol checks) | Node-relevant changes |
| `checks-fast-contracts-channels` | Sharded channel contract checks with stable aggregate | Node-relevant changes |
| `checks-node-extensions` | Full bundled-plugin test shards across extension suite | Node-relevant changes |
| `checks-node-core-test` | Core Node test shards, excluding channel/bundled/contract/extension | Node-relevant changes |
| `extension-fast` | Focused tests for only changed bundled plugins | PRs with extension changes |
| `check` | Sharded main local gate: prod types, lint, guards, test types, strict smoke | Node-relevant changes |
| `check-additional` | Architecture, boundary, extension-surface, package-boundary, gateway-watch shards | Node-relevant changes |
| `build-smoke` | Built-CLI smoke tests and startup-memory smoke | Node-relevant changes |
| `checks` | Verifier for built-artifact channel tests plus push-only Node 22 compat | Node-relevant changes |
| `check-docs` | Docs formatting, lint, and broken-link checks | Docs changed |
| `skills-python` | Ruff + pytest for Python-backed skills | Python-skill-relevant changes |
| `checks-windows` | Windows-specific test lanes | Windows-relevant changes |
| `macos-node` | macOS TypeScript test lane using shared built artifacts | macOS-relevant changes |
| `macos-swift` | Swift lint, build, and tests for the macOS app | macOS-relevant changes |
| `android` | Android unit tests for both flavors plus one debug APK build | Android-relevant changes |
| `test-performance-agent` | Daily Codex slow-test optimization after trusted activity | Main CI success or manual dispatch |
### Fail-Fast Order
1. `preflight` decides which lanes exist
2. `security-*`, `check`, `check-additional`, `check-docs`, `skills-python` fail quickly
3. `build-artifacts` overlaps with fast Linux lanes
4. Heavier platform and runtime lanes fan out after that
Scope logic: `scripts/ci-changed-scope.mjs` (unit tests in `src/scripts/ci-changed-scope.test.ts`).
### Runners
| Runner | Jobs |
|---|---|
| `ubuntu-24.04` | `preflight`, fast security jobs, fast protocol/contract/bundled checks, check shards except lint, docs checks |
| `blacksmith-8vcpu-ubuntu-2404` | `build-artifacts`, build-smoke, Linux Node test shards, bundled plugin test shards, `android` |
| `blacksmith-16vcpu-ubuntu-2404` | `check-lint`, install-smoke Docker builds |
| `blacksmith-16vcpu-windows-2025` | `checks-windows` |
| `blacksmith-6vcpu-macos-latest` | `macos-node` |
| `blacksmith-12vcpu-macos-latest` | `macos-swift` |
### Local Equivalents
```bash
pnpm changed:lanes # inspect the local changed-lane classifier
```
CI concurrency key is versioned (`CI-v7-*`) so zombie queued groups don't block newer main runs.
---
## RPC Adapters
OpenClaw integrates external CLIs via JSON-RPC using two patterns.
### Pattern A: HTTP Daemon (signal-cli)
- `signal-cli` runs as a daemon with JSON-RPC over HTTP
- Event stream is SSE (`/api/v1/events`)
- Health probe: `/api/v1/check`
- OpenClaw owns lifecycle when `channels.signal.autoStart=true`
### Pattern B: stdio Child Process (legacy: imsg)
> **Note**: For new iMessage setups, use [BlueBubbles](/channels/bluebubbles) instead.
- OpenClaw spawns `imsg rpc` as a child process (legacy iMessage integration)
- JSON-RPC is line-delimited over stdin/stdout
- No TCP port, no daemon required
Core methods:
- `watch.subscribe` → notifications (`method: "message"`)
- `watch.unsubscribe`
- `send`
- `chats.list` (probe/diagnostics)
### Adapter Guidelines
- Gateway owns the process (start/stop tied to provider lifecycle)
- Keep RPC clients resilient: timeouts, restart on exit
- Prefer stable IDs (e.g., `chat_id`) over display strings
FILE:references/11-platforms.md
# OpenClaw Platforms Reference
## Table of Contents
- [Overview](#overview)
- [macOS](#macos)
- [Windows](#windows)
- [Linux](#linux)
- [iOS](#ios)
- [Android](#android)
- [VPS Hosting](#vps-hosting)
- [Web UI (Control UI & Dashboard)](#web-ui-control-ui--dashboard)
## Overview
OpenClaw core is written in TypeScript. **Node is the recommended runtime.** Bun is not recommended for the Gateway (known issues with WhatsApp and Telegram channels).
Companion apps exist for:
- **macOS**: menu bar app (available)
- **iOS**: internal preview (not publicly distributed yet)
- **Android**: source available, not publicly released
**Gateway service install target by OS:**
- macOS: LaunchAgent (`ai.openclaw.gateway` or `ai.openclaw.<profile>`)
- Linux/WSL2: systemd user service (`openclaw-gateway[-<profile>].service`)
- Native Windows: Scheduled Task (`OpenClaw Gateway` or `OpenClaw Gateway (<profile>)`) with per-user Startup-folder fallback
---
## macOS
### What the macOS App Does
The macOS app is the **menu-bar companion** for OpenClaw. It:
- Shows native notifications and status in the menu bar
- Owns TCC prompts (Notifications, Accessibility, Screen Recording, Microphone, Speech Recognition, Automation/AppleScript)
- Runs or connects to the Gateway (local or remote)
- Exposes macOS-only tools (Canvas, Camera, Screen Recording, `system.run`)
- Starts the local node host service in **remote** mode (launchd), and stops it in **local** mode
- Optionally hosts **PeekabooBridge** for UI automation
- Installs the global CLI (`openclaw`) on request via npm, pnpm, or bun (app prefers npm, then pnpm, then bun)
### Local vs Remote Mode
**Local** (default): the app attaches to a running local Gateway if present; otherwise enables the launchd service via `openclaw gateway install`.
**Remote**: the app connects to a Gateway over SSH/Tailscale and never starts a local process. The app starts the local **node host service** so the remote Gateway can reach this Mac.
Gateway discovery prefers Tailscale MagicDNS names over raw tailnet IPs, so the Mac app recovers more reliably when tailnet IPs change.
### Launchd Control
The app manages a per-user LaunchAgent labeled `ai.openclaw.gateway` (or `ai.openclaw.<profile>` with `--profile`/`OPENCLAW_PROFILE`; legacy `com.openclaw.*` still unloads).
```bash
launchctl kickstart -k gui/$UID/ai.openclaw.gateway
launchctl bootout gui/$UID/ai.openclaw.gateway
```
### Node Capabilities (macOS)
Common commands exposed:
- Canvas: `canvas.present`, `canvas.navigate`, `canvas.eval`, `canvas.snapshot`, `canvas.a2ui.*`
- Camera: `camera.snap`, `camera.clip`
- Screen: `screen.snapshot`, `screen.record`
- System: `system.run`, `system.notify`
The node reports a `permissions` map so agents can decide what's allowed.
**Node service + app IPC architecture:**
```
Gateway → Node Service (WS)
| IPC (UDS + token + HMAC + TTL)
v
Mac App (UI + TCC + system.run)
```
When the headless node host service is running (remote mode), it connects to the Gateway WS as a node. `system.run` executes in the macOS app (UI/TCC context) over a local Unix socket; prompts + output stay in-app.
### Exec Approvals (system.run)
`system.run` is controlled by **Exec approvals** in Settings → Exec approvals. Stored at `~/.openclaw/exec-approvals.json`.
```json
{
"version": 1,
"defaults": {
"security": "deny",
"ask": "on-miss"
},
"agents": {
"main": {
"security": "allowlist",
"ask": "on-miss",
"allowlist": [{ "pattern": "/opt/homebrew/bin/rg" }]
}
}
}
```
**Notes:**
- `allowlist` entries are glob patterns for resolved binary paths
- Raw shell command text containing shell control/expansion syntax (`&&`, `||`, `;`, `|`, `` ` ``, `$`, `<`, `>`, `(`, `)`) is treated as an allowlist miss
- Choosing "Always Allow" in the prompt adds that command to the allowlist
- Environment overrides are filtered (drops `PATH`, `DYLD_*`, `LD_*`, `NODE_OPTIONS`, `PYTHON*`, `PERL*`, `RUBYOPT`, `SHELLOPTS`, `PS4`)
- For shell wrappers (`bash|sh|zsh ... -c/-lc`), request-scoped env overrides are reduced to: `TERM`, `LANG`, `LC_*`, `COLORTERM`, `NO_COLOR`, `FORCE_COLOR`
- For allow-always decisions, known dispatch wrappers (`env`, `nice`, `nohup`, `stdbuf`, `timeout`) persist inner executable paths instead of wrapper paths
### Deep Links
The app registers the `openclaw://` URL scheme.
**`openclaw://agent`** — Triggers a Gateway `agent` request:
```bash
open 'openclaw://agent?message=Hello%20from%20deep%20link'
```
Query parameters:
- `message` (required)
- `sessionKey` (optional)
- `thinking` (optional)
- `deliver` / `to` / `channel` (optional)
- `timeoutSeconds` (optional)
- `key` (optional — unattended mode key)
**Safety:**
- Without `key`, the app prompts for confirmation and enforces a short message limit
- With a valid `key`, the run is unattended (intended for personal automations)
### Onboarding Flow (Typical)
1. Install and launch **OpenClaw.app**
2. Complete the permissions checklist (TCC prompts)
3. Ensure **Local** mode is active and the Gateway is running
4. Install the CLI if you want terminal access
### State Dir Placement (macOS)
Avoid putting your OpenClaw state dir in iCloud or other cloud-synced folders (can add latency and cause file-lock/sync races for sessions and credentials).
Prefer: `OPENCLAW_STATE_DIR=~/.openclaw`
Paths `openclaw doctor` warns about:
- `~/Library/Mobile Documents/com~apple~CloudDocs/...`
- `~/Library/CloudStorage/...`
### Build & Dev Workflow (Native)
```bash
cd apps/macos && swift build
swift run OpenClaw # or Xcode
scripts/package-mac-app.sh # package app
```
### Debug Gateway Connectivity (macOS CLI)
```bash
cd apps/macos
swift run openclaw-mac connect --json
swift run openclaw-mac discover --timeout 3000 --json
```
Connect options:
- `--url <ws://host:port>`: override config
- `--mode <local|remote>`: resolve from config (default: config or local)
- `--probe`: force a fresh health probe
- `--timeout <ms>`: request timeout (default: `15000`)
- `--json`: structured output for diffing
Discovery options:
- `--include-local`: include gateways filtered as "local"
- `--timeout <ms>`: overall discovery window (default: `2000`)
- `--json`: structured output for diffing
Tip: compare against `openclaw gateway discover --json` to see whether the macOS app’s discovery pipeline (`local.` plus configured wide-area domain, with wide-area and Tailscale Serve fallbacks) differs from the Node CLI’s `dns-sd` based discovery.
### Remote Connection Plumbing (SSH Tunnels)
When in **Remote** mode, the app opens an SSH tunnel.
**Control tunnel (Gateway WebSocket port):**
- Purpose: health checks, status, Web Chat, config, and other control-plane calls
- Local port: the Gateway port (default `18789`), always stable
- SSH shape: `ssh -N -L <local>:127.0.0.1:<remote>` with BatchMode + ExitOnForwardFailure + keepalive options
- Note: the SSH tunnel uses loopback, so the gateway will see the node IP as `127.0.0.1`. Use **Direct (ws/wss)** transport if you want the real client IP.
---
## Windows
OpenClaw supports both **native Windows** and **WSL2**. WSL2 is the more stable path and recommended for the full experience.
Native Windows companion apps are not yet available. The live docs state: "We do not have a Windows companion app yet. Contributions are welcome if you want contributions to make it happen."
### WSL2 (Recommended)
- Follow [Getting Started](/start/getting-started) inside WSL
- Official WSL2 guide: https://learn.microsoft.com/windows/wsl/install
**Step-by-step WSL2 install:**
1. **Install WSL2 + Ubuntu** (PowerShell as Admin):
```powershell
wsl --install
# Or pick a distro explicitly:
wsl --list --online
wsl --install -d Ubuntu-24.04
```
Reboot if Windows asks.
2. **Enable systemd** (required for gateway install):
```bash
sudo tee /etc/wsl.conf >/dev/null <<'EOF'
[boot]
systemd=true
EOF
```
Then from PowerShell: `wsl --shutdown`, then re-open Ubuntu.
Verify: `systemctl --user status`
3. **Install OpenClaw** (inside WSL):
```bash
git clone https://github.com/openclaw/openclaw.git
cd openclaw
pnpm install
pnpm build
pnpm ui:build
pnpm openclaw onboard --install-daemon
```
If developing from source instead of first-time onboarding, use the source dev loop:
```bash
pnpm install
pnpm openclaw setup # first run only (or after resetting config/workspace)
pnpm gateway:watch
```
### Native Windows Status
What works well today:
- Website installer via `install.ps1`
- Local CLI use: `openclaw --version`, `openclaw doctor`, `openclaw plugins list --json`
- Embedded local-agent/provider smoke
Current caveats:
- `openclaw onboard --non-interactive` expects a reachable local gateway unless you pass `--skip-health`
- `openclaw gateway install` tries Windows Scheduled Tasks first (preferred because they provide better supervisor status)
- If Scheduled Task creation is denied, the **fallback service mode still auto-starts after login through the current user's Startup folder**
- If `schtasks` itself wedges, OpenClaw aborts that path quickly and falls back
**Native Windows smoke test (no gateway service required):**
```powershell
openclaw agent --local --agent main --thinking low -m "Reply with exactly WINDOWS-HATCH-OK."
```
**Native CLI only (no gateway service install):**
```powershell
openclaw onboard --non-interactive --skip-health
openclaw gateway run
```
**Managed startup on native Windows:**
```powershell
openclaw gateway install
openclaw gateway status --json
```
### Gateway Auto-Start Before Windows Login
```bash
# Inside WSL: keep user services running without login
sudo loginctl enable-linger "$(whoami)"
# Inside WSL: install the OpenClaw gateway user service
openclaw gateway install
# PowerShell as Administrator: start WSL automatically at Windows boot
schtasks /create /tn "WSL Boot" /tr "wsl.exe -d Ubuntu --exec /bin/true" /sc onstart /ru SYSTEM
```
Replace `Ubuntu` with your distro name from `wsl --list --verbose`.
**Verify startup chain** (after reboot, before Windows sign-in):
```bash
systemctl --user is-enabled openclaw-gateway.service
systemctl --user status openclaw-gateway.service --no-pager
```
### Expose WSL Services Over LAN (portproxy)
WSL has its own virtual network. To forward a Windows port to a WSL service:
```powershell
$Distro = "Ubuntu-24.04"
$ListenPort = 2222
$TargetPort = 22
$WslIp = (wsl -d $Distro -- hostname -I).Trim().Split(" ")[0]
if (-not $WslIp) { throw "WSL IP not found." }
netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=$ListenPort `
connectaddress=$WslIp connectport=$TargetPort
```
Allow port through Windows Firewall (one-time):
```powershell
New-NetFirewallRule -DisplayName "WSL SSH $ListenPort" -Direction Inbound `
-Protocol TCP -LocalPort $ListenPort -Action Allow
```
Refresh portproxy after WSL restarts:
```powershell
netsh interface portproxy delete v4tov4 listenport=$ListenPort listenaddress=0.0.0.0 | Out-Null
netsh interface portproxy add v4tov4 listenport=$ListenPort listenaddress=0.0.0.0 `
connectaddress=$WslIp connectport=$TargetPort | Out-Null
```
**Notes:**
- SSH from another machine targets the **Windows host IP**: `ssh user@windows-host -p 2222`
- Remote nodes must point at a **reachable** Gateway URL (not `127.0.0.1`)
- Use `listenaddress=0.0.0.0` for LAN access; `127.0.0.1` keeps it local only
---
## Linux
The Gateway is fully supported on Linux. **Node is the recommended runtime.** Bun is not recommended (WhatsApp/Telegram bugs).
Native Linux companion apps are planned.
### Beginner Quick Path (VPS)
1. Install Node 24 (recommended; Node 22 LTS, currently `22.14+`, still works for compatibility): `curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash - && sudo apt install -y nodejs`
2. `npm i -g openclaw@latest`
3. `openclaw onboard --install-daemon`
4. From your laptop: `ssh -N -L 18789:127.0.0.1:18789 <user>@<host>`
5. Open `http://127.0.0.1:18789/` and authenticate with the configured shared secret (token by default; password if you set `gateway.auth.mode: "password"`)
**Systemd unit note:** `openclaw gateway install` and `openclaw onboard --install-daemon` already render the current canonical unit for you; write one by hand only when you need a custom system/service-manager setup. The full service guidance lives in the Gateway runbook.
Full Linux server guide: [Linux Server](/vps). Step-by-step VPS example: [exe.dev](/install/exe-dev)
### Gateway Service Install
```bash
openclaw onboard --install-daemon
# OR
openclaw gateway install
# OR
openclaw configure # then select Gateway service
```
Repair/migrate: `openclaw doctor`
### System Control (systemd User Unit)
OpenClaw installs a systemd **user** service by default. Use a **system** service for shared or always-on servers.
**Minimal unit:**
```ini
[Unit]
Description=OpenClaw Gateway (profile: <profile>, v<version>)
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=/usr/local/bin/openclaw gateway --port 18789
Restart=always
RestartSec=5
TimeoutStopSec=30
TimeoutStartSec=30
SuccessExitStatus=0 143
KillMode=control-group
[Install]
WantedBy=default.target
```
```bash
systemctl --user enable --now openclaw-gateway.service
```
### Memory Pressure and OOM Kills
On Linux, OpenClaw biases transient child processes to be killed before the Gateway when possible.
For eligible Linux child spawns, OpenClaw starts the child through a short `/bin/sh` wrapper that raises the child's own `oom_score_adj` to `1000`, then `exec`s the real command.
**Covered child process surfaces:**
- Supervisor-managed command children
- PTY shell children
- MCP stdio server children
- OpenClaw-launched browser/Chrome processes
The wrapper is Linux-only and is skipped when `/bin/sh` is unavailable. Also skipped if child env sets `OPENCLAW_CHILD_OOM_SCORE_ADJ=0`, `false`, `no`, or `off`.
**Verify:**
```bash
cat /proc/<child-pid>/oom_score_adj
# Expected: 1000 for covered children; Gateway should keep its normal score (usually 0)
```
**Note:** This does not replace normal memory tuning. If a VPS or container repeatedly kills children, increase the memory limit, reduce concurrency, or add stronger resource controls such as systemd `MemoryMax=` or container-level memory limits.
---
## iOS
**Availability**: internal preview — NOT publicly distributed yet.
### What it Does
- Connects to a Gateway over WebSocket (LAN or tailnet)
- Exposes node capabilities: Canvas, Screen snapshot, Camera capture, Location, Talk mode, Voice wake
- Receives `node.invoke` commands and reports node status events
### Requirements
- Gateway running on another device
- Network path: same LAN via Bonjour, OR tailnet via unicast DNS-SD, OR manual host/port (fallback)
### Quick Start
1. Start the Gateway: `openclaw gateway --port 18789`
2. In the iOS app, open Settings and pick a discovered gateway (or enable Manual Host and enter host/port)
3. Approve pairing: `openclaw devices list && openclaw devices approve <requestId>`
4. Verify: `openclaw nodes status`
Optional: if the iOS node always connects from a tightly controlled subnet, opt in to first-time node auto-approval:
```json5
{
gateway: {
nodes: {
pairing: {
autoApproveCidrs: ["192.168.1.0/24"],
},
},
},
}
```
Disabled by default. Applies only to fresh `role: node` pairing with no requested scopes. Operator/browser pairing and role/scope/metadata/public-key changes still require manual approval.
### Relay-Backed Push (Official Builds)
Official distributed iOS builds use an external push relay. Gateway config required:
```json5
{
gateway: {
push: {
apns: {
relay: {
baseUrl: "https://relay.example.com",
},
},
},
},
}
```
**How it works:** The iOS app registers with the relay using App Attest and the app receipt. The relay returns an opaque relay handle plus a registration-scoped send grant. The app includes the paired gateway identity in relay registration so the relay-backed registration is delegated to that specific gateway. Another gateway cannot reuse that stored registration, even if it somehow obtains the handle.
**Compatibility note:** `OPENCLAW_APNS_RELAY_BASE_URL` still works as a temporary env override for the gateway. If the app later connects to a different gateway or a build with a different relay base URL, it refreshes the relay registration instead of reusing the old binding.
**Authentication and trust flow (hop by hop):**
1. `iOS app → gateway`: normal Gateway auth flow + device pairing; operator session is used to call `gateway.identity.get`
2. `iOS app → relay`: App Attest proof + app receipt; validates bundle ID and official/production distribution path. **This blocks local Xcode/dev builds from using the hosted relay** (a local build may be signed, but it does not satisfy the official Apple distribution proof the relay expects)
3. `Gateway identity delegation`: relay returns handle + send grant delegated to that specific gateway identity
4. `Gateway → relay`: gateway signs send request with its own device identity; relay verifies both stored send grant and gateway signature
5. `Relay → APNs`: relay owns production APNs credentials; gateway never stores the raw APNs token for relay-backed official builds
**What the gateway does NOT need for this path:**
- No deployment-wide relay token.
- No direct APNs key for official/TestFlight relay-backed sends.
**Expected operator flow:**
1. Install the official/TestFlight iOS build
2. Set `gateway.push.apns.relay.baseUrl` on the gateway
3. Pair the app to the gateway and let it finish connecting
4. The app publishes `push.apns.register` automatically after it has an APNs token, operator session is connected, and relay registration succeeds
5. After that, `push.test`, reconnect wakes, and wake nudges can use the stored relay-backed registration
**For local/manual builds without the relay:**
```bash
export OPENCLAW_APNS_TEAM_ID="TEAMID"
export OPENCLAW_APNS_KEY_ID="KEYID"
export OPENCLAW_APNS_PRIVATE_KEY_P8="$(cat /path/to/AuthKey_KEYID.p8)"
```
These are gateway-host runtime env vars. `apps/ios/fastlane/.env` only stores App Store Connect / TestFlight auth (such as `ASC_KEY_ID` and `ASC_ISSUER_ID`); it does **not** configure direct APNs delivery for local iOS builds.
**Recommended gateway-host APNs key storage:**
```bash
mkdir -p ~/.openclaw/credentials/apns
chmod 700 ~/.openclaw/credentials/apns
mv /path/to/AuthKey_KEYID.p8 ~/.openclaw/credentials/apns/AuthKey_KEYID.p8
chmod 600 ~/.openclaw/credentials/apns/AuthKey_KEYID.p8
export OPENCLAW_APNS_PRIVATE_KEY_PATH="$HOME/.openclaw/credentials/apns/AuthKey_KEYID.p8"
```
### Discovery Paths
**Bonjour (LAN)**: iOS app browses `_openclaw-gw._tcp` on `local.` AND, when configured, the same wide-area DNS-SD discovery domain. Same-LAN gateways appear automatically from `local.`; cross-network discovery can use the configured wide-area domain without changing the beacon type.
**Tailnet (cross-network)**: Use unicast DNS-SD zone (e.g., `openclaw.internal.`) and Tailscale split DNS.
**Manual host/port**: In Settings, enable **Manual Host** and enter gateway host + port (default `18789`).
### Canvas + A2UI
The iOS node renders a WKWebView canvas. Use `node.invoke` to drive it:
```bash
openclaw nodes invoke --node "iOS Node" --command canvas.navigate --params '{"url":"http://<gateway-host>:18789/__openclaw__/canvas/"}'
```
Canvas commands:
```bash
openclaw nodes invoke --node "iOS Node" --command canvas.eval --params '{"javaScript":"document.title"}'
openclaw nodes invoke --node "iOS Node" --command canvas.snapshot --params '{"maxWidth":900,"format":"jpeg"}'
```
The Gateway canvas host serves `/__openclaw__/canvas/` and `/__openclaw__/a2ui/` (same port as `gateway.port`, default `18789`).
**Auto-navigate**: The iOS node auto-navigates to A2UI on connect when a canvas host URL is advertised. Return to the built-in scaffold with `canvas.navigate` and `{"url":""}`.
### Voice Wake + Talk Mode
Available in Settings. iOS may suspend background audio — treat voice features as best-effort when the app is not active.
### Common Errors
- `NODE_BACKGROUND_UNAVAILABLE`: bring the iOS app to the foreground
- `A2UI_HOST_NOT_CONFIGURED`: the Gateway did not advertise a canvas host URL; check `canvasHost` in Gateway configuration
- Pairing prompt never appears: run `openclaw devices list` and approve manually
- Reconnect fails after reinstall: the Keychain pairing token was cleared; re-pair the node
---
## Android
> **Note**: The Android app has NOT been publicly released yet. Source code available in repo under `apps/android`. Build with Java 17 and Android SDK: `./gradlew :app:assemblePlayDebug`
### Support Snapshot
- Role: companion node app (Android does NOT host the Gateway)
- Gateway required: yes (run it on macOS, Linux, or Windows via WSL2)
### Connection Requirements
- Android connects directly to the Gateway WebSocket (`role: node`)
- For Tailscale/public hosts: requires secure endpoint (`wss://` or Tailscale Serve)
- Cleartext `ws://` supported on private LAN addresses / `.local` hosts, plus `localhost`, `127.0.0.1`, and the Android emulator bridge (`10.0.2.2`)
- **Important**: Tailnet/public mobile pairing does **not** use raw tailnet IP `ws://` endpoints — use Tailscale Serve instead
### Connection Runbook
1. **Start the Gateway**:
```bash
openclaw gateway --port 18789 --verbose
```
For remote Android access over Tailscale:
```bash
openclaw gateway --tailscale serve
```
2. **Verify discovery** (optional):
```bash
dns-sd -B _openclaw-gw._tcp local.
openclaw gateway discover --json
```
3. **Connect from Android**: In the Android app, the app keeps its gateway connection alive via a **foreground service** (persistent notification). Open the **Connect** tab and use **Setup Code** or **Manual** mode. For private LAN hosts, `ws://` still works. For Tailscale/public hosts, turn on TLS and use a `wss://` / Tailscale Serve endpoint.
4. **Approve pairing**:
```bash
openclaw devices list
openclaw devices approve <requestId>
openclaw devices reject <requestId>
```
Optional: if the Android node always connects from a tightly controlled subnet, opt in to first-time node auto-approval:
```json5
{
gateway: {
nodes: {
pairing: {
autoApproveCidrs: ["192.168.1.0/24"],
},
},
},
}
```
Disabled by default. Applies only to fresh `role: node` pairing with no requested scopes.
After the first successful pairing, Android auto-reconnects on launch: manual endpoint (if enabled), otherwise the last discovered gateway (best-effort).
5. **Verify node connected**:
```bash
openclaw nodes status
openclaw gateway call node.list --params "{}"
```
### Tailnet Discovery (cross-network)
Android NSD/mDNS discovery won't cross networks (for example Vienna ⇄ London cross-tailnet). For cross-network via Tailscale, use Wide-Area Bonjour / unicast DNS-SD:
1. Set up a DNS-SD zone (e.g., `openclaw.internal.`) on the gateway host
2. Configure Tailscale split DNS for your chosen domain
**Note:** Discovery alone is not sufficient for tailnet/public Android pairing. The discovered route still needs a secure endpoint (`wss://` or Tailscale Serve).
Compare discovery against: `openclaw gateway discover --json` (shows `local.` plus the configured wide-area domain in one pass, uses the resolved service endpoint instead of TXT-only hints).
### Chat + History
- `chat.history` (display-normalized; inline directive tags are stripped from visible text, plain-text tool-call XML payloads (including `<tool_call>...</tool_call>`, `<function_call>...</function_call>`, `<tool_calls>...</tool_calls>`, `<function_calls>...</function_calls>`, and truncated tool-call blocks) and leaked ASCII/full-width model control tokens are stripped, pure silent-token assistant rows such as exact `NO_REPLY` / `no_reply` are omitted, and oversized rows can be replaced with placeholders)
- `chat.send`
- `chat.subscribe` → `event:"chat"` (best-effort push updates)
### Canvas + Camera
**Navigate node to Gateway Canvas Host:**
```bash
openclaw nodes invoke --node "<Android Node>" --command canvas.navigate --params '{"url":"http://<gateway-hostname>.local:18789/__openclaw__/canvas/"}'
```
Canvas commands (foreground only):
- `canvas.eval`, `canvas.snapshot`, `canvas.navigate`
- A2UI: `canvas.a2ui.push`, `canvas.a2ui.reset` (`canvas.a2ui.pushJSONL` is a legacy alias)
Camera commands (foreground only; permission-gated):
- `camera.snap` (jpg)
- `camera.clip` (mp4)
### Voice + Expanded Android Command Surface
- Voice: single mic on/off flow with transcript capture and `talk.speak` playback. Local system TTS is used only when `talk.speak` is unavailable. Voice stops when the app leaves the foreground.
- Voice wake/talk-mode toggles are currently removed from Android UX/runtime
Additional Android command families (availability depends on device + permissions):
- `device.status`, `device.info`, `device.permissions`, `device.health`
- `notifications.list`, `notifications.actions`
- `photos.latest`
- `contacts.search`, `contacts.add`
- `calendar.events`, `calendar.add`
- `callLog.search`, `sms.search`
- `motion.activity`, `motion.pedometer`
### Assistant Entrypoints
Android supports launching OpenClaw from the system assistant trigger (Google Assistant). Uses Android **App Actions** metadata declared in the app manifest — no extra gateway config needed.
### Notification Forwarding Config
```json5
{
notifications: {
allowPackages: ["com.slack", "com.whatsapp"],
denyPackages: ["com.android.systemui"],
quietHours: {
start: "22:00",
end: "07:00",
},
rateLimit: 5,
},
}
```
| Key | Type | Description |
|---|---|---|
| `notifications.allowPackages` | string[] | Only forward notifications from these packages |
| `notifications.denyPackages` | string[] | Never forward from these packages (applied after allowPackages) |
| `notifications.quietHours.start` | string (HH:mm) | Start of quiet hours window (local device time) |
| `notifications.quietHours.end` | string (HH:mm) | End of quiet hours window |
| `notifications.rateLimit` | number | Maximum forwarded notifications per package per minute |
Notification forwarding requires the Android Notification Listener permission.
**Note:** The notification picker also uses safer behavior for forwarded notification events, preventing accidental forwarding of sensitive system notifications.
---
## VPS Hosting
### Provider Options
- **Railway**: one-click, browser setup
- **Northflank**: one-click, browser setup
- **DigitalOcean**: simple paid VPS
- **Oracle Cloud**: always Free ARM tier
- **Fly.io**: Fly Machines
- **Hetzner**: Docker on Hetzner VPS
- **Hostinger**: VPS with one-click setup
- **GCP**: Compute Engine
- **Azure**: Linux VM
- **exe.dev**: VM with HTTPS proxy
- **Raspberry Pi**: ARM self-hosted
**AWS (EC2 / Lightsail / free tier)** also works well.
### How Cloud Setups Work
- The **Gateway runs on the VPS** and owns state + workspace
- You connect from your laptop or phone via the **Control UI** or **Tailscale/SSH**
- Treat the VPS as the source of truth and **back up** the state + workspace regularly
- Secure default: keep the Gateway on loopback and access it via SSH tunnel or Tailscale Serve
- If you bind to `lan` or `tailnet`, require `gateway.auth.token` or `gateway.auth.password`
**Shared company agent on a VPS:**
Running a single agent for a team is valid when every user is in the same trust boundary and the agent is business-only. Keep it on a dedicated runtime (VPS/VM/container + dedicated OS user/accounts). Do not sign that runtime into personal Apple/Google accounts or personal browser/password-manager profiles. If users are adversarial to each other, split by gateway/host/OS user.
**Using nodes with a VPS:**
Keep the Gateway in the cloud and pair nodes on local devices (Mac/iOS/Android/headless). Nodes provide local screen/camera/canvas and `system.run` capabilities while the Gateway stays in the cloud.
### Startup Tuning for Small VMs
```bash
grep -q 'NODE_COMPILE_CACHE=/var/tmp/openclaw-compile-cache' ~/.bashrc || cat >> ~/.bashrc <<'EOF'
export NODE_COMPILE_CACHE=/var/tmp/openclaw-compile-cache
mkdir -p /var/tmp/openclaw-compile-cache
export OPENCLAW_NO_RESPAWN=1
EOF
source ~/.bashrc
```
- `NODE_COMPILE_CACHE` improves repeated command startup times
- `OPENCLAW_NO_RESPAWN=1` avoids extra startup overhead from a self-respawn path
- First command run warms the cache; subsequent runs are faster
**systemd tuning:**
```bash
systemctl --user edit openclaw-gateway.service
```
```ini
[Service]
Environment=OPENCLAW_NO_RESPAWN=1
Environment=NODE_COMPILE_CACHE=/var/tmp/openclaw-compile-cache
Restart=always
RestartSec=2
```
The live docs also recommend `TimeoutStartSec=90` in the systemd tuning checklist and suggest preferring SSD-backed disks for state/cache paths to reduce cold-start penalties.
### Raspberry Pi
Run a persistent, always-on Gateway on a Raspberry Pi.
**Prerequisites:**
- Raspberry Pi 4 or 5, 2 GB+ RAM (4 GB recommended)
- MicroSD card (16 GB+) or USB SSD
- 64-bit Raspberry Pi OS Lite (**do NOT use 32-bit**)
- About 30 minutes
**Setup steps:**
1. Flash with Raspberry Pi Imager (64-bit OS, enable SSH, set hostname/user/WiFi)
2. Connect via SSH
3. Update: `sudo apt update && sudo apt upgrade -y && sudo apt install -y git curl build-essential`
4. Set timezone: `sudo timedatectl set-timezone America/Chicago`
5. Install Node.js 24: `curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash - && sudo apt install -y nodejs`
6. Add swap (for 2 GB or less):
```bash
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
```
7. Install OpenClaw: `curl -fsSL https://openclaw.ai/install.sh | bash`
8. Run onboarding: `openclaw onboard --install-daemon`
9. Verify: `openclaw status && systemctl --user status openclaw-gateway.service`
**Access Control UI (from your laptop):**
```bash
ssh user@gateway-host 'openclaw dashboard --no-open'
ssh -N -L 18789:127.0.0.1:18789 user@gateway-host
```
**Performance tips:**
- **Use a USB SSD** — SD cards are slow and wear out
- Enable module compile cache (see above)
- Reduce memory: `echo 'gpu_mem=16' | sudo tee -a /boot/config.txt && sudo systemctl disable bluetooth`
**Troubleshooting:**
- Out of memory: verify swap with `free -h`, disable unused services
- Slow performance: use USB SSD, check `vcgencmd get_throttled` (should return `0x0`)
- Service won't start: `journalctl --user -u openclaw-gateway.service --no-pager -n 100`; verify lingering: `sudo loginctl enable-linger "$(whoami)"`
- ARM binary issues: verify `uname -m` shows `aarch64`
- WiFi drops: `sudo iwconfig wlan0 power off`
---
## Web UI (Control UI & Dashboard)
### Overview
The Gateway serves a small **browser Control UI** (Vite + Lit) from the same port as the Gateway WebSocket:
- Default: `http://<host>:18789/`
- Optional prefix: set `gateway.controlUi.basePath` (e.g., `/openclaw`)
### Configuration
```json5
{
gateway: {
controlUi: { enabled: true, basePath: "/openclaw" }, // basePath optional
},
}
```
Control UI is **enabled by default** (`controlUi.enabled: true` in the config schema).
The Control UI fetches runtime settings from `/__openclaw/control-ui-config.json`, gated by the same gateway auth as the rest of the HTTP surface.
### Tailscale Access
**Integrated Serve (recommended):**
```json5
{
gateway: {
bind: "loopback",
tailscale: { mode: "serve" },
},
}
```
Open `https://<magicdns>/` (or your configured `gateway.controlUi.basePath`).
**Tailnet bind + token:**
```json5
{
gateway: {
bind: "tailnet",
controlUi: { enabled: true },
auth: { mode: "token", token: "your-token" },
},
}
```
**Public internet (Funnel):**
```json5
{
gateway: {
bind: "loopback",
tailscale: { mode: "funnel" },
auth: { mode: "password" }, // or OPENCLAW_GATEWAY_PASSWORD
},
}
```
### Security Notes
- Gateway auth is required by default (token, password, trusted-proxy, or Tailscale Serve identity headers)
- Non-loopback binds still **require** gateway auth
- For non-loopback Control UI deployments, set `gateway.controlUi.allowedOrigins` explicitly
- `gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback=true` is a dangerous security downgrade
- **Browser-origin WS auth attempts are always throttled with loopback exemption disabled** (defense-in-depth against browser-based localhost brute force); repeated failures from the same normalized `Origin` are locked out temporarily in their own bucket
### Building the UI
```bash
pnpm ui:build
```
Static files served from `dist/control-ui`.
### Dashboard
Quick open (local Gateway): `http://127.0.0.1:18789/` or `http://localhost:18789/`
Open from CLI:
```bash
openclaw dashboard
```
**Authentication** is enforced at the WebSocket handshake via:
- `connect.params.auth.token`
- `connect.params.auth.password`
- Tailscale Serve identity headers when `gateway.auth.allowTailscale: true`
- trusted-proxy identity headers when `gateway.auth.mode: "trusted-proxy"`
**If you see "unauthorized" / 1008:**
- Ensure the gateway is reachable
- For `AUTH_TOKEN_MISMATCH`, clients may do one trusted retry with a cached device token
- Retrieve or supply the shared secret: `openclaw config get gateway.auth.token`
- In the dashboard settings, paste the token or password into the auth field
**Shared-secret token source**: `gateway.auth.token` (or `OPENCLAW_GATEWAY_TOKEN`). `openclaw dashboard` can pass it via URL fragment for one-time bootstrap.
**Device pairing (first connection)**: Even if you're on the same Tailnet, a one-time pairing approval is required.
```bash
openclaw devices list
openclaw devices approve <requestId>
```
Direct local loopback browser connections (`127.0.0.1` / `localhost`) are auto-approved. Each browser profile generates a unique device ID.
If the browser is already paired and you change it from read access to write/admin access, this is treated as an approval upgrade, not a silent reconnect — OpenClaw keeps the old approval active, blocks the broader reconnect, and asks you to approve the new scope set explicitly.
**Personal identity (browser-local):** The Control UI supports a per-browser personal identity (display name and avatar) attached to outgoing messages for attribution in shared sessions. It lives in browser storage, is scoped to the current browser profile, and is not synced to other devices or persisted server-side beyond transcript authorship metadata.
### Control UI Capabilities
- Chat with the model via Gateway WS (`chat.history`, `chat.send`, `chat.abort`, `chat.inject`)
- Talk to OpenAI Realtime directly from the browser via WebRTC
- Stream tool calls + live tool output cards in Chat
- Channels: status, QR login, per-channel config
- Sessions: list + per-session model/thinking/fast/verbose/trace/reasoning overrides
- Dreams: dreaming status, enable/disable, Dream Diary reader
- Cron jobs: list/add/edit/run/enable/disable + run history
- Skills: status, enable/disable, install, API key updates
- Nodes: list + caps
- Exec approvals: edit gateway or node allowlists + ask policy
- Config: view/edit `~/.openclaw/openclaw.json` with `config.get`/`config.set`/`config.apply` + restart with validation. Config writes include a base-hash guard to prevent clobbering concurrent edits. Config writes also preflight active SecretRef resolution — unresolved active submitted refs are rejected before write. Config schema + form rendering via `config.schema`/`config.schema.lookup`. Raw JSON editor available only when the snapshot can safely round-trip
- Debug: status/health/models snapshots + event log + manual RPC calls
- Logs: live tail of gateway file logs with filter/export
- Update: run a package/git update + restart with a restart report
**Language support**: `en`, `zh-CN`, `zh-TW`, `pt-BR`, `de`, `es`, `ja-JP`, `ko`, `fr`, `tr`, `uk`, `id`, `pl`, `th`
**Locale picker**: Overview → Gateway Access → Language (NOT under Appearance)
**Chat behavior:**
- `chat.send` is **non-blocking**: acks immediately with `{ runId, status: "started" }`, response streams via `chat` events
- Re-sending with the same `idempotencyKey` returns `{ status: "in_flight" }` while running, and `{ status: "ok" }` after completion
- Aborted partial assistant text persists into transcript history when buffered output exists, marked with abort metadata
- During an active send and the final history refresh, the chat view keeps local optimistic user/assistant messages visible if `chat.history` briefly returns an older snapshot; the canonical transcript replaces those once the Gateway history catches up
- `chat.inject` appends an assistant note to the session transcript and broadcasts a `chat` event for UI-only updates (no agent run, no channel delivery)
- The chat header model and thinking pickers patch the active session immediately through `sessions.patch` (persistent session overrides, not one-turn-only)
- When fresh Gateway session usage reports show high context pressure, the chat composer shows a context notice and compact button
- Talk mode uses a registered realtime voice provider (browser WebRTC). The Gateway mints a short-lived Realtime client secret with `talk.realtime.session`; the browser sends microphone audio directly to OpenAI and relays `openclaw_agent_consult` tool calls back through `chat.send`. The browser never receives the standard OpenAI API key
**Hosted embeds**: `gateway.controlUi.embedSandbox`:
- `strict`: disables script execution inside hosted embeds
- `scripts`: allows interactive embeds (default)
- `trusted`: adds `allow-same-origin` on top of `allow-scripts`
**Content Security Policy**: Only same-origin assets and `data:` URLs are allowed for images. Remote avatar URLs from channel metadata are replaced with the built-in logo/badge.
**Control UI over plain HTTP:**
- Needs `gateway.controlUi.allowInsecureAuth=true` for localhost in non-secure HTTP contexts
- `dangerouslyDisableDeviceAuth=true` disables device identity checks entirely (severe security downgrade)
### WebChat
The macOS/iOS SwiftUI chat UI talks directly to the Gateway WebSocket.
**Behavior:**
- Uses the same sessions and routing rules as other channels
- Deterministic routing: replies always go back to WebChat
- `chat.history` is bounded for stability (Gateway may truncate long text fields, omit heavy metadata, and replace oversized entries with `[chat.history omitted: message too large]`)
- `chat.history` is also display-normalized: strips runtime-only OpenClaw context, inbound envelope wrappers, inline delivery directive tags, plain-text tool-call XML payloads, leaked control tokens; omits assistant entries whose whole visible text is only `NO_REPLY`/`no_reply`
- `chat.inject` appends an assistant note directly to the transcript and broadcasts it to the UI (no agent run)
- Gateway persists aborted partial assistant text into transcript history when buffered output exists, marked with abort metadata
- History is always fetched from the gateway (no local file watching)
- If the gateway is unreachable, WebChat is read-only
**Configuration:**
- `gateway.webchat.chatHistoryMaxChars`: maximum character count for text fields in `chat.history` responses. Per-request `maxChars` can also be sent by the client to override this default for a single `chat.history` call
**Control UI agents tools panel:**
- **Available Right Now**: uses `tools.effective(sessionKey=...)` — shows what the current session can actually use at runtime, including core, plugin, and channel-owned tools
- **Tool Configuration**: uses `tools.catalog` — focused on profiles, overrides, and catalog semantics
- Runtime availability is session-scoped. Switching sessions on the same agent can change the **Available Right Now** list
- The config editor does not imply runtime availability; effective access still follows policy precedence (`allow`/`deny`, per-agent and provider/channel overrides)
**Dev server + remote Gateway:**
```text
http://localhost:5173/?gatewayUrl=ws://<gateway-host>:18789
```
Optional one-time auth:
```text
http://localhost:5173/?gatewayUrl=wss://<gateway-host>:18789#token=<gateway-token>
```
FILE:references/12-reference.md
# OpenClaw Reference Documentation
## Table of Contents
- [Configuration System Overview](#configuration-system-overview)
- [Configuration Reference — Full Schema](#configuration-reference--full-schema)
- [Agent Configuration Reference](#agent-configuration-reference)
- [Heartbeat Reference](#heartbeat-reference)
- [Agent Workspace Reference](#agent-workspace-reference)
- [Session Management Reference](#session-management-reference)
- [Authentication Reference](#authentication-reference)
- [Gateway Protocol Reference](#gateway-protocol-reference)
- [RPC Adapters Reference](#rpc-adapters-reference)
- [CLI Reference](#cli-reference)
- [Onboarding Reference](#onboarding-reference)
- [File Locations Quick Reference](#file-locations-quick-reference)
- [Environment Variables Quick Reference](#environment-variables-quick-reference)
## Configuration System Overview
OpenClaw reads an optional **JSON5** config from `~/.openclaw/openclaw.json` (supports comments and trailing commas). The active config path must be a regular file — symlinked `openclaw.json` layouts are unsupported for OpenClaw-owned writes; an atomic write may replace the path instead of preserving the symlink. If you keep config outside the default state directory, point `OPENCLAW_CONFIG_PATH` directly at the real file.
If the file is missing, OpenClaw uses safe defaults.
**Editing config:**
```bash
openclaw onboard # full onboarding flow
openclaw configure # config wizard
openclaw config get agents.defaults.workspace
openclaw config set agents.defaults.heartbeat.every "2h"
openclaw config unset plugins.entries.brave.config.webSearch.apiKey
```
Or edit `~/.openclaw/openclaw.json` directly — the Gateway watches the file and applies changes automatically.
### Strict Validation
OpenClaw only accepts configurations that fully match the schema. Unknown keys, malformed types, or invalid values cause the Gateway to **refuse to start**. The only root-level exception is `$schema` (string).
When validation fails:
- The Gateway does not boot
- Only diagnostic commands work (`openclaw doctor`, `openclaw logs`, `openclaw health`, `openclaw status`)
- Run `openclaw doctor` to see exact issues
- Run `openclaw doctor --fix` to apply repairs
The Gateway keeps a trusted last-known-good copy after each successful startup. If `openclaw.json` later fails validation (drops `gateway.mode`, shrinks sharply, or has a stray log line prepended), OpenClaw preserves the broken file as `.clobbered.*`, restores the last-known-good copy, and logs the recovery reason. The next agent turn also receives a system-event warning so the main agent does not blindly rewrite the restored config. Promotion to last-known-good is skipped when a candidate contains redacted secret placeholders such as `***`.
When every validation issue is scoped to `plugins.entries.<id>...`, OpenClaw does not perform whole-file recovery — it keeps the current config active and surfaces the plugin-local failure so a plugin schema or host-version mismatch cannot roll back unrelated user settings.
**Check live schema:**
```bash
openclaw config schema
```
`config.schema.lookup` fetches a single path-scoped node plus child summaries for drill-down tooling. Field `title`/`description` docs metadata carries through nested objects, wildcard (`*`), array-item (`[]`), and `anyOf`/`oneOf`/`allOf` branches. Runtime plugin and channel schemas merge in when the manifest registry is loaded.
### $include Directive (Split Config)
```json5
// ~/.openclaw/openclaw.json
{
gateway: { port: 18789 },
agents: { $include: "./agents.json5" },
broadcast: {
$include: ["./clients/a.json5", "./clients/b.json5"],
},
}
```
- **Single file**: replaces the containing object
- **Array of files**: deep-merged in order (later wins)
- **Sibling keys**: merged after includes (override included values)
- **Nested includes**: supported up to 10 levels deep
- **Relative paths**: resolved relative to the including file
---
## Configuration Reference — Full Schema
### Gateway
```json5
{
gateway: {
mode: "local", // "local" | "remote"
port: 18789, // single multiplexed port for WS + HTTP
bind: "loopback", // "auto" | "loopback" | "lan" (0.0.0.0) | "tailnet" | "custom"
auth: {
mode: "token", // "none" | "token" | "password" | "trusted-proxy"
// Note: "none" is intentionally not offered by onboarding prompts
token: "your-token", // or OPENCLAW_GATEWAY_TOKEN env var
password: "...", // or OPENCLAW_GATEWAY_PASSWORD
// trustedProxy: { userHeader: "x-forwarded-user" }, // for mode=trusted-proxy
// Note: trusted-proxy mode fails closed on loopback-source proxies; same-host loopback
// reverse proxies do NOT satisfy trusted-proxy auth
allowTailscale: true,
// Note: HTTP API endpoints do NOT use Tailscale header auth; they follow the
// gateway's normal HTTP auth mode instead
// Note: HTTP API endpoints do NOT use Tailscale header auth; they follow the
// gateway's normal HTTP auth mode instead
rateLimit: {
maxAttempts: 10,
windowMs: 60000,
lockoutMs: 300000,
exemptLoopback: true,
// Note: on async Tailscale Serve Control UI path, failed attempts for the same
// {scope, clientIp} are serialized before the failure write. Concurrent bad
// attempts can trip the limiter on the second request instead of both racing through.
// Browser-origin WS auth attempts are always throttled with loopback exemption
// disabled (defense-in-depth against browser-based localhost brute force).
},
},
tailscale: {
mode: "off", // "off" | "serve" | "funnel"
resetOnExit: false,
},
controlUi: {
enabled: true,
basePath: "/",
// embedSandbox: "scripts", // "strict" | "scripts" | "trusted"
// allowExternalEmbedUrls: false,
// allowedOrigins: ["https://control.example.com"],
// dangerouslyAllowHostHeaderOriginFallback: false,
// allowInsecureAuth: false,
// dangerouslyDisableDeviceAuth: false,
// allowExternalEmbedUrls: false,
// allowedOrigins: ["https://control.example.com"],
// dangerouslyAllowHostHeaderOriginFallback: false,
},
remote: {
url: "ws://gateway.tailnet:18789",
transport: "ssh", // "ssh" | "direct"
token: "your-token",
},
trustedProxies: ["10.0.0.1"],
allowRealIpFallback: false,
tools: {
deny: ["browser"], // Additional /tools/invoke HTTP denies
allow: ["gateway"], // Remove tools from the default HTTP deny list
},
push: {
apns: {
relay: {
baseUrl: "https://relay.example.com",
timeoutMs: 10000,
},
},
},
channelHealthCheckMinutes: 5,
channelStaleEventThresholdMinutes: 30,
channelMaxRestartsPerHour: 10,
},
}
```
**Bind mode notes:**
- Use bind mode values (`auto`, `loopback`, `lan`, `tailnet`, `custom`), NOT host aliases (`0.0.0.0`, `127.0.0.1`, `localhost`)
- **Docker note**: the default `loopback` bind listens on `127.0.0.1` inside the container; use `bind: "lan"` or `bind: "custom"` with `customBindHost: "0.0.0.0"` to listen on all interfaces
- `OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1` is a client-side process-environment break-glass override that allows plaintext `ws://` to trusted private-network IPs (no `openclaw.json` equivalent)
- Non-loopback binds require gateway auth
- If both `gateway.auth.token` and `gateway.auth.password` are configured, set `gateway.auth.mode` explicitly
### Skills
```json5
{
skills: {
allowBundled: ["gemini", "peekaboo"],
load: {
extraDirs: ["~/Projects/agent-scripts/skills"],
},
install: {
preferBrew: true,
nodeManager: "npm", // "npm" | "pnpm" | "yarn" | "bun"
},
entries: {
"image-lab": {
apiKey: { source: "env", provider: "default", id: "GEMINI_API_KEY" },
env: { GEMINI_API_KEY: "GEMINI_KEY_HERE" },
},
peekaboo: { enabled: true },
sag: { enabled: false },
},
},
}
```
- `allowBundled`: optional allowlist for bundled skills only (managed/workspace skills unaffected)
- `load.extraDirs`: extra shared skill roots (lowest precedence)
- `install.preferBrew`: when true, prefer Homebrew installers when `brew` is available
- `entries.<skillKey>.enabled: false`: disables a skill even if bundled/installed
- `entries.<skillKey>.apiKey`: convenience for skills declaring a primary env var
### Plugins
```json5
{
plugins: {
enabled: true,
allow: ["voice-call"],
deny: [],
load: {
paths: ["~/Projects/oss/voice-call-plugin"],
},
entries: {
"voice-call": {
enabled: true,
hooks: {
allowPromptInjection: false,
allowConversationAccess: false,
},
config: { provider: "twilio" },
subagent: {
allowModelOverride: false,
allowedModels: [],
},
},
},
installs: {}, // CLI-managed install metadata
slots: {
memory: null, // active memory plugin id or "none"
contextEngine: "legacy", // active context engine plugin id
},
},
}
```
- Loaded from `~/.openclaw/extensions`, `<workspace>/.openclaw/extensions`, plus `plugins.load.paths`
- **Discovery** accepts native OpenClaw plugins plus compatible Codex bundles and Claude bundles, including manifestless Claude default-layout bundles
- `allow`: optional allowlist (only listed plugins load). `deny` wins.
- **Config changes require a gateway restart.**
- `plugins.entries.<id>.hooks.allowPromptInjection`: when `false`, core blocks `before_prompt_build`
- `plugins.entries.<id>.hooks.allowConversationAccess`: enables trusted non-bundled plugins to read raw conversation content from specific typed hooks (`llm_input`, `llm_output`, `agent_end`)
- `plugins.entries.<id>.apiKey`: plugin-level API key convenience field
- `plugins.entries.<id>.env`: plugin-scoped env var map
- `plugins.entries.<id>.subagent.allowModelOverride`: trust this plugin to request per-run model overrides
- `plugins.entries.<id>.subagent.allowedModels`: allowlist of canonical `provider/model` targets
- `plugins.entries.firecrawl.config.webFetch`: Firecrawl web-fetch provider settings:
- `baseUrl`: default `https://api.firecrawl.dev`
- `onlyMainContent`: default `true`
- `maxAgeMs`: default `172800000` (2 days)
- `timeoutSeconds`: default `60`
- `apiKey` fallback chain: also checks `webSearch.apiKey`, legacy `tools.web.fetch.firecrawl.apiKey`, and `FIRECRAWL_API_KEY` env var
- `plugins.entries.xai.config.xSearch`: xAI X Search settings (includes `enabled` and `model`, e.g. `"grok-4-1-fast"`)
- `plugins.entries.memory-core.config.dreaming`: memory dreaming settings (see Dreaming docs)
- `plugins.installs`: CLI-managed install metadata (includes `source`, `spec`, `sourcePath`, `installPath`, `version`, `resolvedName`, `resolvedVersion`; prefer CLI commands over manual edits)
### Browser
```json5
{
browser: {
enabled: true,
evaluateEnabled: true,
defaultProfile: "user",
ssrfPolicy: {
// dangerouslyAllowPrivateNetwork: true, // opt in only for trusted private-network
// hostnameAllowlist: ["*.example.com"],
// allowedHostnames: ["localhost"],
},
profiles: {
openclaw: { cdpPort: 18800, color: "#FF4500" },
work: { cdpPort: 18801, color: "#0066CC" },
user: { driver: "existing-session", attachOnly: true, color: "#00AA00" },
brave: {
driver: "existing-session",
attachOnly: true,
userDataDir: "~/Library/Application Support/BraveSoftware/Brave-Browser",
color: "#FB542B",
},
remote: { cdpUrl: "http://10.0.0.42:9222", color: "#00AA00" },
},
color: "#FF4500",
// headless: false,
// noSandbox: false,
// extraArgs: [],
// executablePath: "/Applications/Brave Browser.app/...",
// attachOnly: false,
},
}
```
- `evaluateEnabled: false` disables `act:evaluate` and `wait --fn`
- `ssrfPolicy.dangerouslyAllowPrivateNetwork` is disabled when unset; `ssrfPolicy.allowPrivateNetwork: true` is a supported legacy alias for `dangerouslyAllowPrivateNetwork`
- In strict mode, use `ssrfPolicy.hostnameAllowlist` and `ssrfPolicy.allowedHostnames` for explicit exceptions
- In strict mode, remote CDP profile endpoints (`profiles.*.cdpUrl`) are subject to the same private-network blocking during reachability/discovery checks
- Remote profiles are attach-only (start/stop/reset disabled)
- `profiles.*.cdpUrl` accepts `http://`, `https://`, `ws://`, and `wss://`
- `existing-session` profiles use Chrome MCP instead of CDP. **Chrome MCP route limits**: snapshot/ref-driven actions instead of CSS-selector targeting, one-file upload hooks, no dialog timeout overrides, no `wait --load networkidle`, and no `responsebody`, PDF export, download interception, or batch actions
- `existing-session` profiles can set `userDataDir` to target specific browser profiles like Brave or Edge
- **Auto-detect order for browser executable**: default browser if Chromium-based → Chrome → Brave → Edge → Chromium → Chrome Canary
- Control service: loopback only (port derived from `gateway.port`, default `18791`)
- `extraArgs` appends extra launch flags to local Chromium startup
### UI
```json5
{
ui: {
seamColor: "#FF4500",
assistant: {
name: "OpenClaw",
avatar: "CB", // emoji, short text, image URL, or data URI
},
},
}
```
- `seamColor`: accent color for native app UI chrome (Talk Mode bubble tint, etc.)
- `assistant`: Control UI identity override; falls back to active agent identity
### Cron
```json5
{
cron: {
enabled: true,
store: "~/.openclaw/cron/jobs.json",
maxConcurrentRuns: 1,
retry: {
maxAttempts: 3,
backoffMs: [60000, 120000, 300000],
retryOn: ["rate_limit", "overloaded", "network", "server_error"],
},
webhookToken: "",
sessionRetention: "24h",
runLog: { maxBytes: "2mb", keepLines: 2000 },
},
}
```
### Hooks
```json5
{
hooks: {
enabled: true,
token: "shared-secret",
path: "/hooks",
defaultSessionKey: "hook:ingress",
allowRequestSessionKey: false,
allowedSessionKeyPrefixes: ["hook:"],
mappings: [
{
match: { path: "gmail" },
action: "agent",
agentId: "main",
deliver: true,
},
],
gmail: {
account: "",
model: null,
thinking: null,
},
internal: {
enabled: false,
entries: {},
load: { extraDirs: [] },
},
},
}
```
### Diagnostics and Logging
```json5
{
diagnostics: {
flags: ["telegram.http", "gateway.*"],
},
logging: {
level: "info",
consoleLevel: "warn",
redactSensitive: "tools",
file: null, // custom log file path
},
update: {
channel: "stable",
checkOnStart: true,
auto: {
enabled: false,
stableDelayHours: 6,
stableJitterHours: 12,
betaCheckIntervalHours: 1,
},
},
}
```
### Environment Variables
```json5
{
env: {
OPENROUTER_API_KEY: "sk-or-...",
vars: {
GROQ_API_KEY: "gsk-...",
},
shellEnv: {
enabled: false,
timeoutMs: 15000,
},
},
}
```
---
## Agent Configuration Reference
### Agent Defaults
```json5
{
agents: {
defaults: {
workspace: "~/.openclaw/workspace",
skipBootstrap: false,
bootstrapMaxChars: 12000,
bootstrapTotalMaxChars: 60000,
model: {
primary: "anthropic/claude-opus-4-6",
fallbacks: ["openai/gpt-5.4"],
},
models: {
"anthropic/claude-opus-4-6": { alias: "Opus" },
"openai/gpt-5.4": { alias: "GPT" },
},
imageMaxDimensionPx: 1200,
thinking: "low", // "off" | "low" | "medium" | "high" | "auto"
tools: {
profile: "coding", // "minimal" | "messaging" | "coding"
allow: [],
deny: [],
fs: { workspaceOnly: false },
exec: {
host: "auto", // "auto" | "gateway" | "node" | "sandbox"
security: "full",// "full" | "allowlist" | "deny"
ask: "off", // "off" | "on-miss" | "always"
node: null, // node id or name when host=node
},
elevated: { enabled: false },
agentToAgent: {
enabled: false,
allow: [],
},
},
sandbox: {
mode: "off", // "off" | "non-main" | "all"
scope: "agent", // "session" | "agent" | "shared"
docker: {
image: null,
user: null,
setupCommand: null,
dangerouslyAllowReservedContainerTargets: false,
dangerouslyAllowExternalBindSources: false,
dangerouslyAllowContainerNamespaceJoin: false,
},
},
heartbeat: {
every: "30m",
model: null, // optional model override for heartbeat runs (provider/model)
target: "none", // "none" | "last" | <channel-id>
to: null,
accountId: null,
session: null, // optional session key override ("main" or explicit session key)
directPolicy: "allow",
lightContext: false,
isolatedSession: false,
includeReasoning: false,
prompt: "...",
ackMaxChars: 300,
activeHours: null,
timeoutSeconds: null, // optional timeout for heartbeat runs
suppressToolErrorWarnings: false, // suppresses tool error warning payloads
},
subagents: {
model: null,
},
skills: null, // null = all skills; [] = no skills; ["skill1"] = allowlist
memorySearch: {
qmd: {
extraCollections: [],
},
},
},
list: [
{
id: "main",
default: true,
name: "Main",
workspace: "~/.openclaw/workspace",
agentDir: "~/.openclaw/agents/main/agent",
model: null, // override agents.defaults.model
// tools, sandbox, heartbeat, skills, etc. (per-agent overrides)
},
],
},
}
```
### Session Configuration
```json5
{
session: {
dmScope: "per-channel-peer", // "main" | "per-peer" | "per-channel-peer" | "per-account-channel-peer"
mainKey: "main",
threadBindings: {
enabled: true,
idleHours: 24,
maxAgeHours: 0,
},
reset: {
mode: "daily", // "daily" | "idle" | "none"
atHour: 4,
idleMinutes: 120,
},
maintenance: {
mode: "warn", // "warn" | "enforce"
pruneAfter: "30d",
maxEntries: 500,
},
identityLinks: [], // link same person across channels
},
}
```
### Multi-Agent Routing (Bindings)
```json5
{
agents: {
list: [
{ id: "home", default: true, workspace: "~/.openclaw/workspace-home" },
{ id: "work", workspace: "~/.openclaw/workspace-work" },
],
},
bindings: [
// Most specific first: peer match > parentPeer > guildId+roles > guildId > teamId > accountId > channel > default
{ agentId: "home", match: { channel: "whatsapp", accountId: "personal" } },
{ agentId: "work", match: { channel: "whatsapp", accountId: "biz" } },
// Per-peer override
{
agentId: "work",
match: {
channel: "whatsapp",
accountId: "personal",
peer: { kind: "group", id: "[email protected]" },
},
},
],
}
```
**Routing rule precedence (most-specific wins):**
1. `peer` match (exact DM/group/channel id)
2. `parentPeer` match (thread inheritance)
3. `guildId + roles` (Discord role routing)
4. `guildId` (Discord)
5. `teamId` (Slack)
6. `accountId` match for a channel
7. channel-level match (`accountId: "*"`)
8. fallback to default agent
**Notes:**
- A binding that omits `accountId` matches the default account only
- Use `accountId: "*"` for a channel-wide fallback across all accounts
- If multiple bindings match in the same tier, first in config order wins
- Multiple match fields = AND semantics (all fields required)
---
## Heartbeat Reference
Heartbeat runs **periodic agent turns** in the main session so the model can surface anything that needs attention without spamming you.
Key behaviors:
- Heartbeat turns do **NOT** create background task records
- Default interval: `30m` (or `1h` for Anthropic OAuth/token auth including Claude CLI reuse)
- `0m` disables heartbeats; also omits `HEARTBEAT.md` from bootstrap context
- Empty `HEARTBEAT.md` (only blank lines + markdown headers) → skipped as `reason=empty-heartbeat-file`
- No tasks due in task mode → skipped as `reason=no-tasks-due`
- All three visibility flags false → skipped as `reason=alerts-disabled`
- Active hours (`heartbeat.activeHours`) are checked in the configured timezone; outside the window, heartbeats are skipped
**Additional key fields:**
- `heartbeat.model`: optional model override (`provider/model`) for heartbeat runs
- `heartbeat.timeoutSeconds`: optional timeout for heartbeat runs
- `heartbeat.suppressToolErrorWarnings`: when true, suppresses tool error warning payloads during heartbeat runs
- `heartbeat.session`: optional session key override (`"main"` default, or explicit session key)
**Delivery behavior notes:**
- If the main queue is busy, the heartbeat is skipped and retried later
- Heartbeat-only replies do **NOT** keep the session alive; the last `updatedAt` is restored so idle expiry behaves normally
- If the resolved heartbeat target supports typing, OpenClaw shows typing while the heartbeat run is active
- Heartbeat can react to completed background tasks, but a heartbeat run itself does not create a task record
**Per-agent heartbeats:** If any `agents.list[]` entry includes a `heartbeat` block, **only those agents** run heartbeats. The per-agent block merges on top of `agents.defaults.heartbeat`.
**24/7 setup:** To run heartbeats all day, omit `activeHours` entirely (default) or set `activeHours: { start: "00:00", end: "24:00" }`. Do NOT set the same `start` and `end` time (e.g., `08:00` to `08:00`) — that is treated as a zero-width window and heartbeats are always skipped.
**Manual wake:** If multiple agents have `heartbeat` configured, a manual wake runs each of those agent heartbeats immediately.
**Delivery behavior notes:**
- If the main queue is busy, the heartbeat is skipped and retried later
- If `target` resolves to no external destination, the run still happens but no outbound message is sent
- Heartbeat-only replies do **NOT** keep the session alive; the last `updatedAt` is restored so idle expiry behaves normally
- If the resolved heartbeat target supports typing, OpenClaw shows typing while the heartbeat run is active
**Response contract:**
- If nothing needs attention → reply with `HEARTBEAT_OK`
- `HEARTBEAT_OK` at the start/end of reply: stripped; reply dropped if remaining content ≤ `ackMaxChars` (default: 300)
- `HEARTBEAT_OK` in the **middle** of a reply: not treated specially
- For alerts: do NOT include `HEARTBEAT_OK`
- Outside heartbeats, stray `HEARTBEAT_OK` at the start/end of a message is stripped and logged; a message that is only `HEARTBEAT_OK` is dropped
### HEARTBEAT.md
Optional file in workspace that the agent reads each heartbeat. Keep it tiny to avoid token burn.
**`tasks:` block support:**
```markdown
tasks:
- name: inbox-triage
interval: 30m
prompt: "Check for urgent unread emails and flag anything time sensitive."
- name: calendar-scan
interval: 2h
prompt: "Check for upcoming meetings that need prep or follow-up."
# Additional instructions
- Keep alerts short.
- If nothing needs attention after all due tasks, reply HEARTBEAT_OK.
```
Behavior:
- Only **due** tasks are included in the heartbeat prompt for that tick
- If no tasks are due, the heartbeat is skipped entirely (`reason=no-tasks-due`)
- Task last-run timestamps stored in session state (`heartbeatTaskState`)
- Timestamps only advanced after heartbeat run completes normal reply path
### Manual Wake
```bash
openclaw system event --text "Check for urgent follow-ups" --mode now
```
### Visibility Controls
```yaml
channels:
defaults:
heartbeat:
showOk: false # Hide HEARTBEAT_OK (default)
showAlerts: true # Show alert messages (default)
useIndicator: true # Emit indicator events (default)
telegram:
heartbeat:
showOk: true # Show OK acknowledgments on Telegram
whatsapp:
accounts:
work:
heartbeat:
showAlerts: false # Suppress alert delivery for this account
```
Precedence: per-account → per-channel → channel defaults → built-in defaults.
If **all three** are false, OpenClaw skips the heartbeat run entirely (no model call).
---
## Agent Workspace Reference
The workspace is the agent's home directory for file tools and workspace context. Default: `~/.openclaw/workspace`
**Important:** The workspace is the **default cwd**, not a hard sandbox. Tools resolve relative paths against the workspace, but absolute paths can still reach elsewhere on the host unless sandboxing is enabled. When sandboxing is enabled and `workspaceAccess` is not `"rw"`, tools operate inside a sandbox workspace under `~/.openclaw/sandboxes`, not your host workspace.
### Workspace File Map
| File | Purpose | Loaded |
|---|---|---|
| `AGENTS.md` | Operating instructions, rules, priorities, "how to behave" | Every session |
| `SOUL.md` | Persona, tone, and boundaries | Every session |
| `USER.md` | Who the user is and how to address them | Every session |
| `IDENTITY.md` | The agent's name, vibe, and emoji | Every session |
| `TOOLS.md` | Notes about local tools and conventions (guidance only, not tool control) | Every session |
| `HEARTBEAT.md` | Tiny checklist for heartbeat runs | Heartbeat runs |
| `BOOT.md` | Startup checklist run automatically on gateway restart (hooks required; only runs when hooks are required) | Gateway start |
| `BOOT.md` | Startup checklist run automatically on gateway restart (when internal hooks enabled) | Gateway start |
| `BOOTSTRAP.md` | One-time first-run ritual (delete after completing) | Once only |
| `memory/YYYY-MM-DD.md` | Daily memory log (one file per day) | Manually loaded |
| `MEMORY.md` | Curated long-term memory | Main/private session only |
| `skills/` | Workspace-specific skills (highest precedence) | Skills resolution |
| `canvas/` | Canvas UI files for node displays | Canvas tool |
**Bootstrap file size limits:**
- `agents.defaults.bootstrapMaxChars` (default: 12000) — per-file limit
- `agents.defaults.bootstrapTotalMaxChars` (default: 60000) — total limit
- `openclaw setup` can recreate missing defaults without overwriting existing files
- Sandbox seed copies only accept regular in-workspace files; symlink/hardlink aliases that resolve outside the source workspace are ignored
### What is NOT in the Workspace
| Item | Location |
|---|---|
| Config | `~/.openclaw/openclaw.json` |
| Auth profiles (model auth: OAuth + API keys) | `~/.openclaw/agents/<agentId>/agent/auth-profiles.json` |
| Channel/provider credentials | `~/.openclaw/credentials/` |
| Session transcripts + metadata | `~/.openclaw/agents/<agentId>/sessions/` |
| Managed skills | `~/.openclaw/skills/` |
### Git Backup (Recommended)
```bash
cd ~/.openclaw/workspace
git init
git add AGENTS.md SOUL.md TOOLS.md IDENTITY.md USER.md HEARTBEAT.md memory/
git commit -m "Add agent workspace"
# Add private remote (GitHub CLI)
gh repo create openclaw-workspace --private --source . --remote origin --push
# Ongoing updates
git add . && git commit -m "Update memory" && git push
```
**Suggested `.gitignore`:**
```gitignore
.DS_Store
.env
**/*.key
**/*.pem
**/secrets*
```
---
## Session Management Reference
Sessions organize conversations. Each message is routed to a session based on where it came from.
### Message Routing
| Source | Behavior |
|---|---|
| Direct messages | Shared session by default |
| Group chats | Isolated per group |
| Rooms/channels | Isolated per room |
| Cron jobs | Fresh session per run |
| Webhooks | Isolated per hook |
### DM Isolation
```json5
{
session: {
dmScope: "per-channel-peer", // recommended for multi-user
},
}
```
Options:
- `main` (default): all DMs share one session
- `per-peer`: isolate by sender (across channels)
- `per-channel-peer`: isolate by channel + sender (recommended)
- `per-account-channel-peer`: isolate by account + channel + sender
**Thread bindings:** `session.threadBindings` controls thread-bound session routing (Discord supports `/focus`, `/unfocus`, `/agents`, `/session idle`, and `/session max-age`).
**Warning:** If multiple people can message your agent, enable DM isolation. Without it, all users share the same conversation context.
### Session Lifecycle
- **Daily reset** (default): new session at 4:00 AM local time on the gateway host
- **Idle reset** (optional): new session after a period of inactivity (`session.reset.idleMinutes`)
- **Manual reset**: type `/new` or `/reset` in chat; `/new <model>` also switches the model
When both daily and idle resets are configured, whichever expires first wins.
### Session Storage
```
~/.openclaw/agents/<agentId>/sessions/sessions.json # session index
~/.openclaw/agents/<agentId>/sessions/<sessionId>.jsonl # transcripts
```
### Session Maintenance
```json5
{
session: {
maintenance: {
mode: "enforce",
pruneAfter: "30d",
maxEntries: 500,
},
},
}
```
Preview with `openclaw sessions cleanup --dry-run`.
### Inspecting Sessions
```bash
openclaw status # session store path and recent activity
openclaw sessions --json # all sessions
openclaw sessions --json --active 60 # active in last 60 minutes
/status # context usage, model, and toggles (in chat)
/context list # what is in the system prompt
```
---
## Authentication Reference
### Model Provider Authentication
OpenClaw supports OAuth and API keys for model providers.
**Recommended setup (API key):**
```bash
export ANTHROPIC_API_KEY="..."
# Or for daemon persistence:
cat >> ~/.openclaw/.env <<'EOF'
ANTHROPIC_API_KEY=...
EOF
openclaw models status
```
**Anthropic Claude CLI reuse:**
```bash
claude auth login
claude auth status --text
openclaw models auth login --provider anthropic --method cli --set-default
```
**Manual token entry:**
```bash
openclaw models auth paste-token --provider openrouter
```
**Check auth status:**
```bash
openclaw models status
openclaw models status --probe # live auth probes
openclaw models status --check # automation-friendly (exit 1=expired/missing, 2=expiring)
```
### API Key Rotation Behavior
Priority order for key rotation on rate limits:
1. `OPENCLAW_LIVE_<PROVIDER>_KEY` (single override)
2. `<PROVIDER>_API_KEYS`
3. `<PROVIDER>_API_KEY`
4. `<PROVIDER>_API_KEY_*`
OpenClaw retries with the next key only for rate-limit errors (`429`, `rate_limit`, `quota`, `resource exhausted`, etc.). Non-rate-limit errors are not retried.
SecretRef support:
- `api_key` credentials can use `keyRef: { source, provider, id }`
- `token` credentials can use `tokenRef: { source, provider, id }`
- OAuth-mode profiles do NOT support SecretRef credentials
### Per-Session Auth
Use `/model <alias-or-id>@<profileId>` to pin a specific provider credential for the current session.
```bash
/model anthropic/claude-opus-4-6@anthropic:work
/model list # compact picker
/model status # full view with candidates + next auth profile
```
### Per-Agent Auth Order
```bash
openclaw models auth order get --provider anthropic
openclaw models auth order set --provider anthropic anthropic:default
openclaw models auth order clear --provider anthropic
```
Use `--agent <id>` to target a specific agent.
### Auth Profiles Storage
Auth profiles live at: `~/.openclaw/agents/<agentId>/agent/auth-profiles.json`
SecretRef support:
- `api_key` credentials can use `keyRef: { source, provider, id }`
- `token` credentials can use `tokenRef: { source, provider, id }`
- OAuth-mode profiles do NOT support SecretRef credentials
---
## Gateway Protocol Reference
The Gateway WebSocket protocol is the **single control plane + node transport** for OpenClaw.
### Transport
- WebSocket, text frames with JSON payloads
- First frame **must** be a `connect` request
- Pre-connect frames capped at 64 KiB
- After handshake, follow `hello-ok.policy.maxPayload` and `hello-ok.policy.maxBufferedBytes` limits
- With diagnostics enabled, oversized inbound frames and slow outbound buffers emit `payload.large` events before the gateway closes or drops the affected frame (keeps sizes/limits/surfaces/safe reason codes; does not keep message body, attachment contents, tokens, or secrets)
### Handshake
**Gateway → Client (pre-connect challenge):**
```json
{
"type": "event",
"event": "connect.challenge",
"payload": { "nonce": "…", "ts": 1737264000000 }
}
```
**Client → Gateway (connect request):**
```json
{
"type": "req",
"id": "…",
"method": "connect",
"params": {
"minProtocol": 3,
"maxProtocol": 3,
"client": { "id": "cli", "version": "1.2.3", "platform": "macos", "mode": "operator" },
"role": "operator",
"scopes": ["operator.read", "operator.write"],
"caps": [],
"commands": [],
"permissions": {},
"auth": { "token": "…" },
"locale": "en-US",
"userAgent": "openclaw-cli/1.2.3",
"device": {
"id": "device_fingerprint",
"publicKey": "…",
"signature": "…",
"signedAt": 1737264000000,
"nonce": "…"
}
}
}
```
**Gateway → Client (hello-ok):**
```json
{
"type": "res",
"id": "…",
"ok": true,
"payload": {
"type": "hello-ok",
"protocol": 3,
"server": { "version": "…", "connId": "…" },
"features": { "methods": ["…"], "events": ["…"] },
"snapshot": {},
"policy": {
"maxPayload": 26214400,
"maxBufferedBytes": 52428800,
"tickIntervalMs": 15000
},
"auth": {
"deviceToken": "…",
"role": "operator",
"scopes": ["operator.read", "operator.write"]
}
}
}
```
`server`, `features`, `snapshot`, and `policy` are all required by the schema. `canvasHostUrl` is optional. `auth` reports the negotiated role/scopes when available. When no device token is issued, `hello-ok.auth` can still report just the negotiated permissions.
During trusted bootstrap handoff, `hello-ok.auth` may also include additional bounded role entries in `deviceTokens`. Bootstrap scope checks stay role-prefixed: operator entries only satisfy operator requests.
**Node connect example:**
```json
{
"method": "connect",
"params": {
"role": "node",
"scopes": [],
"caps": ["camera", "canvas", "screen", "location", "voice"],
"commands": ["camera.snap", "canvas.navigate", "screen.record", "location.get"],
"permissions": { "camera.capture": true, "screen.record": false }
}
}
```
### Framing
- **Request**: `{type:"req", id, method, params}`
- **Response**: `{type:"res", id, ok, payload|error}`
- **Event**: `{type:"event", event, payload, seq?, stateVersion?}`
Side-effecting methods require **idempotency keys**.
### Roles + Scopes
**Roles:**
- `operator` = control plane client (CLI/UI/automation)
- `node` = capability host (camera/screen/canvas/system.run)
**Operator scopes:**
- `operator.read`
- `operator.write`
- `operator.admin`
- `operator.approvals`
- `operator.pairing`
- `operator.talk.secrets`
Plugin-registered gateway RPC methods may request their own operator scope, but reserved core admin prefixes (`config.*`, `exec.approvals.*`, `wizard.*`, `update.*`) always resolve to `operator.admin`.
Method scope is only the first gate. Some slash commands reached through `chat.send` apply stricter command-level checks on top (e.g. persistent `/config set` and `/config unset` writes require `operator.admin`).
**Node approval scope checks (`node.pair.approve`):**
- Commandless requests: `operator.pairing`
- Requests with non-exec node commands: `operator.pairing` + `operator.write`
- Requests with `system.run`, `system.run.prepare`, or `system.which`: `operator.pairing` + `operator.admin`
**Presence:**
- `system-presence` returns entries keyed by device identity
- Presence entries include `deviceId`, `roles`, and `scopes` so UIs can show a single row per device even when it connects as both **operator** and **node**
### Broadcast Event Scoping
- **Chat, agent, tool-result frames**: require at least `operator.read`
- **Plugin-defined `plugin.*` broadcasts**: gated to `operator.write` or `operator.admin`
- **Status and transport events**: unrestricted
- **Unknown broadcast event families**: scope-gated by default (fail-closed) unless a registered handler explicitly relaxes them
Each client connection keeps its own per-client sequence number so broadcasts preserve monotonic ordering on that socket even when different clients see different scope-filtered subsets.
### Common RPC Method Families
**System and identity:**
- `health` — cached or fresh gateway health snapshot
- `diagnostics.stability` — recent bounded diagnostic stability recorder (operational metadata; no chat text, webhook bodies, or secrets)
- `status` — gateway summary; sensitive fields included only for admin-scoped operator clients
- `gateway.identity.get` — gateway device identity
- `system-presence` — current presence snapshot
- `system-event` — appends a system event
- `last-heartbeat` — latest persisted heartbeat event
- `set-heartbeats` — toggles heartbeat processing on the gateway
**Models and usage:**
- `models.list` — runtime-allowed model catalog
- `usage.status` — provider usage windows/remaining quota
- `usage.cost` — aggregated cost usage summaries for a date range
- `doctor.memory.status` — vector-memory / embedding readiness
- `sessions.usage` — per-session usage summaries
- `sessions.usage.timeseries` — timeseries usage for one session
- `sessions.usage.logs` — usage log entries for one session
**Channels and login:**
- `channels.status` — channel/plugin status summaries
- `channels.logout` — logs out a specific channel/account
- `web.login.start` / `web.login.wait` — QR/web login flows
- `push.test` — sends a test APNs push
- `voicewake.get` / `voicewake.set` — wake-word triggers
**Messaging and logs:**
- `send` — direct outbound-delivery RPC for channel/account/thread-targeted sends
- `logs.tail` — configured gateway file-log tail with cursor/limit and max-byte controls
**Talk and TTS:**
- `talk.config` — effective Talk config payload
- `talk.mode` — sets/broadcasts current Talk mode state
- `talk.speak` — synthesizes speech
- `tts.status`, `tts.providers`, `tts.enable`, `tts.disable`, `tts.setProvider`, `tts.convert`
**Secrets, config, update, and wizard:**
- `secrets.reload` — re-resolves active SecretRefs
- `config.get` — current config snapshot and hash
- `config.set` — writes a validated config payload
- `config.patch` — merges a partial config update
- `config.apply` — validates + replaces the full config payload
- `secrets.resolve` — resolves command-target secret assignments for a specific command/target set
- `config.schema` — live config schema payload (includes field `title`/`description`, plugin + channel schemas, `uiHints`)
- `config.schema.lookup` — path-scoped schema node for drill-down tooling (normalized path, shallow schema, matched hint + `hintPath`, immediate child summaries)
- `update.run` — runs the gateway update flow
- `wizard.start`, `wizard.next`, `wizard.status`, `wizard.cancel` — onboarding wizard over WS RPC
**Agent and workspace helpers:**
- `agents.list`, `agents.create`, `agents.update`, `agents.delete`
- `agents.files.list`, `agents.files.get`, `agents.files.set` — bootstrap workspace files
- `agent.identity.get` — effective assistant identity
- `agent.wait` — waits for a run to finish
**Session control:**
- `sessions.list`, `sessions.subscribe`, `sessions.unsubscribe`
- `sessions.preview` — bounded transcript previews
- `sessions.create`, `sessions.send`, `sessions.steer`, `sessions.abort`, `sessions.patch`
- `sessions.reset`, `sessions.delete`, `sessions.compact`, `sessions.get`
- `chat.history`, `chat.send`, `chat.abort`, `chat.inject` — for chat execution
**Device pairing:**
- `device.pair.list`, `device.pair.approve`, `device.pair.reject`, `device.pair.remove`
- `device.token.rotate`, `device.token.revoke`
**Node pairing and invoke:**
- `node.pair.request`, `node.pair.list`, `node.pair.approve`, `node.pair.reject`, `node.pair.verify`
- `node.list`, `node.describe`, `node.rename`, `node.invoke`, `node.invoke.result`
- `node.pending.pull`, `node.pending.ack`, `node.pending.enqueue`, `node.pending.drain`
**Approval families:**
- `exec.approval.request`, `exec.approval.get`, `exec.approval.list`, `exec.approval.resolve`, `exec.approval.waitDecision`
- `exec.approvals.get`, `exec.approvals.set`, `exec.approvals.node.get`, `exec.approvals.node.set`
- `plugin.approval.request`, `plugin.approval.list`, `plugin.approval.waitDecision`, `plugin.approval.resolve`
**Automation, skills, and tools:**
- `wake` — schedules an immediate or next-heartbeat wake
- `cron.list`, `cron.status`, `cron.add`, `cron.update`, `cron.remove`, `cron.run`, `cron.runs`
- `commands.list`, `skills.*`, `tools.catalog`, `tools.effective`
### Common Event Families
- `chat` — UI chat updates
- `session.message` / `session.tool` — transcript/event-stream updates
- `sessions.changed` — session index or metadata changed
- `presence` — system presence snapshot updates
- `tick` — periodic keepalive / liveness event
- `health` — gateway health snapshot update
- `heartbeat` — heartbeat event stream update
- `cron` — cron run/job change event
- `shutdown` — gateway shutdown notification
- `node.pair.requested` / `node.pair.resolved` — node pairing lifecycle
- `node.invoke.request` — node invoke request broadcast
- `device.pair.requested` / `device.pair.resolved` — paired-device lifecycle
- `voicewake.changed` — wake-word trigger config changed
- `exec.approval.requested` / `exec.approval.resolved` — exec approval lifecycle
- `plugin.approval.requested` / `plugin.approval.resolved` — plugin approval lifecycle
---
## RPC Adapters Reference
OpenClaw integrates external CLIs via JSON-RPC using two patterns.
### Pattern A: HTTP Daemon (signal-cli)
- `signal-cli` runs as a daemon with JSON-RPC over HTTP
- Event stream is SSE (`/api/v1/events`)
- Health probe: `/api/v1/check`
- OpenClaw owns lifecycle when `channels.signal.autoStart=true`
See [Signal](/channels/signal) for setup and endpoints.
### Pattern B: stdio Child Process (legacy: imsg)
> **Note**: For new iMessage setups, use [BlueBubbles](/channels/bluebubbles) instead.
- OpenClaw spawns `imsg rpc` as a child process
- JSON-RPC is line-delimited over stdin/stdout (one JSON object per line)
- No TCP port, no daemon required
Core methods:
- `watch.subscribe` → notifications (`method: "message"`)
- `watch.unsubscribe`
- `send`
- `chats.list` (probe/diagnostics)
**Adapter guidelines:**
- Gateway owns the process (start/stop tied to provider lifecycle)
- Keep RPC clients resilient: timeouts, restart on exit
- Prefer stable IDs (e.g., `chat_id`) over display strings
---
## CLI Reference
### Command Index
```
openclaw [--dev] [--profile <name>] <command>
```
| Area | Commands |
|---|---|
| Setup and onboarding | `setup` · `onboard` · `configure` · `config` · `completion` · `doctor` · `dashboard` |
| Reset and uninstall | `backup` · `reset` · `uninstall` · `update` |
| Messaging and agents | `message` · `agent` · `agents` · `acp` · `mcp` |
| Health and sessions | `status` · `health` · `sessions` |
| Gateway and logs | `gateway` · `logs` · `system` |
| Models and inference | `models` · `infer` · `memory` · `wiki` |
| Network and nodes | `directory` · `nodes` · `devices` · `node` |
| Runtime and sandbox | `approvals` · `sandbox` · `tui` · `browser` |
| Automation | `cron` · `tasks` · `hooks` · `webhooks` · `flows` |
| Discovery and docs | `dns` · `docs` |
| Pairing and channels | `pairing` · `qr` · `channels` · `voicecall` |
| Security and plugins | `security` · `secrets` · `skills` · `plugins` · `proxy` |
| Legacy aliases | `daemon` (gateway service) · `clawbot` (namespace) |
### Global Flags
| Flag | Purpose |
|---|---|
| `--dev` | Isolate state under `~/.openclaw-dev` and shift default ports |
| `--profile <name>` | Isolate state under `~/.openclaw-<name>` |
| `--container <name>` | Target a named container for execution |
| `--no-color` | Disable ANSI colors (`NO_COLOR=1` also respected) |
| `--update` | Shorthand for `openclaw update` (source installs only) |
| `-V`, `--version`, `-v` | Print version and exit |
### Output Modes
- ANSI colors and progress indicators render only in TTY sessions
- `--json` (and `--plain` where supported) disables styling for clean output
- OSC-8 hyperlinks render as clickable links where supported
### Core Commands
```bash
# Gateway management
openclaw gateway status
openclaw gateway start / stop / restart
openclaw gateway install / uninstall
openclaw gateway probe
openclaw gateway discover
openclaw gateway call <method> --params "{}"
# Setup & onboarding
openclaw onboard [--install-daemon] [--non-interactive] [--json]
openclaw configure
openclaw config get <path>
openclaw config set <path> <value>
openclaw config unset <path>
openclaw config schema
openclaw config validate
openclaw doctor [--fix] [--yes] [--non-interactive]
# Status & health
openclaw status [--all] [--deep] [--json]
openclaw health [--verbose] [--json]
# Models and auth
openclaw models list [--all] [--provider <name>]
openclaw models status [--probe] [--check] [--json]
openclaw models set <model>
openclaw models auth login --provider <name> [--method <method>]
openclaw models auth order get/set/clear --provider <name>
# Channels
openclaw channels list
openclaw channels status [--probe]
openclaw channels login [--channel <name>] [--account <id>]
openclaw channels logout --channel <name>
# Sessions
openclaw sessions --json [--active <minutes>]
openclaw sessions cleanup --dry-run
# Tasks
openclaw tasks list [--runtime <type>] [--status <status>] [--json]
openclaw tasks show <lookup>
openclaw tasks cancel <lookup>
openclaw tasks notify <lookup> <policy>
openclaw tasks audit [--json]
openclaw tasks maintenance [--apply] [--json]
openclaw tasks flow list/show/cancel
# Cron
openclaw cron list
openclaw cron add --name "..." --cron "0 7 * * *" --session isolated --message "..."
openclaw cron show <jobId>
openclaw cron edit <jobId> [options]
openclaw cron run <jobId> [--due]
openclaw cron runs --id <jobId> --limit 50
openclaw cron remove <jobId>
openclaw cron status
# Hooks
openclaw hooks list [--eligible] [--verbose] [--json]
openclaw hooks info <hook-name>
openclaw hooks check
openclaw hooks enable/disable <hook-name>
# Security
openclaw security audit [--deep] [--fix] [--json]
# Nodes
openclaw nodes status
openclaw nodes describe --node <id>
openclaw nodes invoke --node <id> --command <cmd> --params '{}'
openclaw nodes canvas snapshot/present/hide/navigate/eval
openclaw nodes camera list/snap/clip
openclaw nodes screen record
# Devices
openclaw devices list
openclaw devices approve/reject <requestId>
openclaw devices remove/revoke <id>
# Skills
openclaw skills search "keyword"
openclaw skills install <slug> [--version <v>] [--force]
openclaw skills update [--all]
openclaw skills list [--eligible]
openclaw skills info <slug>
openclaw skills check
# Plugins
openclaw plugins list [--json]
openclaw plugins install <spec>
openclaw plugins uninstall <id>
openclaw plugins update [<id>]
openclaw plugins enable/disable <id>
openclaw plugins doctor
# Update
openclaw update [--channel beta] [--tag main] [--dry-run]
# Uninstall
openclaw uninstall [--all] [--yes] [--non-interactive]
```
### Chat Slash Commands
Highlights:
- `/status` — quick diagnostics
- `/trace` — session-scoped plugin trace/debug lines
- `/config` — persisted config changes
- `/debug` — runtime-only config overrides (memory, not disk; requires `commands.debug: true`)
- `/model` — switch model for the session
- `/new` — start a new session (optionally with a model)
- `/reset` — reset the current session
- `/reasoning` — toggle reasoning display
- `/verbose` — toggle verbose mode
- `/context list` — show what's in the system prompt
- `/subagents` — show subagent status
- `/tasks` — show background tasks linked to this session
- `/agents` — show agent/binding state
- `/focus <target>` — bind current thread to a target (Discord)
- `/unfocus` — detach thread binding
- `/session idle <duration|off>` — set session idle timeout
- `/session max-age <duration|off>` — set session max age
---
## Onboarding Reference
Full reference for `openclaw onboard`.
### Onboarding Flow (Local Mode)
1. **Existing config detection**: Keep / Modify / Reset
- `--reset` defaults to `config+creds+sessions`; use `--reset-scope full` to include workspace
- Reset uses `trash` (never `rm`)
- If the config is invalid or contains legacy keys, the wizard stops and asks you to run `openclaw doctor` before continuing
2. **Model/Auth**: choose provider and auth flow (API key, OAuth, Claude CLI)
- API key storage: plaintext (default) or `--secret-input-mode ref` for env-backed refs
3. **Workspace**: default `~/.openclaw/workspace`; seeds bootstrap files. Full workspace layout + backup guide: [Agent workspace](/concepts/agent-workspace)
4. **Gateway**: port, bind address, auth mode, Tailscale exposure
- Auth recommendation: keep **Token** even for loopback
- In token mode, interactive setup offers: generate/store plaintext token (default) or use SecretRef (opt-in)
- Quickstart reuses existing `gateway.auth.token` SecretRefs across `env`, `file`, and `exec` providers
- If that SecretRef is configured but cannot be resolved, onboarding fails early with a clear fix message
- Non-interactive token SecretRef path: `--gateway-token-ref-env <ENV_VAR>` (cannot be combined with `--gateway-token`)
- If both `gateway.auth.token` and `gateway.auth.password` are configured and `gateway.auth.mode` is unset, daemon install is blocked until mode is set explicitly
5. **Channels**: Telegram, Discord, WhatsApp, Signal, BlueBubbles, etc.
6. **Web search**: Brave, DuckDuckGo, Exa, Firecrawl, Gemini, Grok, Kimi, MiniMax Search, Ollama Web Search, Perplexity, SearXNG, or Tavily
7. **Daemon**: LaunchAgent (macOS) / systemd user unit (Linux/WSL2) / Scheduled Task (Windows native)
- **Runtime selection:** Node (recommended; required for WhatsApp/Telegram). Bun is **not recommended**
- Linux onboarding attempts to enable lingering via `loginctl enable-linger <user>` (may prompt for sudo)
- If token auth requires a token and `gateway.auth.token` is SecretRef-managed, daemon install validates it but does not persist resolved plaintext token values into supervisor service environment metadata
8. **Health check**: starts Gateway and runs `openclaw health`
9. **Skills**: installs recommended skills
### Non-Interactive Mode
```bash
openclaw onboard --non-interactive \
--mode local \
--auth-choice apiKey \
--anthropic-api-key "$ANTHROPIC_API_KEY" \
--gateway-port 18789 \
--gateway-bind loopback \
--install-daemon \
--daemon-runtime node \
--skip-skills
```
**Gateway token SecretRef:**
```bash
export OPENCLAW_GATEWAY_TOKEN="your-token"
openclaw onboard --non-interactive \
--mode local \
--auth-choice skip \
--gateway-auth token \
--gateway-token-ref-env OPENCLAW_GATEWAY_TOKEN
```
**Add another agent (non-interactive):**
```bash
openclaw agents add work \
--workspace ~/.openclaw/workspace-work \
--model openai/gpt-5.4 \
--bind whatsapp:biz \
--non-interactive \
--json
```
### Gateway Wizard RPC
The Gateway exposes the onboarding flow over RPC:
- `wizard.start`, `wizard.next`, `wizard.cancel`, `wizard.status`
### Supported Providers in Onboarding
| Provider | Auth Method | Notes |
|---|---|---|
| Anthropic | API key / Claude CLI / setup-token | API key most predictable for servers; setup-token still available though OpenClaw now prefers Claude CLI reuse |
| OpenAI | API key / OAuth / Codex subscription (OAuth or device pairing) | Codex sets model to `openai-codex/gpt-5.5`; API key sets to `openai/gpt-5.4` when unset or OpenAI-family |
| xAI (Grok) | API key (`XAI_API_KEY`) | |
| OpenCode | API key (`OPENCODE_API_KEY`) | Get at https://opencode.ai/auth |
| Ollama | API key / local | Cloud+Local / Cloud only / Local only; auto-pull selected local model |
| Vercel AI Gateway | API key (`AI_GATEWAY_API_KEY`) | Multi-model proxy |
| Cloudflare AI Gateway | Account ID + Gateway ID + API key | |
| MiniMax | Auto-configured | Default: `MiniMax-M2.7`; API-key uses `minimax/...`, OAuth uses `minimax-portal/...` |
| StepFun | Auto-configured | Standard or Step Plan |
| Synthetic | API key (`SYNTHETIC_API_KEY`) | Anthropic-compatible |
| Moonshot (Kimi K2) | Auto-configured | |
| Kimi Coding | Auto-configured | |
| StepFun | Auto-configured | Standard or Step Plan, China or global endpoints |
### What the Wizard Writes
Key fields in `~/.openclaw/openclaw.json`:
- `agents.defaults.workspace`
- `agents.defaults.model` / `models.providers`
- `tools.profile` (defaults to `"coding"` when unset on local onboarding; existing explicit values preserved)
- `gateway.*` (mode, bind, auth, tailscale)
- `session.dmScope`
- Channel tokens (`channels.telegram.botToken`, `channels.discord.token`, etc.)
- Channel allowlists when you opt in
- `skills.install.nodeManager` (accepts `npm`, `pnpm`, `yarn`, or `bun`)
- `wizard.lastRunAt`, `wizard.lastRunVersion`, `wizard.lastRunCommit`, `wizard.lastRunCommand`, `wizard.lastRunMode`
**Add another agent (non-interactive):**
```bash
openclaw agents add work \
--workspace ~/.openclaw/workspace-work \
--model openai/gpt-5.4 \
--bind whatsapp:biz \
--non-interactive \
--json
```
`openclaw agents add` writes `agents.list[]` and optional `bindings`.
**Storage locations:**
- WhatsApp credentials: `~/.openclaw/credentials/whatsapp/<accountId>/`
- Sessions: `~/.openclaw/agents/<agentId>/sessions/`
- Auth profiles: `~/.openclaw/agents/<agentId>/agent/auth-profiles.json`
---
## File Locations Quick Reference
| Item | Location |
|---|---|
| Main config | `~/.openclaw/openclaw.json` |
| State directory | `~/.openclaw/` |
| Agent directories | `~/.openclaw/agents/<agentId>/` |
| Auth profiles | `~/.openclaw/agents/<agentId>/agent/auth-profiles.json` |
| Sessions index | `~/.openclaw/agents/<agentId>/sessions/sessions.json` |
| Session transcripts | `~/.openclaw/agents/<agentId>/sessions/<sessionId>.jsonl` |
| WhatsApp credentials | `~/.openclaw/credentials/whatsapp/<accountId>/creds.json` |
| Channel allowlists | `~/.openclaw/credentials/<channel>-allowFrom.json` |
| Task records | `$OPENCLAW_STATE_DIR/tasks/runs.sqlite` |
| Cron jobs | `~/.openclaw/cron/jobs.json` |
| Cron runtime state | `~/.openclaw/cron/jobs-state.json` |
| Managed hooks | `~/.openclaw/hooks/` |
| Managed skills | `~/.openclaw/skills/` |
| Exec approvals (headless node) | `~/.openclaw/exec-approvals.json` |
| Node host info | `~/.openclaw/node.json` |
| Secrets payload | `~/.openclaw/secrets.json` |
| Gateway env | `~/.openclaw/.env` |
| Media | `~/.openclaw/media/` |
| Logs | `/tmp/openclaw/openclaw-YYYY-MM-DD.log` |
| Plugin extensions | `~/.openclaw/extensions/` |
| Workspace (default) | `~/.openclaw/workspace/` |
| Sandbox workspaces | `~/.openclaw/sandboxes/` |
---
## Environment Variables Quick Reference
| Variable | Purpose |
|---|---|
| `OPENCLAW_HOME` | Override home directory for all internal path resolution |
| `OPENCLAW_STATE_DIR` | Override state directory (default `~/.openclaw`) |
| `OPENCLAW_CONFIG_PATH` | Override config file path |
| `OPENCLAW_GATEWAY_TOKEN` | Gateway shared secret token |
| `OPENCLAW_GATEWAY_PASSWORD` | Gateway shared secret password |
| `OPENCLAW_GATEWAY_PORT` | Gateway port (default 18789) |
| `OPENCLAW_GATEWAY_BIND` | Gateway bind mode |
| `OPENCLAW_PROFILE` | Active profile name |
| `OPENCLAW_LOG_LEVEL` | Override log level (e.g., `debug`, `trace`) |
| `OPENCLAW_DIAGNOSTICS` | Diagnostics flags (comma-separated) |
| `OPENCLAW_SKIP_CRON` | Skip cron scheduler |
| `OPENCLAW_SKIP_CHANNELS` | Skip channel providers |
| `OPENCLAW_SKIP_GMAIL_WATCHER` | Skip Gmail watcher |
| `OPENCLAW_NIX_MODE` | Enable Nix deterministic mode |
| `OPENCLAW_NO_RESPAWN` | Avoid self-respawn startup overhead |
| `OPENCLAW_LOAD_SHELL_ENV` | Enable login-shell env import |
| `OPENCLAW_SHELL_ENV_TIMEOUT_MS` | Shell env import timeout |
| `OPENCLAW_THEME` | TUI palette (`light` or `dark`) |
| `OPENCLAW_RAW_STREAM` | Enable raw stream logging |
| `OPENCLAW_RAW_STREAM_PATH` | Custom raw stream log path |
| `OPENCLAW_DEBUG_TIMING` | Enable CLI debug timing (`1` or `json`) |
| `OPENCLAW_CHILD_OOM_SCORE_ADJ` | Disable Linux child OOM bias (`0`, `false`, `no`, `off`) |
| `OPENCLAW_PLUGIN_STAGE_DIR` | Plugin runtime deps stage directory |
| `OPENCLAW_ALLOW_INSECURE_PRIVATE_WS` | Allow plaintext `ws://` to trusted private-network IPs (break-glass; no config equivalent, client-side process-env override) |
| `OPENCLAW_APNS_RELAY_BASE_URL` | iOS relay base URL override (temporary env override) |
| `OPENCLAW_APNS_RELAY_TIMEOUT_MS` | iOS relay timeout in milliseconds |
| `OPENCLAW_APNS_RELAY_ALLOW_HTTP` | Allow HTTP relay URL (loopback-only dev escape hatch) |
| `OPENCLAW_APNS_TEAM_ID` | Apple Developer Team ID for direct APNs |
| `OPENCLAW_APNS_KEY_ID` | APNs Key ID |
| `OPENCLAW_APNS_PRIVATE_KEY_P8` | APNs private key content (inline p8) |
| `OPENCLAW_APNS_PRIVATE_KEY_PATH` | Path to APNs private key `.p8` file |
| `PI_RAW_STREAM` | Enable pi-mono raw chunk logging (captures chunks before block parsing) |
| `PI_RAW_STREAM_PATH` | Custom output path for pi-mono raw chunks (JSONL) |
| `NODE_EXTRA_CA_CERTS` | Fix nvm TLS failures (auto-set on Linux; nvm bundled CA store may miss modern root CAs) |
| `NODE_COMPILE_CACHE` | Node module compile cache path |
**Runtime-injected env vars (not user config):**
| Variable | Purpose |
|---|---|
| `OPENCLAW_SHELL=exec` | Set for commands run through the `exec` tool |
| `OPENCLAW_SHELL=acp` | Set for ACP runtime backend process spawns |
| `OPENCLAW_SHELL=acp-client` | Set for `openclaw acp client` ACP bridge process |
| `OPENCLAW_SHELL=tui-local` | Set for local TUI `!` shell commands |
**Env var precedence (highest → lowest):**
1. Process environment (from parent shell/daemon)
2. `.env` in current working directory (dotenv default; does not override)
3. Global `.env` at `~/.openclaw/.env` (does not override)
4. Config `env` block in `~/.openclaw/openclaw.json` (applied only if missing)
5. Optional login-shell import (`env.shellEnv.enabled` or `OPENCLAW_LOAD_SHELL_ENV=1`)
On Ubuntu fresh installs, OpenClaw also treats `~/.config/openclaw/gateway.env` as a compatibility fallback after the global `.env`.
**Env var substitution in config:** Reference env vars in config string values using `VAR_NAME` syntax.
**Provider API key env vars:**
- `ANTHROPIC_API_KEY`
- `OPENAI_API_KEY`
- `XAI_API_KEY`
- `GEMINI_API_KEY` / `GOOGLE_API_KEY`
- `OPENROUTER_API_KEY`
- `DISCORD_BOT_TOKEN`
- `TELEGRAM_BOT_TOKEN`
- `SLACK_BOT_TOKEN`