diff --git a/.gitignore b/.gitignore index 87751335a6d..6667c670952 100644 --- a/.gitignore +++ b/.gitignore @@ -69,6 +69,7 @@ apps/ios/*.mobileprovision # Local untracked files .local/ +docs/.local/ IDENTITY.md USER.md .tgz diff --git a/.markdownlint-cli2.jsonc b/.markdownlint-cli2.jsonc index 0b6b8f0fb71..94035711053 100644 --- a/.markdownlint-cli2.jsonc +++ b/.markdownlint-cli2.jsonc @@ -1,6 +1,6 @@ { "globs": ["docs/**/*.md", "docs/**/*.mdx", "README.md"], - "ignores": ["docs/zh-CN/**", "docs/.i18n/**", "docs/reference/templates/**"], + "ignores": ["docs/zh-CN/**", "docs/.i18n/**", "docs/reference/templates/**", "**/.local/**"], "config": { "default": true, diff --git a/docs/assets/install-script.svg b/docs/assets/install-script.svg new file mode 100644 index 00000000000..78a6f975641 --- /dev/null +++ b/docs/assets/install-script.svg @@ -0,0 +1 @@ +seb@ubuntu:~$curl-fsSLhttps://openclaw.ai/install.sh|bash╭─────────────────────────────────────────╮🦞OpenClawInstallerBecauseSiriwasn'tansweringat3AM.moderninstallermode╰─────────────────────────────────────────╯gumbootstrapped(temp,verified,v0.17.0)Detected:linuxInstallplanOSlinuxInstallmethodnpmRequestedversionlatest[1/3]PreparingenvironmentINFONode.jsnotfound,installingitnowINFOInstallingNode.jsviaNodeSourceConfiguringNodeSourcerepositoryConfiguringNodeSourcerepositoryConfiguringNodeSourcerepositoryConfiguringNodeSourcerepositoryConfiguringNodeSourcerepositoryConfiguringNodeSourcerepositoryConfiguringNodeSourcerepositoryConfiguringNodeSourcerepositoryInstallingNode.jsInstallingNode.jsInstallingNode.jsInstallingNode.jsInstallingNode.jsInstallingNode.jsInstallingNode.jsInstallingNode.jsNode.jsv22installed[2/3]InstallingOpenClawINFOGitnotfound,installingitnowUpdatingpackageindexInstallingGitInstallingGitInstallingGitInstallingGitInstallingGitInstallingGitInstallingGitInstallingGitGitinstalledINFOConfiguringnpmforuser-localinstallsnpmconfiguredforuserinstallsINFOInstallingOpenClawv2026.2.9InstallingOpenClawpackageInstallingOpenClawpackageInstallingOpenClawpackageInstallingOpenClawpackageInstallingOpenClawpackageInstallingOpenClawpackageInstallingOpenClawpackageInstallingOpenClawpackageOpenClawnpmpackageinstalledOpenClawinstalled[3/3]FinalizingsetupWARNPATHmissingnpmglobalbindir:/home/seb/.npm-global/binThiscanmakeopenclawshowas"commandnotfound"innewterminals.Fix(zsh:~/.zshrc,bash:~/.bashrc):exportPATH="/home/seb/.npm-global/bin:$PATH"🦞OpenClawinstalledsuccessfully(2026.2.9)!Finallyunpacked.Nowpointmeatyourproblems.INFOStartingsetup🦞OpenClaw2026.2.9(33c75cb)Thinkdifferent.Actuallythink.▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄██░▄▄▄░██░▄▄░██░▄▄▄██░▀██░██░▄▄▀██░████░▄▄▀██░███░████░███░██░▀▀░██░▄▄▄██░█░█░██░█████░████░▀▀░██░█░█░████░▀▀▀░██░█████░▀▀▀██░██▄░██░▀▀▄██░▀▀░█░██░██▄▀▄▀▄██▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀🦞OPENCLAW🦞OpenClawonboardingSecurity──────────────────────────────────────────────────────────────────────────────╮Securitywarningpleaseread.OpenClawisahobbyprojectandstillinbeta.Expectsharpedges.Thisbotcanreadfilesandrunactionsiftoolsareenabled.Abadpromptcantrickitintodoingunsafethings.Ifyou’renotcomfortablewithbasicsecurityandaccesscontrol,don’trunOpenClaw.Asksomeoneexperiencedtohelpbeforeenablingtoolsorexposingittotheinternet.Recommendedbaseline:-Pairing/allowlists+mentiongating.-Sandbox+least-privilegetools.-Keepsecretsoutoftheagent’sreachablefilesystem.-Usethestrongestavailablemodelforanybotwithtoolsoruntrustedinboxes.Runregularly:openclawsecurityaudit--deepopenclawsecurityaudit--fixMustread:https://docs.openclaw.ai/gateway/security├─────────────────────────────────────────────────────────────────────────────────────────╯Iunderstandthisispowerfulandinherentlyrisky.Continue?Yes/NoYes/Noseb@ubuntu:~$asciinemaseb@ubuntu:~$asciinemauploadseb@ubuntu:~$asciinemauploaddemo.castseb@ubuntu:~$seb@ubuntu:~$curl -fsSL https://openclaw.ai/install.sh | bashUpdatingpackageindexUpdatingpackageindexUpdatingpackageindexUpdatingpackageindexUpdatingpackageindexUpdatingpackageindexUpdatingpackageindexAbadpromptcantrickitintodoingunsafethings.-Keepsecretsoutoftheagent’sreachablefilesystem.seb@ubuntu:~$seb@ubuntu:~$aseb@ubuntu:~$asseb@ubuntu:~$ascseb@ubuntu:~$asciseb@ubuntu:~$asciiseb@ubuntu:~$asciinseb@ubuntu:~$asciineseb@ubuntu:~$asciinemseb@ubuntu:~$asciinemauseb@ubuntu:~$asciinemaupseb@ubuntu:~$asciinemauplseb@ubuntu:~$asciinemauploseb@ubuntu:~$asciinemauploaseb@ubuntu:~$asciinemauploaddseb@ubuntu:~$asciinemauploaddeseb@ubuntu:~$asciinemauploaddemseb@ubuntu:~$asciinemauploaddemoseb@ubuntu:~$asciinemauploaddemo.seb@ubuntu:~$asciinemauploaddemo.cseb@ubuntu:~$asciinemauploaddemo.caseb@ubuntu:~$asciinemauploaddemo.cas \ No newline at end of file diff --git a/docs/docs.json b/docs/docs.json index 42dcf5e337e..4ef7baffbae 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -24,6 +24,14 @@ "dark": "#FF5A36", "light": "#FF8A6B" }, + "styling": { + "codeblocks": { + "theme": { + "dark": "min-dark", + "light": "min-light" + } + } + }, "navbar": { "links": [ { @@ -1100,6 +1108,7 @@ "group": "Configuration and operations", "pages": [ "gateway/configuration", + "gateway/configuration-reference", "gateway/configuration-examples", "gateway/authentication", "gateway/health", diff --git a/docs/gateway/configuration-examples.md b/docs/gateway/configuration-examples.md index ac3f992930a..ca77eef132d 100644 --- a/docs/gateway/configuration-examples.md +++ b/docs/gateway/configuration-examples.md @@ -67,7 +67,11 @@ Save to `~/.openclaw/openclaw.json` and you can DM the bot from that number. // Auth profile metadata (secrets live in auth-profiles.json) auth: { profiles: { - "anthropic:me@example.com": { provider: "anthropic", mode: "oauth", email: "me@example.com" }, + "anthropic:me@example.com": { + provider: "anthropic", + mode: "oauth", + email: "me@example.com", + }, "anthropic:work": { provider: "anthropic", mode: "api_key" }, "openai:default": { provider: "openai", mode: "api_key" }, "openai-codex:default": { provider: "openai-codex", mode: "oauth" }, @@ -375,7 +379,10 @@ Save to `~/.openclaw/openclaw.json` and you can DM the bot from that number. to: "+15555550123", thinking: "low", timeoutSeconds: 300, - transform: { module: "./transforms/gmail.js", export: "transformGmail" }, + transform: { + module: "./transforms/gmail.js", + export: "transformGmail", + }, }, ], gmail: { diff --git a/docs/gateway/configuration-reference.md b/docs/gateway/configuration-reference.md new file mode 100644 index 00000000000..9dc16e68c1f --- /dev/null +++ b/docs/gateway/configuration-reference.md @@ -0,0 +1,2318 @@ +--- +title: "Configuration Reference" +description: "Complete field-by-field reference for ~/.openclaw/openclaw.json" +--- + +# Configuration Reference + +Every field available in `~/.openclaw/openclaw.json`. For a task-oriented overview, see [Configuration](/gateway/configuration). + +Config format is **JSON5** (comments + trailing commas allowed). All fields are optional — OpenClaw uses safe defaults when omitted. + +--- + +## Channels + +Each channel starts automatically when its config section exists (unless `enabled: false`). + +### DM and group access + +All channels support DM policies and group policies: + +| DM policy | Behavior | +| ------------------- | --------------------------------------------------------------- | +| `pairing` (default) | Unknown senders get a one-time pairing code; owner must approve | +| `allowlist` | Only senders in `allowFrom` (or paired allow store) | +| `open` | Allow all inbound DMs (requires `allowFrom: ["*"]`) | +| `disabled` | Ignore all inbound DMs | + +| Group policy | Behavior | +| --------------------- | ------------------------------------------------------ | +| `allowlist` (default) | Only groups matching the configured allowlist | +| `open` | Bypass group allowlists (mention-gating still applies) | +| `disabled` | Block all group/room messages | + + +`channels.defaults.groupPolicy` sets the default when a provider's `groupPolicy` is unset. +Pairing codes expire after 1 hour. Pending DM pairing requests are capped at **3 per channel**. +Slack/Discord have a special fallback: if their provider section is missing entirely, runtime group policy can resolve to `open` (with a startup warning). + + +### WhatsApp + +WhatsApp runs through the gateway's web channel (Baileys Web). It starts automatically when a linked session exists. + +```json5 +{ + channels: { + whatsapp: { + dmPolicy: "pairing", // pairing | allowlist | open | disabled + allowFrom: ["+15555550123", "+447700900123"], + textChunkLimit: 4000, + chunkMode: "length", // length | newline + mediaMaxMb: 50, + sendReadReceipts: true, // blue ticks (false in self-chat mode) + groups: { + "*": { requireMention: true }, + }, + groupPolicy: "allowlist", + groupAllowFrom: ["+15551234567"], + }, + }, + web: { + enabled: true, + heartbeatSeconds: 60, + reconnect: { + initialMs: 2000, + maxMs: 120000, + factor: 1.4, + jitter: 0.2, + maxAttempts: 0, + }, + }, +} +``` + + + +```json5 +{ + channels: { + whatsapp: { + accounts: { + default: {}, + personal: {}, + biz: { + // authDir: "~/.openclaw/credentials/whatsapp/biz", + }, + }, + }, + }, +} +``` + +- Outbound commands default to account `default` if present; otherwise the first configured account id (sorted). +- Legacy single-account Baileys auth dir is migrated by `openclaw doctor` into `whatsapp/default`. +- Per-account override: `channels.whatsapp.accounts..sendReadReceipts`. + + + +### Telegram + +```json5 +{ + channels: { + telegram: { + enabled: true, + botToken: "your-bot-token", + dmPolicy: "pairing", + allowFrom: ["tg:123456789"], + groups: { + "*": { requireMention: true }, + "-1001234567890": { + allowFrom: ["@admin"], + systemPrompt: "Keep answers brief.", + topics: { + "99": { + requireMention: false, + skills: ["search"], + systemPrompt: "Stay on topic.", + }, + }, + }, + }, + customCommands: [ + { command: "backup", description: "Git backup" }, + { command: "generate", description: "Create an image" }, + ], + historyLimit: 50, + replyToMode: "first", // off | first | all + linkPreview: true, + streamMode: "partial", // off | partial | block + draftChunk: { + minChars: 200, + maxChars: 800, + breakPreference: "paragraph", // paragraph | newline | sentence + }, + actions: { reactions: true, sendMessage: true }, + reactionNotifications: "own", // off | own | all + mediaMaxMb: 5, + retry: { + attempts: 3, + minDelayMs: 400, + maxDelayMs: 30000, + jitter: 0.1, + }, + network: { autoSelectFamily: false }, + proxy: "socks5://localhost:9050", + webhookUrl: "https://example.com/telegram-webhook", + webhookSecret: "secret", + webhookPath: "/telegram-webhook", + }, + }, +} +``` + +- Bot token: `channels.telegram.botToken` or `channels.telegram.tokenFile`, with `TELEGRAM_BOT_TOKEN` as fallback for the default account. +- `configWrites: false` blocks Telegram-initiated config writes (supergroup ID migrations, `/config set|unset`). +- Draft streaming uses Telegram `sendMessageDraft` (requires private chat topics). +- Retry policy: see [Retry policy](/concepts/retry). + +### Discord + +```json5 +{ + channels: { + discord: { + enabled: true, + token: "your-bot-token", + mediaMaxMb: 8, + allowBots: false, + actions: { + reactions: true, + stickers: true, + polls: true, + permissions: true, + messages: true, + threads: true, + pins: true, + search: true, + memberInfo: true, + roleInfo: true, + roles: false, + channelInfo: true, + voiceStatus: true, + events: true, + moderation: false, + }, + replyToMode: "off", // off | first | all + dm: { + enabled: true, + policy: "pairing", + allowFrom: ["1234567890", "steipete"], + groupEnabled: false, + groupChannels: ["openclaw-dm"], + }, + guilds: { + "123456789012345678": { + slug: "friends-of-openclaw", + requireMention: false, + reactionNotifications: "own", + users: ["987654321098765432"], + channels: { + general: { allow: true }, + help: { + allow: true, + requireMention: true, + users: ["987654321098765432"], + skills: ["docs"], + systemPrompt: "Short answers only.", + }, + }, + }, + }, + historyLimit: 20, + textChunkLimit: 2000, + chunkMode: "length", // length | newline + maxLinesPerMessage: 17, + retry: { + attempts: 3, + minDelayMs: 500, + maxDelayMs: 30000, + jitter: 0.1, + }, + }, + }, +} +``` + +- Token: `channels.discord.token`, with `DISCORD_BOT_TOKEN` as fallback for the default account. +- Use `user:` (DM) or `channel:` (guild channel) for delivery targets; bare numeric IDs are rejected. +- Guild slugs are lowercase with spaces replaced by `-`; channel keys use the slugged name (no `#`). Prefer guild IDs. +- Bot-authored messages are ignored by default. `allowBots: true` enables them (own messages still filtered). +- `maxLinesPerMessage` (default 17) splits tall messages even when under 2000 chars. + +**Reaction notification modes:** `off` (none), `own` (bot's messages, default), `all` (all messages), `allowlist` (from `guilds..users` on all messages). + +### Google Chat + +```json5 +{ + channels: { + googlechat: { + enabled: true, + serviceAccountFile: "/path/to/service-account.json", + audienceType: "app-url", // app-url | project-number + audience: "https://gateway.example.com/googlechat", + webhookPath: "/googlechat", + botUser: "users/1234567890", + dm: { + enabled: true, + policy: "pairing", + allowFrom: ["users/1234567890"], + }, + groupPolicy: "allowlist", + groups: { + "spaces/AAAA": { allow: true, requireMention: true }, + }, + actions: { reactions: true }, + typingIndicator: "message", + mediaMaxMb: 20, + }, + }, +} +``` + +- Service account JSON: inline (`serviceAccount`) or file-based (`serviceAccountFile`). +- Env fallbacks: `GOOGLE_CHAT_SERVICE_ACCOUNT` or `GOOGLE_CHAT_SERVICE_ACCOUNT_FILE`. +- Use `spaces/` or `users/` for delivery targets. + +### Slack + +```json5 +{ + channels: { + slack: { + enabled: true, + botToken: "xoxb-...", + appToken: "xapp-...", + dm: { + enabled: true, + policy: "pairing", + allowFrom: ["U123", "U456", "*"], + groupEnabled: false, + groupChannels: ["G123"], + }, + channels: { + C123: { allow: true, requireMention: true, allowBots: false }, + "#general": { + allow: true, + requireMention: true, + allowBots: false, + users: ["U123"], + skills: ["docs"], + systemPrompt: "Short answers only.", + }, + }, + historyLimit: 50, + allowBots: false, + reactionNotifications: "own", + reactionAllowlist: ["U123"], + replyToMode: "off", // off | first | all + thread: { + historyScope: "thread", // thread | channel + inheritParent: false, + }, + actions: { + reactions: true, + messages: true, + pins: true, + memberInfo: true, + emojiList: true, + }, + slashCommand: { + enabled: true, + name: "openclaw", + sessionPrefix: "slack:slash", + ephemeral: true, + }, + textChunkLimit: 4000, + chunkMode: "length", + mediaMaxMb: 20, + }, + }, +} +``` + +- **Socket mode** requires both `botToken` and `appToken` (`SLACK_BOT_TOKEN` + `SLACK_APP_TOKEN` for default account env fallback). +- **HTTP mode** requires `botToken` plus `signingSecret` (at root or per-account). +- `configWrites: false` blocks Slack-initiated config writes. +- Use `user:` (DM) or `channel:` for delivery targets. + +**Reaction notification modes:** `off`, `own` (default), `all`, `allowlist` (from `reactionAllowlist`). + +**Thread session isolation:** `thread.historyScope` is per-thread (default) or shared across channel. `thread.inheritParent` copies parent channel transcript to new threads. + +| Action group | Default | Notes | +| ------------ | ------- | ---------------------- | +| reactions | enabled | React + list reactions | +| messages | enabled | Read/send/edit/delete | +| pins | enabled | Pin/unpin/list | +| memberInfo | enabled | Member info | +| emojiList | enabled | Custom emoji list | + +### Mattermost + +Mattermost ships as a plugin: `openclaw plugins install @openclaw/mattermost`. + +```json5 +{ + channels: { + mattermost: { + enabled: true, + botToken: "mm-token", + baseUrl: "https://chat.example.com", + dmPolicy: "pairing", + chatmode: "oncall", // oncall | onmessage | onchar + oncharPrefixes: [">", "!"], + textChunkLimit: 4000, + chunkMode: "length", + }, + }, +} +``` + +Chat modes: `oncall` (respond on @-mention, default), `onmessage` (every message), `onchar` (messages starting with trigger prefix). + +### Signal + +```json5 +{ + channels: { + signal: { + reactionNotifications: "own", // off | own | all | allowlist + reactionAllowlist: ["+15551234567", "uuid:123e4567-e89b-12d3-a456-426614174000"], + historyLimit: 50, + }, + }, +} +``` + +**Reaction notification modes:** `off`, `own` (default), `all`, `allowlist` (from `reactionAllowlist`). + +### iMessage + +OpenClaw spawns `imsg rpc` (JSON-RPC over stdio). No daemon or port required. + +```json5 +{ + channels: { + imessage: { + enabled: true, + cliPath: "imsg", + dbPath: "~/Library/Messages/chat.db", + remoteHost: "user@gateway-host", + dmPolicy: "pairing", + allowFrom: ["+15555550123", "user@example.com", "chat_id:123"], + historyLimit: 50, + includeAttachments: false, + mediaMaxMb: 16, + service: "auto", + region: "US", + }, + }, +} +``` + +- Requires Full Disk Access to the Messages DB. +- Prefer `chat_id:` targets. Use `imsg chats --limit 20` to list chats. +- `cliPath` can point to an SSH wrapper; set `remoteHost` for SCP attachment fetching. + + + +```bash +#!/usr/bin/env bash +exec ssh -T gateway-host imsg "$@" +``` + + + +### Multi-account (all channels) + +Run multiple accounts per channel (each with its own `accountId`): + +```json5 +{ + channels: { + telegram: { + accounts: { + default: { + name: "Primary bot", + botToken: "123456:ABC...", + }, + alerts: { + name: "Alerts bot", + botToken: "987654:XYZ...", + }, + }, + }, + }, +} +``` + +- `default` is used when `accountId` is omitted (CLI + routing). +- Env tokens only apply to the **default** account. +- Base channel settings apply to all accounts unless overridden per account. +- Use `bindings[].match.accountId` to route each account to a different agent. + +### Group chat mention gating + +Group messages default to **require mention** (metadata mention or regex patterns). Applies to WhatsApp, Telegram, Discord, Google Chat, and iMessage group chats. + +**Mention types:** + +- **Metadata mentions**: Native platform @-mentions. Ignored in WhatsApp self-chat mode. +- **Text patterns**: Regex patterns in `agents.list[].groupChat.mentionPatterns`. Always checked. +- Mention gating is enforced only when detection is possible (native mentions or at least one pattern). + +```json5 +{ + messages: { + groupChat: { historyLimit: 50 }, + }, + agents: { + list: [{ id: "main", groupChat: { mentionPatterns: ["@openclaw", "openclaw"] } }], + }, +} +``` + +`messages.groupChat.historyLimit` sets the global default. Channels can override with `channels..historyLimit` (or per-account). Set `0` to disable. + +#### DM history limits + +```json5 +{ + channels: { + telegram: { + dmHistoryLimit: 30, + dms: { + "123456789": { historyLimit: 50 }, + }, + }, + }, +} +``` + +Resolution: per-DM override → provider default → no limit (all retained). + +Supported: `telegram`, `whatsapp`, `discord`, `slack`, `signal`, `imessage`, `msteams`. + +#### Self-chat mode + +Include your own number in `allowFrom` to enable self-chat mode (ignores native @-mentions, only responds to text patterns): + +```json5 +{ + channels: { + whatsapp: { + allowFrom: ["+15555550123"], + groups: { "*": { requireMention: true } }, + }, + }, + agents: { + list: [ + { + id: "main", + groupChat: { mentionPatterns: ["reisponde", "@openclaw"] }, + }, + ], + }, +} +``` + +### Commands (chat command handling) + +```json5 +{ + commands: { + native: "auto", // register native commands when supported + text: true, // parse /commands in chat messages + bash: false, // allow ! (alias: /bash) + bashForegroundMs: 2000, + config: false, // allow /config + debug: false, // allow /debug + restart: false, // allow /restart + gateway restart tool + allowFrom: { + "*": ["user1"], + discord: ["user:123"], + }, + useAccessGroups: true, + }, +} +``` + + + +- Text commands must be **standalone** messages with leading `/`. +- `native: "auto"` turns on native commands for Discord/Telegram, leaves Slack off. +- Override per channel: `channels.discord.commands.native` (bool or `"auto"`). `false` clears previously registered commands. +- `channels.telegram.customCommands` adds extra Telegram bot menu entries. +- `bash: true` enables `! ` for host shell. Requires `tools.elevated.enabled` and sender in `tools.elevated.allowFrom.`. +- `config: true` enables `/config` (reads/writes `openclaw.json`). +- `channels..configWrites` gates config mutations per channel (default: true). +- `allowFrom` is per-provider. When set, it is the **only** authorization source (channel allowlists/pairing and `useAccessGroups` are ignored). +- `useAccessGroups: false` allows commands to bypass access-group policies when `allowFrom` is not set. + + + +--- + +## Agent defaults + +### `agents.defaults.workspace` + +Default: `~/.openclaw/workspace`. + +```json5 +{ + agents: { defaults: { workspace: "~/.openclaw/workspace" } }, +} +``` + +### `agents.defaults.repoRoot` + +Optional repository root shown in the system prompt's Runtime line. If unset, OpenClaw auto-detects by walking upward from the workspace. + +```json5 +{ + agents: { defaults: { repoRoot: "~/Projects/openclaw" } }, +} +``` + +### `agents.defaults.skipBootstrap` + +Disables automatic creation of workspace bootstrap files (`AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, `BOOTSTRAP.md`). + +```json5 +{ + agents: { defaults: { skipBootstrap: true } }, +} +``` + +### `agents.defaults.bootstrapMaxChars` + +Max characters per workspace bootstrap file before truncation. Default: `20000`. + +```json5 +{ + agents: { defaults: { bootstrapMaxChars: 20000 } }, +} +``` + +### `agents.defaults.userTimezone` + +Timezone for system prompt context (not message timestamps). Falls back to host timezone. + +```json5 +{ + agents: { defaults: { userTimezone: "America/Chicago" } }, +} +``` + +### `agents.defaults.timeFormat` + +Time format in system prompt. Default: `auto` (OS preference). + +```json5 +{ + agents: { defaults: { timeFormat: "auto" } }, // auto | 12 | 24 +} +``` + +### `agents.defaults.model` + +```json5 +{ + agents: { + defaults: { + models: { + "anthropic/claude-opus-4-6": { alias: "opus" }, + "minimax/MiniMax-M2.1": { alias: "minimax" }, + }, + model: { + primary: "anthropic/claude-opus-4-6", + fallbacks: ["minimax/MiniMax-M2.1"], + }, + imageModel: { + primary: "openrouter/qwen/qwen-2.5-vl-72b-instruct:free", + fallbacks: ["openrouter/google/gemini-2.0-flash-vision:free"], + }, + thinkingDefault: "low", + verboseDefault: "off", + elevatedDefault: "on", + timeoutSeconds: 600, + mediaMaxMb: 5, + contextTokens: 200000, + maxConcurrent: 3, + }, + }, +} +``` + +- `model.primary`: format `provider/model` (e.g. `anthropic/claude-opus-4-6`). If you omit the provider, OpenClaw assumes `anthropic` (deprecated). +- `models`: the configured model catalog and allowlist for `/model`. Each entry can include `alias` (shortcut) and `params` (provider-specific: `temperature`, `maxTokens`). +- `imageModel`: only used if the primary model lacks image input. +- `maxConcurrent`: max parallel agent runs across sessions (each session still serialized). Default: 1. + +**Built-in alias shorthands** (only apply when the model is in `agents.defaults.models`): + +| Alias | Model | +| -------------- | ------------------------------- | +| `opus` | `anthropic/claude-opus-4-6` | +| `sonnet` | `anthropic/claude-sonnet-4-5` | +| `gpt` | `openai/gpt-5.2` | +| `gpt-mini` | `openai/gpt-5-mini` | +| `gemini` | `google/gemini-3-pro-preview` | +| `gemini-flash` | `google/gemini-3-flash-preview` | + +Your configured aliases always win over defaults. + +Z.AI GLM-4.x models automatically enable thinking mode unless you set `--thinking off` or define `agents.defaults.models["zai/"].params.thinking` yourself. + +### `agents.defaults.cliBackends` + +Optional CLI backends for text-only fallback runs (no tool calls). Useful as a backup when API providers fail. + +```json5 +{ + agents: { + defaults: { + cliBackends: { + "claude-cli": { + command: "/opt/homebrew/bin/claude", + }, + "my-cli": { + command: "my-cli", + args: ["--json"], + output: "json", + modelArg: "--model", + sessionArg: "--session", + sessionMode: "existing", + systemPromptArg: "--system", + systemPromptWhen: "first", + imageArg: "--image", + imageMode: "repeat", + }, + }, + }, + }, +} +``` + +- CLI backends are text-first; tools are always disabled. +- Sessions supported when `sessionArg` is set. +- Image pass-through supported when `imageArg` accepts file paths. + +### `agents.defaults.heartbeat` + +Periodic heartbeat runs. + +```json5 +{ + agents: { + defaults: { + heartbeat: { + every: "30m", // 0m disables + model: "openai/gpt-5.2-mini", + includeReasoning: false, + session: "main", + to: "+15555550123", + target: "last", // last | whatsapp | telegram | discord | ... | none + prompt: "Read HEARTBEAT.md if it exists...", + ackMaxChars: 300, + }, + }, + }, +} +``` + +- `every`: duration string (ms/s/m/h). Default: `30m`. +- Per-agent: set `agents.list[].heartbeat`. When any agent defines `heartbeat`, **only those agents** run heartbeats. +- Heartbeats run full agent turns — shorter intervals burn more tokens. + +### `agents.defaults.compaction` + +```json5 +{ + agents: { + defaults: { + compaction: { + mode: "safeguard", // default | safeguard + reserveTokensFloor: 24000, + memoryFlush: { + enabled: true, + softThresholdTokens: 6000, + systemPrompt: "Session nearing compaction. Store durable memories now.", + prompt: "Write any lasting notes to memory/YYYY-MM-DD.md; reply with NO_REPLY if nothing to store.", + }, + }, + }, + }, +} +``` + +- `mode`: `default` or `safeguard` (chunked summarization for long histories). See [Compaction](/concepts/compaction). +- `memoryFlush`: silent agentic turn before auto-compaction to store durable memories. Skipped when workspace is read-only. + +### `agents.defaults.contextPruning` + +Prunes **old tool results** from in-memory context before sending to the LLM. Does **not** modify session history on disk. + +```json5 +{ + agents: { + defaults: { + contextPruning: { + mode: "cache-ttl", // off | cache-ttl + ttl: "1h", // duration (ms/s/m/h), default unit: minutes + keepLastAssistants: 3, + softTrimRatio: 0.3, + hardClearRatio: 0.5, + minPrunableToolChars: 50000, + softTrim: { maxChars: 4000, headChars: 1500, tailChars: 1500 }, + hardClear: { enabled: true, placeholder: "[Old tool result content cleared]" }, + tools: { deny: ["browser", "canvas"] }, + }, + }, + }, +} +``` + + + +- `mode: "cache-ttl"` enables pruning passes. +- `ttl` controls how often pruning can run again (after the last cache touch). +- Pruning soft-trims oversized tool results first, then hard-clears older tool results if needed. + +**Soft-trim** keeps beginning + end and inserts `...` in the middle. + +**Hard-clear** replaces the entire tool result with the placeholder. + +Notes: + +- Image blocks are never trimmed/cleared. +- Ratios are character-based (approximate), not exact token counts. +- If fewer than `keepLastAssistants` assistant messages exist, pruning is skipped. + + + +See [Session Pruning](/concepts/session-pruning) for behavior details. + +### Block streaming + +```json5 +{ + agents: { + defaults: { + blockStreamingDefault: "off", // on | off + blockStreamingBreak: "text_end", // text_end | message_end + blockStreamingChunk: { minChars: 800, maxChars: 1200 }, + blockStreamingCoalesce: { idleMs: 1000 }, + humanDelay: { mode: "natural" }, // off | natural | custom (use minMs/maxMs) + }, + }, +} +``` + +- Non-Telegram channels require explicit `*.blockStreaming: true` to enable block replies. +- Channel overrides: `channels..blockStreamingCoalesce` (and per-account variants). Signal/Slack/Discord/Google Chat default `minChars: 1500`. +- `humanDelay`: randomized pause between block replies. `natural` = 800–2500ms. Per-agent override: `agents.list[].humanDelay`. + +See [Streaming](/concepts/streaming) for behavior + chunking details. + +### Typing indicators + +```json5 +{ + agents: { + defaults: { + typingMode: "instant", // never | instant | thinking | message + typingIntervalSeconds: 6, + }, + }, +} +``` + +- Defaults: `instant` for direct chats/mentions, `message` for unmentioned group chats. +- Per-session overrides: `session.typingMode`, `session.typingIntervalSeconds`. + +See [Typing Indicators](/concepts/typing-indicators). + +### `agents.defaults.sandbox` + +Optional **Docker sandboxing** for the embedded agent. See [Sandboxing](/gateway/sandboxing) for the full guide. + +```json5 +{ + agents: { + defaults: { + sandbox: { + mode: "non-main", // off | non-main | all + scope: "agent", // session | agent | shared + workspaceAccess: "none", // none | ro | rw + workspaceRoot: "~/.openclaw/sandboxes", + docker: { + image: "openclaw-sandbox:bookworm-slim", + containerPrefix: "openclaw-sbx-", + workdir: "/workspace", + readOnlyRoot: true, + tmpfs: ["/tmp", "/var/tmp", "/run"], + network: "none", + user: "1000:1000", + capDrop: ["ALL"], + env: { LANG: "C.UTF-8" }, + setupCommand: "apt-get update && apt-get install -y git curl jq", + pidsLimit: 256, + memory: "1g", + memorySwap: "2g", + cpus: 1, + ulimits: { + nofile: { soft: 1024, hard: 2048 }, + nproc: 256, + }, + seccompProfile: "/path/to/seccomp.json", + apparmorProfile: "openclaw-sandbox", + dns: ["1.1.1.1", "8.8.8.8"], + extraHosts: ["internal.service:10.0.0.5"], + binds: ["/home/user/source:/source:rw"], + }, + browser: { + enabled: false, + image: "openclaw-sandbox-browser:bookworm-slim", + cdpPort: 9222, + vncPort: 5900, + noVncPort: 6080, + headless: false, + enableNoVnc: true, + allowHostControl: false, + autoStart: true, + autoStartTimeoutMs: 12000, + }, + prune: { + idleHours: 24, + maxAgeDays: 7, + }, + }, + }, + }, + tools: { + sandbox: { + tools: { + allow: [ + "exec", + "process", + "read", + "write", + "edit", + "apply_patch", + "sessions_list", + "sessions_history", + "sessions_send", + "sessions_spawn", + "session_status", + ], + deny: ["browser", "canvas", "nodes", "cron", "discord", "gateway"], + }, + }, + }, +} +``` + + + +**Workspace access:** + +- `none`: per-scope sandbox workspace under `~/.openclaw/sandboxes` +- `ro`: sandbox workspace at `/workspace`, agent workspace mounted read-only at `/agent` +- `rw`: agent workspace mounted read/write at `/workspace` + +**Scope:** + +- `session`: per-session container + workspace +- `agent`: one container + workspace per agent (default) +- `shared`: shared container and workspace (no cross-session isolation) + +**`setupCommand`** runs once after container creation (via `sh -lc`). Needs network egress, writable root, root user. + +**Containers default to `network: "none"`** — set to `"bridge"` if the agent needs outbound access. + +**Inbound attachments** are staged into `media/inbound/*` in the active workspace. + +**`docker.binds`** mounts additional host directories; global and per-agent binds are merged. + +**Sandboxed browser** (`sandbox.browser.enabled`): Chromium + CDP in a container. noVNC URL injected into system prompt. Does not require `browser.enabled` in main config. + +- `allowHostControl: false` (default) blocks sandboxed sessions from targeting the host browser. + + + +Build images: + +```bash +scripts/sandbox-setup.sh # main sandbox image +scripts/sandbox-browser-setup.sh # optional browser image +``` + +### `agents.list` (per-agent overrides) + +```json5 +{ + agents: { + list: [ + { + id: "main", + default: true, + name: "Main Agent", + workspace: "~/.openclaw/workspace", + agentDir: "~/.openclaw/agents/main/agent", + model: "anthropic/claude-opus-4-6", // or { primary, fallbacks } + identity: { + name: "Samantha", + theme: "helpful sloth", + emoji: "🦥", + avatar: "avatars/samantha.png", + }, + groupChat: { mentionPatterns: ["@openclaw"] }, + sandbox: { mode: "off" }, + subagents: { allowAgents: ["*"] }, + tools: { + profile: "coding", + allow: ["browser"], + deny: ["canvas"], + elevated: { enabled: true }, + }, + }, + ], + }, +} +``` + +- `id`: stable agent id (required). +- `default`: when multiple are set, first wins (warning logged). If none set, first list entry is default. +- `model`: string form overrides `primary` only; object form `{ primary, fallbacks }` overrides both (`[]` disables global fallbacks). +- `identity.avatar`: workspace-relative path, `http(s)` URL, or `data:` URI. +- `identity` derives defaults: `ackReaction` from `emoji`, `mentionPatterns` from `name`/`emoji`. +- `subagents.allowAgents`: allowlist of agent ids for `sessions_spawn` (`["*"]` = any; default: same agent only). + +--- + +## Multi-agent routing + +Run multiple isolated agents inside one Gateway. See [Multi-Agent](/concepts/multi-agent). + +```json5 +{ + agents: { + list: [ + { id: "home", default: true, workspace: "~/.openclaw/workspace-home" }, + { id: "work", workspace: "~/.openclaw/workspace-work" }, + ], + }, + bindings: [ + { agentId: "home", match: { channel: "whatsapp", accountId: "personal" } }, + { agentId: "work", match: { channel: "whatsapp", accountId: "biz" } }, + ], +} +``` + +### Binding match fields + +- `match.channel` (required) +- `match.accountId` (optional; `*` = any account; omitted = default account) +- `match.peer` (optional; `{ kind: direct|group|channel, id }`) +- `match.guildId` / `match.teamId` (optional; channel-specific) + +**Deterministic match order:** + +1. `match.peer` +2. `match.guildId` +3. `match.teamId` +4. `match.accountId` (exact, no peer/guild/team) +5. `match.accountId: "*"` (channel-wide) +6. Default agent + +Within each tier, the first matching `bindings` entry wins. + +### Per-agent access profiles + + + +```json5 +{ + agents: { + list: [ + { + id: "personal", + workspace: "~/.openclaw/workspace-personal", + sandbox: { mode: "off" }, + }, + ], + }, +} +``` + + + + + +```json5 +{ + agents: { + list: [ + { + id: "family", + workspace: "~/.openclaw/workspace-family", + sandbox: { mode: "all", scope: "agent", workspaceAccess: "ro" }, + tools: { + allow: [ + "read", + "sessions_list", + "sessions_history", + "sessions_send", + "sessions_spawn", + "session_status", + ], + deny: ["write", "edit", "apply_patch", "exec", "process", "browser"], + }, + }, + ], + }, +} +``` + + + + + +```json5 +{ + agents: { + list: [ + { + id: "public", + workspace: "~/.openclaw/workspace-public", + sandbox: { mode: "all", scope: "agent", workspaceAccess: "none" }, + tools: { + allow: [ + "sessions_list", + "sessions_history", + "sessions_send", + "sessions_spawn", + "session_status", + "whatsapp", + "telegram", + "slack", + "discord", + "gateway", + ], + deny: [ + "read", + "write", + "edit", + "apply_patch", + "exec", + "process", + "browser", + "canvas", + "nodes", + "cron", + "gateway", + "image", + ], + }, + }, + ], + }, +} +``` + + + +See [Multi-Agent Sandbox & Tools](/tools/multi-agent-sandbox-tools) for precedence details. + +--- + +## Session + +```json5 +{ + session: { + scope: "per-sender", + dmScope: "main", // main | per-peer | per-channel-peer | per-account-channel-peer + identityLinks: { + alice: ["telegram:123456789", "discord:987654321012345678"], + }, + reset: { + mode: "daily", // daily | idle + atHour: 4, + idleMinutes: 60, + }, + resetByType: { + thread: { mode: "daily", atHour: 4 }, + direct: { mode: "idle", idleMinutes: 240 }, + group: { mode: "idle", idleMinutes: 120 }, + }, + resetTriggers: ["/new", "/reset"], + store: "~/.openclaw/agents/{agentId}/sessions/sessions.json", + maintenance: { + mode: "warn", // warn | enforce + pruneAfter: "30d", + maxEntries: 500, + rotateBytes: "10mb", + }, + mainKey: "main", // legacy (runtime always uses "main") + agentToAgent: { maxPingPongTurns: 5 }, + sendPolicy: { + rules: [{ action: "deny", match: { channel: "discord", chatType: "group" } }], + default: "allow", + }, + }, +} +``` + + + +- **`dmScope`**: how DMs are grouped. + - `main`: all DMs share the main session. + - `per-peer`: isolate by sender id across channels. + - `per-channel-peer`: isolate per channel + sender (recommended for multi-user inboxes). + - `per-account-channel-peer`: isolate per account + channel + sender (recommended for multi-account). +- **`identityLinks`**: map canonical ids to provider-prefixed peers for cross-channel session sharing. +- **`reset`**: primary reset policy. `daily` resets at `atHour` local time; `idle` resets after `idleMinutes`. When both configured, whichever expires first wins. +- **`resetByType`**: per-type overrides (`direct`, `group`, `thread`). Legacy `dm` accepted as alias for `direct`. +- **`mainKey`**: legacy field. Runtime now always uses `"main"` for the main direct-chat bucket. +- **`sendPolicy`**: match by `channel`, `chatType` (`direct|group|channel`, with legacy `dm` alias), or `keyPrefix`. First deny wins. +- **`maintenance`**: `warn` warns the active session on eviction; `enforce` applies pruning and rotation. + + + +--- + +## Messages + +```json5 +{ + messages: { + responsePrefix: "🦞", // or "auto" + ackReaction: "👀", + ackReactionScope: "group-mentions", // group-mentions | group-all | direct | all + removeAckAfterReply: false, + queue: { + mode: "collect", // steer | followup | collect | steer-backlog | steer+backlog | queue | interrupt + debounceMs: 1000, + cap: 20, + drop: "summarize", // old | new | summarize + byChannel: { + whatsapp: "collect", + telegram: "collect", + }, + }, + inbound: { + debounceMs: 2000, // 0 disables + byChannel: { + whatsapp: 5000, + slack: 1500, + }, + }, + }, +} +``` + +### Response prefix + +Per-channel/account overrides: `channels..responsePrefix`, `channels..accounts..responsePrefix`. + +Resolution (most specific wins): account → channel → global. `""` disables and stops cascade. `"auto"` derives `[{identity.name}]`. + +**Template variables:** + +| Variable | Description | Example | +| ----------------- | ---------------------- | --------------------------- | +| `{model}` | Short model name | `claude-opus-4-6` | +| `{modelFull}` | Full model identifier | `anthropic/claude-opus-4-6` | +| `{provider}` | Provider name | `anthropic` | +| `{thinkingLevel}` | Current thinking level | `high`, `low`, `off` | +| `{identity.name}` | Agent identity name | (same as `"auto"`) | + +Variables are case-insensitive. `{think}` is an alias for `{thinkingLevel}`. + +### Ack reaction + +- Defaults to active agent's `identity.emoji`, otherwise `"👀"`. Set `""` to disable. +- Scope: `group-mentions` (default), `group-all`, `direct`, `all`. +- `removeAckAfterReply`: removes ack after reply (Slack/Discord/Telegram/Google Chat only). + +### Inbound debounce + +Batches rapid text-only messages from the same sender into a single agent turn. Media/attachments flush immediately. Control commands bypass debouncing. + +### TTS (text-to-speech) + +```json5 +{ + messages: { + tts: { + auto: "always", // off | always | inbound | tagged + mode: "final", // final | all + provider: "elevenlabs", + summaryModel: "openai/gpt-4.1-mini", + modelOverrides: { enabled: true }, + maxTextLength: 4000, + timeoutMs: 30000, + prefsPath: "~/.openclaw/settings/tts.json", + elevenlabs: { + apiKey: "elevenlabs_api_key", + baseUrl: "https://api.elevenlabs.io", + voiceId: "voice_id", + modelId: "eleven_multilingual_v2", + seed: 42, + applyTextNormalization: "auto", + languageCode: "en", + voiceSettings: { + stability: 0.5, + similarityBoost: 0.75, + style: 0.0, + useSpeakerBoost: true, + speed: 1.0, + }, + }, + openai: { + apiKey: "openai_api_key", + model: "gpt-4o-mini-tts", + voice: "alloy", + }, + }, + }, +} +``` + +- `auto` controls auto-TTS. `/tts off|always|inbound|tagged` overrides per session. +- `summaryModel` overrides `agents.defaults.model.primary` for auto-summary. +- API keys fall back to `ELEVENLABS_API_KEY`/`XI_API_KEY` and `OPENAI_API_KEY`. + +--- + +## Talk + +Defaults for Talk mode (macOS/iOS/Android). + +```json5 +{ + talk: { + voiceId: "elevenlabs_voice_id", + voiceAliases: { + Clawd: "EXAVITQu4vr4xnSDxMaL", + Roger: "CwhRBWXzGAHq8TQ4Fs17", + }, + modelId: "eleven_v3", + outputFormat: "mp3_44100_128", + apiKey: "elevenlabs_api_key", + interruptOnSpeech: true, + }, +} +``` + +- Voice IDs fall back to `ELEVENLABS_VOICE_ID` or `SAG_VOICE_ID`. +- `apiKey` falls back to `ELEVENLABS_API_KEY`. +- `voiceAliases` lets Talk directives use friendly names. + +--- + +## Tools + +### Tool profiles + +`tools.profile` sets a base allowlist before `tools.allow`/`tools.deny`: + +| Profile | Includes | +| ----------- | ----------------------------------------------------------------------------------------- | +| `minimal` | `session_status` only | +| `coding` | `group:fs`, `group:runtime`, `group:sessions`, `group:memory`, `image` | +| `messaging` | `group:messaging`, `sessions_list`, `sessions_history`, `sessions_send`, `session_status` | +| `full` | No restriction (same as unset) | + +### Tool groups + +| Group | Tools | +| ------------------ | ---------------------------------------------------------------------------------------- | +| `group:runtime` | `exec`, `process` (`bash` is accepted as an alias for `exec`) | +| `group:fs` | `read`, `write`, `edit`, `apply_patch` | +| `group:sessions` | `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn`, `session_status` | +| `group:memory` | `memory_search`, `memory_get` | +| `group:web` | `web_search`, `web_fetch` | +| `group:ui` | `browser`, `canvas` | +| `group:automation` | `cron`, `gateway` | +| `group:messaging` | `message` | +| `group:nodes` | `nodes` | +| `group:openclaw` | All built-in tools (excludes provider plugins) | + +### `tools.allow` / `tools.deny` + +Global tool allow/deny policy (deny wins). Case-insensitive, supports `*` wildcards. Applied even when Docker sandbox is off. + +```json5 +{ + tools: { deny: ["browser", "canvas"] }, +} +``` + +### `tools.byProvider` + +Further restrict tools for specific providers or models. Order: base profile → provider profile → allow/deny. + +```json5 +{ + tools: { + profile: "coding", + byProvider: { + "google-antigravity": { profile: "minimal" }, + "openai/gpt-5.2": { allow: ["group:fs", "sessions_list"] }, + }, + }, +} +``` + +### `tools.elevated` + +Controls elevated (host) exec access: + +```json5 +{ + tools: { + elevated: { + enabled: true, + allowFrom: { + whatsapp: ["+15555550123"], + discord: ["steipete", "1234567890123"], + }, + }, + }, +} +``` + +- Per-agent override (`agents.list[].tools.elevated`) can only further restrict. +- `/elevated on|off|ask|full` stores state per session; inline directives apply to single message. +- Elevated `exec` runs on the host, bypasses sandboxing. + +### `tools.exec` + +```json5 +{ + tools: { + exec: { + backgroundMs: 10000, + timeoutSec: 1800, + cleanupMs: 1800000, + notifyOnExit: true, + applyPatch: { + enabled: false, + allowModels: ["gpt-5.2"], + }, + }, + }, +} +``` + +### `tools.web` + +```json5 +{ + tools: { + web: { + search: { + enabled: true, + apiKey: "brave_api_key", // or BRAVE_API_KEY env + maxResults: 5, + timeoutSeconds: 30, + cacheTtlMinutes: 15, + }, + fetch: { + enabled: true, + maxChars: 50000, + maxCharsCap: 50000, + timeoutSeconds: 30, + cacheTtlMinutes: 15, + userAgent: "custom-ua", + }, + }, + }, +} +``` + +### `tools.media` + +Configures inbound media understanding (image/audio/video): + +```json5 +{ + tools: { + media: { + concurrency: 2, + audio: { + enabled: true, + maxBytes: 20971520, + scope: { + default: "deny", + rules: [{ action: "allow", match: { chatType: "direct" } }], + }, + models: [ + { provider: "openai", model: "gpt-4o-mini-transcribe" }, + { type: "cli", command: "whisper", args: ["--model", "base", "{{MediaPath}}"] }, + ], + }, + video: { + enabled: true, + maxBytes: 52428800, + models: [{ provider: "google", model: "gemini-3-flash-preview" }], + }, + }, + }, +} +``` + + + +**Provider entry** (`type: "provider"` or omitted): + +- `provider`: API provider id (`openai`, `anthropic`, `google`/`gemini`, `groq`, etc.) +- `model`: model id override +- `profile` / `preferredProfile`: auth profile selection + +**CLI entry** (`type: "cli"`): + +- `command`: executable to run +- `args`: templated args (supports `{{MediaPath}}`, `{{Prompt}}`, `{{MaxChars}}`, etc.) + +**Common fields:** + +- `capabilities`: optional list (`image`, `audio`, `video`). Defaults: `openai`/`anthropic`/`minimax` → image, `google` → image+audio+video, `groq` → audio. +- `prompt`, `maxChars`, `maxBytes`, `timeoutSeconds`, `language`: per-entry overrides. +- Failures fall back to the next entry. + +Provider auth follows standard order: auth profiles → env vars → `models.providers.*.apiKey`. + + + +### `tools.agentToAgent` + +```json5 +{ + tools: { + agentToAgent: { + enabled: false, + allow: ["home", "work"], + }, + }, +} +``` + +### `tools.subagents` + +```json5 +{ + agents: { + defaults: { + subagents: { + model: "minimax/MiniMax-M2.1", + maxConcurrent: 1, + archiveAfterMinutes: 60, + }, + }, + }, +} +``` + +- `model`: default model for spawned sub-agents. If omitted, sub-agents inherit the caller's model. +- Per-subagent tool policy: `tools.subagents.tools.allow` / `tools.subagents.tools.deny`. + +--- + +## Custom providers and base URLs + +OpenClaw uses the pi-coding-agent model catalog. Add custom providers via `models.providers` in config or `~/.openclaw/agents//agent/models.json`. + +```json5 +{ + models: { + mode: "merge", // merge (default) | replace + providers: { + "custom-proxy": { + baseUrl: "http://localhost:4000/v1", + apiKey: "LITELLM_KEY", + api: "openai-completions", // openai-completions | openai-responses | anthropic-messages | google-generative-ai + models: [ + { + id: "llama-3.1-8b", + name: "Llama 3.1 8B", + reasoning: false, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 128000, + maxTokens: 32000, + }, + ], + }, + }, + }, +} +``` + +- Use `authHeader: true` + `headers` for custom auth needs. +- Override agent config root with `OPENCLAW_AGENT_DIR` (or `PI_CODING_AGENT_DIR`). + +### Provider examples + + + +```json5 +{ + env: { CEREBRAS_API_KEY: "sk-..." }, + agents: { + defaults: { + model: { + primary: "cerebras/zai-glm-4.7", + fallbacks: ["cerebras/zai-glm-4.6"], + }, + models: { + "cerebras/zai-glm-4.7": { alias: "GLM 4.7 (Cerebras)" }, + "cerebras/zai-glm-4.6": { alias: "GLM 4.6 (Cerebras)" }, + }, + }, + }, + models: { + mode: "merge", + providers: { + cerebras: { + baseUrl: "https://api.cerebras.ai/v1", + apiKey: "${CEREBRAS_API_KEY}", + api: "openai-completions", + models: [ + { id: "zai-glm-4.7", name: "GLM 4.7 (Cerebras)" }, + { id: "zai-glm-4.6", name: "GLM 4.6 (Cerebras)" }, + ], + }, + }, + }, +} +``` + +Use `cerebras/zai-glm-4.7` for Cerebras; `zai/glm-4.7` for Z.AI direct. + + + + + +```json5 +{ + agents: { + defaults: { + model: { primary: "opencode/claude-opus-4-6" }, + models: { "opencode/claude-opus-4-6": { alias: "Opus" } }, + }, + }, +} +``` + +Set `OPENCODE_API_KEY` (or `OPENCODE_ZEN_API_KEY`). Shortcut: `openclaw onboard --auth-choice opencode-zen`. + + + + + +```json5 +{ + agents: { + defaults: { + model: { primary: "zai/glm-4.7" }, + models: { "zai/glm-4.7": {} }, + }, + }, +} +``` + +Set `ZAI_API_KEY`. `z.ai/*` and `z-ai/*` are accepted aliases. Shortcut: `openclaw onboard --auth-choice zai-api-key`. + +- General endpoint: `https://api.z.ai/api/paas/v4` +- Coding endpoint (default): `https://api.z.ai/api/coding/paas/v4` +- For the general endpoint, define a custom provider with the base URL override. + + + + + +```json5 +{ + env: { MOONSHOT_API_KEY: "sk-..." }, + agents: { + defaults: { + model: { primary: "moonshot/kimi-k2.5" }, + models: { "moonshot/kimi-k2.5": { alias: "Kimi K2.5" } }, + }, + }, + models: { + mode: "merge", + providers: { + moonshot: { + baseUrl: "https://api.moonshot.ai/v1", + apiKey: "${MOONSHOT_API_KEY}", + api: "openai-completions", + models: [ + { + id: "kimi-k2.5", + name: "Kimi K2.5", + reasoning: false, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 256000, + maxTokens: 8192, + }, + ], + }, + }, + }, +} +``` + +For the China endpoint: `baseUrl: "https://api.moonshot.cn/v1"` or `openclaw onboard --auth-choice moonshot-api-key-cn`. + + + + + +```json5 +{ + env: { KIMI_API_KEY: "sk-..." }, + agents: { + defaults: { + model: { primary: "kimi-coding/k2p5" }, + models: { "kimi-coding/k2p5": { alias: "Kimi K2.5" } }, + }, + }, +} +``` + +Anthropic-compatible, built-in provider. Shortcut: `openclaw onboard --auth-choice kimi-code-api-key`. + + + + + +```json5 +{ + env: { SYNTHETIC_API_KEY: "sk-..." }, + agents: { + defaults: { + model: { primary: "synthetic/hf:MiniMaxAI/MiniMax-M2.1" }, + models: { "synthetic/hf:MiniMaxAI/MiniMax-M2.1": { alias: "MiniMax M2.1" } }, + }, + }, + models: { + mode: "merge", + providers: { + synthetic: { + baseUrl: "https://api.synthetic.new/anthropic", + apiKey: "${SYNTHETIC_API_KEY}", + api: "anthropic-messages", + models: [ + { + id: "hf:MiniMaxAI/MiniMax-M2.1", + name: "MiniMax M2.1", + reasoning: false, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 192000, + maxTokens: 65536, + }, + ], + }, + }, + }, +} +``` + +Base URL should omit `/v1` (Anthropic client appends it). Shortcut: `openclaw onboard --auth-choice synthetic-api-key`. + + + + + +```json5 +{ + agents: { + defaults: { + model: { primary: "minimax/MiniMax-M2.1" }, + models: { + "minimax/MiniMax-M2.1": { alias: "Minimax" }, + }, + }, + }, + models: { + mode: "merge", + providers: { + minimax: { + baseUrl: "https://api.minimax.io/anthropic", + apiKey: "${MINIMAX_API_KEY}", + api: "anthropic-messages", + models: [ + { + id: "MiniMax-M2.1", + name: "MiniMax M2.1", + reasoning: false, + input: ["text"], + cost: { input: 15, output: 60, cacheRead: 2, cacheWrite: 10 }, + contextWindow: 200000, + maxTokens: 8192, + }, + ], + }, + }, + }, +} +``` + +Set `MINIMAX_API_KEY`. Shortcut: `openclaw onboard --auth-choice minimax-api`. + + + + + +See [Local Models](/gateway/local-models). TL;DR: run MiniMax M2.1 via LM Studio Responses API on serious hardware; keep hosted models merged for fallback. + + + +--- + +## Skills + +```json5 +{ + skills: { + allowBundled: ["gemini", "peekaboo"], + load: { + extraDirs: ["~/Projects/agent-scripts/skills"], + }, + install: { + preferBrew: true, + nodeManager: "npm", // npm | pnpm | yarn + }, + entries: { + "nano-banana-pro": { + apiKey: "GEMINI_KEY_HERE", + env: { GEMINI_API_KEY: "GEMINI_KEY_HERE" }, + }, + peekaboo: { enabled: true }, + sag: { enabled: false }, + }, + }, +} +``` + +- `allowBundled`: optional allowlist for bundled skills only (managed/workspace skills unaffected). +- `entries..enabled: false` disables a skill even if bundled/installed. +- `entries..apiKey`: convenience for skills declaring a primary env var. + +--- + +## Plugins + +```json5 +{ + plugins: { + enabled: true, + allow: ["voice-call"], + deny: [], + load: { + paths: ["~/Projects/oss/voice-call-extension"], + }, + entries: { + "voice-call": { + enabled: true, + config: { provider: "twilio" }, + }, + }, + }, +} +``` + +- Loaded from `~/.openclaw/extensions`, `/.openclaw/extensions`, plus `plugins.load.paths`. +- **Config changes require a gateway restart.** +- `allow`: optional allowlist (only listed plugins load). `deny` wins. + +See [Plugins](/tools/plugin). + +--- + +## Browser + +```json5 +{ + browser: { + enabled: true, + evaluateEnabled: true, + defaultProfile: "chrome", + profiles: { + openclaw: { cdpPort: 18800, color: "#FF4500" }, + work: { cdpPort: 18801, color: "#0066CC" }, + remote: { cdpUrl: "http://10.0.0.42:9222", color: "#00AA00" }, + }, + color: "#FF4500", + // headless: false, + // noSandbox: false, + // executablePath: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser", + // attachOnly: false, + }, +} +``` + +- `evaluateEnabled: false` disables `act:evaluate` and `wait --fn`. +- Remote profiles are attach-only (start/stop/reset disabled). +- Auto-detect order: default browser if Chromium-based → Chrome → Brave → Edge → Chromium → Chrome Canary. +- Control service: loopback only (port derived from `gateway.port`, default `18791`). + +--- + +## 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. + +--- + +## Gateway + +```json5 +{ + gateway: { + mode: "local", // local | remote + port: 18789, + bind: "loopback", + auth: { + mode: "token", // token | password + token: "your-token", + // password: "your-password", // or OPENCLAW_GATEWAY_PASSWORD + allowTailscale: true, + }, + tailscale: { + mode: "off", // off | serve | funnel + resetOnExit: false, + }, + controlUi: { + enabled: true, + basePath: "/openclaw", + // root: "dist/control-ui", + // allowInsecureAuth: false, + // dangerouslyDisableDeviceAuth: false, + }, + remote: { + url: "ws://gateway.tailnet:18789", + transport: "ssh", // ssh | direct + token: "your-token", + // password: "your-password", + }, + trustedProxies: ["10.0.0.1"], + }, +} +``` + + + +- `mode`: `local` (run gateway) or `remote` (connect to remote gateway). Gateway refuses to start unless `local`. +- `port`: single multiplexed port for WS + HTTP. Precedence: `--port` > `OPENCLAW_GATEWAY_PORT` > `gateway.port` > `18789`. +- `bind`: `auto`, `loopback` (default), `lan` (`0.0.0.0`), `tailnet` (Tailscale IP only), or `custom`. +- **Auth**: required by default. Non-loopback binds require a shared token/password. Onboarding wizard generates a token by default. +- `auth.allowTailscale`: when `true`, Tailscale Serve identity headers satisfy auth (verified via `tailscale whois`). Defaults to `true` when `tailscale.mode = "serve"`. +- `tailscale.mode`: `serve` (tailnet only, loopback bind) or `funnel` (public, requires auth). +- `remote.transport`: `ssh` (default) or `direct` (ws/wss). For `direct`, `remote.url` must be `ws://` or `wss://`. +- `gateway.remote.token` is for remote CLI calls only; does not enable local gateway auth. +- `trustedProxies`: reverse proxy IPs that terminate TLS. Only list proxies you control. + + + +### OpenAI-compatible endpoints + +- Chat Completions: disabled by default. Enable with `gateway.http.endpoints.chatCompletions.enabled: true`. +- Responses API: `gateway.http.endpoints.responses.enabled`. + +### Multi-instance isolation + +Run multiple gateways on one host with unique ports and state dirs: + +```bash +OPENCLAW_CONFIG_PATH=~/.openclaw/a.json \ +OPENCLAW_STATE_DIR=~/.openclaw-a \ +openclaw gateway --port 19001 +``` + +Convenience flags: `--dev` (uses `~/.openclaw-dev` + port `19001`), `--profile ` (uses `~/.openclaw-`). + +See [Multiple Gateways](/gateway/multiple-gateways). + +--- + +## Hooks + +```json5 +{ + hooks: { + enabled: true, + token: "shared-secret", + path: "/hooks", + maxBodyBytes: 262144, + allowedAgentIds: ["hooks", "main"], + presets: ["gmail"], + transformsDir: "~/.openclaw/hooks", + mappings: [ + { + match: { path: "gmail" }, + action: "agent", + agentId: "hooks", + wakeMode: "now", + name: "Gmail", + sessionKey: "hook:gmail:{{messages[0].id}}", + messageTemplate: "From: {{messages[0].from}}\nSubject: {{messages[0].subject}}\n{{messages[0].snippet}}", + deliver: true, + channel: "last", + model: "openai/gpt-5.2-mini", + }, + ], + }, +} +``` + +Auth: `Authorization: Bearer ` or `x-openclaw-token: `. + +**Endpoints:** + +- `POST /hooks/wake` → `{ text, mode?: "now"|"next-heartbeat" }` +- `POST /hooks/agent` → `{ message, name?, agentId?, sessionKey?, wakeMode?, deliver?, channel?, to?, model?, thinking?, timeoutSeconds? }` +- `POST /hooks/` → resolved via `hooks.mappings` + + + +- `match.path` matches sub-path after `/hooks` (e.g. `/hooks/gmail` → `gmail`). +- `match.source` matches a payload field for generic paths. +- Templates like `{{messages[0].subject}}` read from the payload. +- `transform` can point to a JS/TS module returning a hook action. +- `agentId` routes to a specific agent; unknown IDs fall back to default. +- `allowedAgentIds`: restricts explicit routing (`*` or omitted = allow all, `[]` = deny all). +- `deliver: true` sends final reply to a channel; `channel` defaults to `last`. +- `model` overrides LLM for this hook run (must be allowed if model catalog is set). + + + +### Gmail integration + +```json5 +{ + hooks: { + gmail: { + account: "openclaw@gmail.com", + topic: "projects//topics/gog-gmail-watch", + subscription: "gog-gmail-watch-push", + pushToken: "shared-push-token", + hookUrl: "http://127.0.0.1:18789/hooks/gmail", + includeBody: true, + maxBytes: 20000, + renewEveryMinutes: 720, + serve: { bind: "127.0.0.1", port: 8788, path: "/" }, + tailscale: { mode: "funnel", path: "/gmail-pubsub" }, + model: "openrouter/meta-llama/llama-3.3-70b-instruct:free", + thinking: "off", + }, + }, +} +``` + +- Gateway auto-starts `gog gmail watch serve` on boot when configured. Set `OPENCLAW_SKIP_GMAIL_WATCHER=1` to disable. +- Don't run a separate `gog gmail watch serve` alongside the Gateway. + +--- + +## Canvas host + +```json5 +{ + canvasHost: { + root: "~/.openclaw/workspace/canvas", + port: 18793, + liveReload: true, + // enabled: false, // or OPENCLAW_SKIP_CANVAS_HOST=1 + }, +} +``` + +- Serves HTML/CSS/JS over HTTP for iOS/Android nodes. +- Injects live-reload client into served HTML. +- Auto-creates starter `index.html` when empty. +- Also serves A2UI at `/__openclaw__/a2ui/`. +- Changes require a gateway restart. +- Disable live reload for large directories or `EMFILE` errors. + +--- + +## Discovery + +### mDNS (Bonjour) + +```json5 +{ + discovery: { + mdns: { + mode: "minimal", // minimal | full | off + }, + }, +} +``` + +- `minimal` (default): omit `cliPath` + `sshPort` from TXT records. +- `full`: include `cliPath` + `sshPort`. +- Hostname defaults to `openclaw`. Override with `OPENCLAW_MDNS_HOSTNAME`. + +### Wide-area (DNS-SD) + +```json5 +{ + discovery: { + wideArea: { enabled: true }, + }, +} +``` + +Writes a unicast DNS-SD zone under `~/.openclaw/dns/`. For cross-network discovery, pair with a DNS server (CoreDNS recommended) + Tailscale split DNS. + +Setup: `openclaw dns setup --apply`. + +--- + +## Environment + +### `env` (inline env vars) + +```json5 +{ + env: { + OPENROUTER_API_KEY: "sk-or-...", + vars: { + GROQ_API_KEY: "gsk-...", + }, + shellEnv: { + enabled: true, + timeoutMs: 15000, + }, + }, +} +``` + +- Inline env vars are only applied if the process env is missing the key. +- `.env` files: CWD `.env` + `~/.openclaw/.env` (neither overrides existing vars). +- `shellEnv`: imports missing expected keys from your login shell profile. +- See [Environment](/help/environment) for full precedence. + +### Env var substitution + +Reference env vars in any config string with `${VAR_NAME}`: + +```json5 +{ + gateway: { + auth: { token: "${OPENCLAW_GATEWAY_TOKEN}" }, + }, +} +``` + +- Only uppercase names matched: `[A-Z_][A-Z0-9_]*`. +- Missing/empty vars throw an error at config load. +- Escape with `$${VAR}` for a literal `${VAR}`. +- Works with `$include`. + +--- + +## Auth storage + +```json5 +{ + auth: { + profiles: { + "anthropic:me@example.com": { provider: "anthropic", mode: "oauth", email: "me@example.com" }, + "anthropic:work": { provider: "anthropic", mode: "api_key" }, + }, + order: { + anthropic: ["anthropic:me@example.com", "anthropic:work"], + }, + }, +} +``` + +- Per-agent auth profiles stored at `/auth-profiles.json`. +- Legacy OAuth imports from `~/.openclaw/credentials/oauth.json`. +- See [OAuth](/concepts/oauth). + +--- + +## Logging + +```json5 +{ + logging: { + level: "info", + file: "/tmp/openclaw/openclaw.log", + consoleLevel: "info", + consoleStyle: "pretty", // pretty | compact | json + redactSensitive: "tools", // off | tools + redactPatterns: ["\\bTOKEN\\b\\s*[=:]\\s*([\"']?)([^\\s\"']+)\\1"], + }, +} +``` + +- Default log file: `/tmp/openclaw/openclaw-YYYY-MM-DD.log`. +- Set `logging.file` for a stable path. +- `consoleLevel` bumps to `debug` when `--verbose`. + +--- + +## Wizard + +Metadata written by CLI wizards (`onboard`, `configure`, `doctor`): + +```json5 +{ + wizard: { + lastRunAt: "2026-01-01T00:00:00.000Z", + lastRunVersion: "2026.1.4", + lastRunCommit: "abc1234", + lastRunCommand: "configure", + lastRunMode: "local", + }, +} +``` + +--- + +## Identity + +```json5 +{ + agents: { + list: [ + { + id: "main", + identity: { + name: "Samantha", + theme: "helpful sloth", + emoji: "🦥", + avatar: "avatars/samantha.png", + }, + }, + ], + }, +} +``` + +Written by the macOS onboarding assistant. Derives defaults: + +- `messages.ackReaction` from `identity.emoji` (falls back to 👀) +- `mentionPatterns` from `identity.name`/`identity.emoji` +- `avatar` accepts: workspace-relative path, `http(s)` URL, or `data:` URI + +--- + +## Bridge (legacy, removed) + +Current builds no longer include the TCP bridge. Nodes connect over the Gateway WebSocket. `bridge.*` keys are no longer part of the config schema (validation fails until removed; `openclaw doctor --fix` can strip unknown keys). + + + +```json +{ + "bridge": { + "enabled": true, + "port": 18790, + "bind": "tailnet", + "tls": { + "enabled": true, + "autoGenerate": true + } + } +} +``` + + + +--- + +## Cron + +```json5 +{ + cron: { + enabled: true, + maxConcurrentRuns: 2, + sessionRetention: "24h", // duration string or false + }, +} +``` + +- `sessionRetention`: how long to keep completed cron sessions before pruning. Default: `24h`. + +See [Cron Jobs](/automation/cron-jobs). + +--- + +## Media model template variables + +Template placeholders expanded in `tools.media.*.models[].args`: + +| Variable | Description | +| ------------------ | ------------------------------------------------- | +| `{{Body}}` | Full inbound message body | +| `{{RawBody}}` | Raw body (no history/sender wrappers) | +| `{{BodyStripped}}` | Body with group mentions stripped | +| `{{From}}` | Sender identifier | +| `{{To}}` | Destination identifier | +| `{{MessageSid}}` | Channel message id | +| `{{SessionId}}` | Current session UUID | +| `{{IsNewSession}}` | `"true"` when new session created | +| `{{MediaUrl}}` | Inbound media pseudo-URL | +| `{{MediaPath}}` | Local media path | +| `{{MediaType}}` | Media type (image/audio/document/…) | +| `{{Transcript}}` | Audio transcript | +| `{{Prompt}}` | Resolved media prompt for CLI entries | +| `{{MaxChars}}` | Resolved max output chars for CLI entries | +| `{{ChatType}}` | `"direct"` or `"group"` | +| `{{GroupSubject}}` | Group subject (best effort) | +| `{{GroupMembers}}` | Group members preview (best effort) | +| `{{SenderName}}` | Sender display name (best effort) | +| `{{SenderE164}}` | Sender phone number (best effort) | +| `{{Provider}}` | Provider hint (whatsapp, telegram, discord, etc.) | + +--- + +## Config includes (`$include`) + +Split config into multiple files: + +```json5 +// ~/.openclaw/openclaw.json +{ + gateway: { port: 18789 }, + agents: { $include: "./agents.json5" }, + broadcast: { + $include: ["./clients/mueller.json5", "./clients/schmidt.json5"], + }, +} +``` + +**Merge behavior:** + +- Single file: replaces the containing object. +- Array of files: deep-merged in order (later overrides earlier). +- Sibling keys: merged after includes (override included values). +- Nested includes: up to 10 levels deep. +- Paths: relative (to the including file), absolute, or `../` parent references. +- Errors: clear messages for missing files, parse errors, and circular includes. + +--- + +_Related: [Configuration](/gateway/configuration) · [Configuration Examples](/gateway/configuration-examples) · [Doctor](/gateway/doctor)_ diff --git a/docs/gateway/configuration.md b/docs/gateway/configuration.md index bdb3b1ed729..496aed2ce64 100644 --- a/docs/gateway/configuration.md +++ b/docs/gateway/configuration.md @@ -1,3448 +1,479 @@ --- -summary: "All configuration options for ~/.openclaw/openclaw.json with examples" +summary: "Configuration overview: common tasks, quick setup, and links to the full reference" read_when: - - Adding or modifying config fields + - Setting up OpenClaw for the first time + - Looking for common configuration patterns + - Navigating to specific config sections title: "Configuration" --- -# Configuration 🔧 +# Configuration -OpenClaw reads an optional **JSON5** config from `~/.openclaw/openclaw.json` (comments + trailing commas allowed). +OpenClaw reads an optional **JSON5** config from `~/.openclaw/openclaw.json`. -If the file is missing, OpenClaw uses safe-ish defaults (embedded Pi agent + per-sender sessions + workspace `~/.openclaw/workspace`). You usually only need a config to: +If the file is missing, OpenClaw uses safe defaults. Common reasons to add a config: -- restrict who can trigger the bot (`channels.whatsapp.allowFrom`, `channels.telegram.allowFrom`, etc.) -- control group allowlists + mention behavior (`channels.whatsapp.groups`, `channels.telegram.groups`, `channels.discord.guilds`, `agents.list[].groupChat`) -- customize message prefixes (`messages`) -- set the agent's workspace (`agents.defaults.workspace` or `agents.list[].workspace`) -- tune the embedded agent defaults (`agents.defaults`) and session behavior (`session`) -- set per-agent identity (`agents.list[].identity`) +- Connect channels and control who can message the bot +- Set models, tools, sandboxing, or automation (cron, hooks) +- Tune sessions, media, networking, or UI -> **New to configuration?** Check out the [Configuration Examples](/gateway/configuration-examples) guide for complete examples with detailed explanations! +See the [full reference](/gateway/configuration-reference) for every available field. -## Strict config validation + +**New to configuration?** Start with `openclaw onboard` for interactive setup, or check out the [Configuration Examples](/gateway/configuration-examples) guide for complete copy-paste configs. + -OpenClaw only accepts configurations that fully match the schema. -Unknown keys, malformed types, or invalid values cause the Gateway to **refuse to start** for safety. - -When validation fails: - -- The Gateway does not boot. -- Only diagnostic commands are allowed (for example: `openclaw doctor`, `openclaw logs`, `openclaw health`, `openclaw status`, `openclaw service`, `openclaw help`). -- Run `openclaw doctor` to see the exact issues. -- Run `openclaw doctor --fix` (or `--yes`) to apply migrations/repairs. - -Doctor never writes changes unless you explicitly opt into `--fix`/`--yes`. - -## Schema + UI hints - -The Gateway exposes a JSON Schema representation of the config via `config.schema` for UI editors. -The Control UI renders a form from this schema, with a **Raw JSON** editor as an escape hatch. - -Channel plugins and extensions can register schema + UI hints for their config, so channel settings -stay schema-driven across apps without hard-coded forms. - -Hints (labels, grouping, sensitive fields) ship alongside the schema so clients can render -better forms without hard-coding config knowledge. - -## Apply + restart (RPC) - -Use `config.apply` to validate + write the full config and restart the Gateway in one step. -It writes a restart sentinel and pings the last active session after the Gateway comes back. - -Warning: `config.apply` replaces the **entire config**. If you want to change only a few keys, -use `config.patch` or `openclaw config set`. Keep a backup of `~/.openclaw/openclaw.json`. - -Params: - -- `raw` (string) — JSON5 payload for the entire config -- `baseHash` (optional) — config hash from `config.get` (required when a config already exists) -- `sessionKey` (optional) — last active session key for the wake-up ping -- `note` (optional) — note to include in the restart sentinel -- `restartDelayMs` (optional) — delay before restart (default 2000) - -Example (via `gateway call`): - -```bash -openclaw gateway call config.get --params '{}' # capture payload.hash -openclaw gateway call config.apply --params '{ - "raw": "{\\n agents: { defaults: { workspace: \\"~/.openclaw/workspace\\" } }\\n}\\n", - "baseHash": "", - "sessionKey": "agent:main:whatsapp:dm:+15555550123", - "restartDelayMs": 1000 -}' -``` - -## Partial updates (RPC) - -Use `config.patch` to merge a partial update into the existing config without clobbering -unrelated keys. It applies JSON merge patch semantics: - -- objects merge recursively -- `null` deletes a key -- arrays replace - Like `config.apply`, it validates, writes the config, stores a restart sentinel, and schedules - the Gateway restart (with an optional wake when `sessionKey` is provided). - -Params: - -- `raw` (string) — JSON5 payload containing just the keys to change -- `baseHash` (required) — config hash from `config.get` -- `sessionKey` (optional) — last active session key for the wake-up ping -- `note` (optional) — note to include in the restart sentinel -- `restartDelayMs` (optional) — delay before restart (default 2000) - -Example: - -```bash -openclaw gateway call config.get --params '{}' # capture payload.hash -openclaw gateway call config.patch --params '{ - "raw": "{\\n channels: { telegram: { groups: { \\"*\\": { requireMention: false } } } }\\n}\\n", - "baseHash": "", - "sessionKey": "agent:main:whatsapp:dm:+15555550123", - "restartDelayMs": 1000 -}' -``` - -## Minimal config (recommended starting point) +## Minimal config ```json5 +// ~/.openclaw/openclaw.json { agents: { defaults: { workspace: "~/.openclaw/workspace" } }, channels: { whatsapp: { allowFrom: ["+15555550123"] } }, } ``` -Build the default image once with: +## Editing config -```bash -scripts/sandbox-setup.sh -``` + + + ```bash + openclaw onboard # full setup wizard + openclaw configure # config wizard + ``` + + + ```bash + openclaw config get agents.defaults.workspace + openclaw config set agents.defaults.heartbeat.every "2h" + openclaw config unset tools.web.search.apiKey + ``` + + + Open [http://127.0.0.1:18789](http://127.0.0.1:18789) and use the **Config** tab. + The Control UI renders a form from the config schema, with a **Raw JSON** editor as an escape hatch. + + + Edit `~/.openclaw/openclaw.json` directly. The Gateway watches the file and applies changes automatically (see [hot reload](#config-hot-reload)). + + -## Self-chat mode (recommended for group control) +## Strict validation -To prevent the bot from responding to WhatsApp @-mentions in groups (only respond to specific text triggers): + +OpenClaw only accepts configurations that fully match the schema. Unknown keys, malformed types, or invalid values cause the Gateway to **refuse to start**. + -```json5 -{ - agents: { - defaults: { workspace: "~/.openclaw/workspace" }, - list: [ - { - id: "main", - groupChat: { mentionPatterns: ["@openclaw", "reisponde"] }, +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` (or `--yes`) to apply repairs + +## Common tasks + + + + Each channel has its own config section under `channels.`. See the dedicated channel page for setup steps: + + - [WhatsApp](/channels/whatsapp) — `channels.whatsapp` + - [Telegram](/channels/telegram) — `channels.telegram` + - [Discord](/channels/discord) — `channels.discord` + - [Slack](/channels/slack) — `channels.slack` + - [Signal](/channels/signal) — `channels.signal` + - [iMessage](/channels/imessage) — `channels.imessage` + - [Google Chat](/channels/googlechat) — `channels.googlechat` + - [Mattermost](/channels/mattermost) — `channels.mattermost` + - [MS Teams](/channels/msteams) — `channels.msteams` + + All channels share the same DM policy pattern: + + ```json5 + { + channels: { + telegram: { + enabled: true, + botToken: "123:abc", + dmPolicy: "pairing", // pairing | allowlist | open | disabled + allowFrom: ["tg:123"], // only for allowlist/open + }, }, - ], - }, - channels: { - whatsapp: { - // Allowlist is DMs only; including your own number enables self-chat mode. - allowFrom: ["+15555550123"], - groups: { "*": { requireMention: true } }, - }, + } + ``` + + + + + Set the primary model and optional fallbacks: + + ```json5 + { + agents: { + defaults: { + model: { + primary: "anthropic/claude-sonnet-4-5", + fallbacks: ["openai/gpt-5.2"], + }, + models: { + "anthropic/claude-sonnet-4-5": { alias: "Sonnet" }, + "openai/gpt-5.2": { alias: "GPT" }, + }, + }, + }, + } + ``` + + - `agents.defaults.models` defines the model catalog and acts as the allowlist for `/model`. + - Model refs use `provider/model` format (e.g. `anthropic/claude-opus-4-6`). + - See [Models CLI](/concepts/models) for switching models in chat and [Model Failover](/concepts/model-failover) for auth rotation and fallback behavior. + - For custom/self-hosted providers, see [Custom providers](/gateway/configuration-reference#custom-providers-and-base-urls) in the reference. + + + + + DM access is controlled per channel via `dmPolicy`: + + - `"pairing"` (default): unknown senders get a one-time pairing code to approve + - `"allowlist"`: only senders in `allowFrom` (or the paired allow store) + - `"open"`: allow all inbound DMs (requires `allowFrom: ["*"]`) + - `"disabled"`: ignore all DMs + + For groups, use `groupPolicy` + `groupAllowFrom` or channel-specific allowlists. + + See the [full reference](/gateway/configuration-reference#dm-and-group-access) for per-channel details. + + + + + Group messages default to **require mention**. Configure patterns per agent: + + ```json5 + { + agents: { + list: [ + { + id: "main", + groupChat: { + mentionPatterns: ["@openclaw", "openclaw"], + }, + }, + ], + }, + channels: { + whatsapp: { + groups: { "*": { requireMention: true } }, + }, + }, + } + ``` + + - **Metadata mentions**: native @-mentions (WhatsApp tap-to-mention, Telegram @bot, etc.) + - **Text patterns**: regex patterns in `mentionPatterns` + - See [full reference](/gateway/configuration-reference#group-chat-mention-gating) for per-channel overrides and self-chat mode. + + + + + Sessions control conversation continuity and isolation: + + ```json5 + { + session: { + dmScope: "per-channel-peer", // recommended for multi-user + reset: { + mode: "daily", + atHour: 4, + idleMinutes: 120, + }, + }, + } + ``` + + - `dmScope`: `main` (shared) | `per-peer` | `per-channel-peer` | `per-account-channel-peer` + - See [Session Management](/concepts/session) for scoping, identity links, and send policy. + - See [full reference](/gateway/configuration-reference#session) for all fields. + + + + + Run agent sessions in isolated Docker containers: + + ```json5 + { + agents: { + defaults: { + sandbox: { + mode: "non-main", // off | non-main | all + scope: "agent", // session | agent | shared + }, + }, + }, + } + ``` + + Build the image first: `scripts/sandbox-setup.sh` + + See [Sandboxing](/gateway/sandboxing) for the full guide and [full reference](/gateway/configuration-reference#sandbox) for all options. + + + + + ```json5 + { + agents: { + defaults: { + heartbeat: { + every: "30m", + target: "last", + }, + }, + }, + } + ``` + + - `every`: duration string (`30m`, `2h`). Set `0m` to disable. + - `target`: `last` | `whatsapp` | `telegram` | `discord` | `none` + - See [Heartbeat](/gateway/heartbeat) for the full guide. + + + + + ```json5 + { + cron: { + enabled: true, + maxConcurrentRuns: 2, + sessionRetention: "24h", + }, + } + ``` + + See [Cron jobs](/automation/cron-jobs) for the feature overview and CLI examples. + + + + + Enable HTTP webhook endpoints on the Gateway: + + ```json5 + { + hooks: { + enabled: true, + token: "shared-secret", + path: "/hooks", + mappings: [ + { + match: { path: "gmail" }, + action: "agent", + agentId: "main", + deliver: true, + }, + ], + }, + } + ``` + + See [full reference](/gateway/configuration-reference#hooks) for all mapping options and Gmail integration. + + + + + Run multiple isolated agents with separate workspaces and sessions: + + ```json5 + { + agents: { + list: [ + { id: "home", default: true, workspace: "~/.openclaw/workspace-home" }, + { id: "work", workspace: "~/.openclaw/workspace-work" }, + ], + }, + bindings: [ + { agentId: "home", match: { channel: "whatsapp", accountId: "personal" } }, + { agentId: "work", match: { channel: "whatsapp", accountId: "biz" } }, + ], + } + ``` + + See [Multi-Agent](/concepts/multi-agent) and [full reference](/gateway/configuration-reference#multi-agent-routing) for binding rules and per-agent access profiles. + + + + + Use `$include` to organize large configs: + + ```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 + - **Error handling**: clear errors for missing files, parse errors, and circular includes + + + + +## Config hot reload + +The Gateway watches `~/.openclaw/openclaw.json` and applies changes automatically — no manual restart needed for most settings. + +### Reload modes + +| Mode | Behavior | +| ---------------------- | --------------------------------------------------------------------------------------- | +| **`hybrid`** (default) | Hot-applies safe changes instantly. Automatically restarts for critical ones. | +| **`hot`** | Hot-applies safe changes only. Logs a warning when a restart is needed — you handle it. | +| **`restart`** | Restarts the Gateway on any config change, safe or not. | +| **`off`** | Disables file watching. Changes take effect on the next manual restart. | + +```json5 +{ + gateway: { + reload: { mode: "hybrid", debounceMs: 300 }, }, } ``` -## Config Includes (`$include`) +### What hot-applies vs what needs a restart -Split your config into multiple files using the `$include` directive. This is useful for: +Most fields hot-apply without downtime. In `hybrid` mode, restart-required changes are handled automatically. -- Organizing large configs (e.g., per-client agent definitions) -- Sharing common settings across environments -- Keeping sensitive configs separate +| Category | Fields | Restart needed? | +| ------------------- | -------------------------------------------------------------------- | --------------- | +| Channels | `channels.*`, `web` (WhatsApp) — all built-in and extension channels | No | +| Agent & models | `agent`, `agents`, `models`, `routing` | No | +| Automation | `hooks`, `cron`, `agent.heartbeat` | No | +| Sessions & messages | `session`, `messages` | No | +| Tools & media | `tools`, `browser`, `skills`, `audio`, `talk` | No | +| UI & misc | `ui`, `logging`, `identity`, `bindings` | No | +| Gateway server | `gateway.*` (port, bind, auth, tailscale, TLS, HTTP) | **Yes** | +| Infrastructure | `discovery`, `canvasHost`, `plugins` | **Yes** | -### Basic usage + +`gateway.reload` and `gateway.remote` are exceptions — changing them does **not** trigger a restart. + -```json5 -// ~/.openclaw/openclaw.json -{ - gateway: { port: 18789 }, +## Config RPC (programmatic updates) - // Include a single file (replaces the key's value) - agents: { $include: "./agents.json5" }, + + + Validates + writes the full config and restarts the Gateway in one step. - // Include multiple files (deep-merged in order) - broadcast: { - $include: ["./clients/mueller.json5", "./clients/schmidt.json5"], - }, -} -``` + + `config.apply` replaces the **entire config**. Use `config.patch` for partial updates, or `openclaw config set` for single keys. + -```json5 -// ~/.openclaw/agents.json5 -{ - defaults: { sandbox: { mode: "all", scope: "session" } }, - list: [{ id: "main", workspace: "~/.openclaw/workspace" }], -} -``` + Params: -### Merge behavior + - `raw` (string) — JSON5 payload for the entire config + - `baseHash` (optional) — config hash from `config.get` (required when config exists) + - `sessionKey` (optional) — session key for the post-restart wake-up ping + - `note` (optional) — note for the restart sentinel + - `restartDelayMs` (optional) — delay before restart (default 2000) -- **Single file**: Replaces the object containing `$include` -- **Array of files**: Deep-merges files in order (later files override earlier ones) -- **With sibling keys**: Sibling keys are merged after includes (override included values) -- **Sibling keys + arrays/primitives**: Not supported (included content must be an object) + ```bash + openclaw gateway call config.get --params '{}' # capture payload.hash + openclaw gateway call config.apply --params '{ + "raw": "{ agents: { defaults: { workspace: \"~/.openclaw/workspace\" } } }", + "baseHash": "", + "sessionKey": "agent:main:whatsapp:dm:+15555550123" + }' + ``` -```json5 -// Sibling keys override included values -{ - $include: "./base.json5", // { a: 1, b: 2 } - b: 99, // Result: { a: 1, b: 99 } -} -``` + -### Nested includes + + Merges a partial update into the existing config (JSON merge patch semantics): -Included files can themselves contain `$include` directives (up to 10 levels deep): + - Objects merge recursively + - `null` deletes a key + - Arrays replace -```json5 -// clients/mueller.json5 -{ - agents: { $include: "./mueller/agents.json5" }, - broadcast: { $include: "./mueller/broadcast.json5" }, -} -``` + Params: -### Path resolution + - `raw` (string) — JSON5 with just the keys to change + - `baseHash` (required) — config hash from `config.get` + - `sessionKey`, `note`, `restartDelayMs` — same as `config.apply` -- **Relative paths**: Resolved relative to the including file -- **Absolute paths**: Used as-is -- **Parent directories**: `../` references work as expected + ```bash + openclaw gateway call config.patch --params '{ + "raw": "{ channels: { telegram: { groups: { \"*\": { requireMention: false } } } } }", + "baseHash": "" + }' + ``` -```json5 -{ "$include": "./sub/config.json5" } // relative -{ "$include": "/etc/openclaw/base.json5" } // absolute -{ "$include": "../shared/common.json5" } // parent dir -``` + + -### Error handling +## Environment variables -- **Missing file**: Clear error with resolved path -- **Parse error**: Shows which included file failed -- **Circular includes**: Detected and reported with include chain - -### Example: Multi-client legal setup - -```json5 -// ~/.openclaw/openclaw.json -{ - gateway: { port: 18789, auth: { token: "secret" } }, - - // Common agent defaults - agents: { - defaults: { - sandbox: { mode: "all", scope: "session" }, - }, - // Merge agent lists from all clients - list: { $include: ["./clients/mueller/agents.json5", "./clients/schmidt/agents.json5"] }, - }, - - // Merge broadcast configs - broadcast: { - $include: ["./clients/mueller/broadcast.json5", "./clients/schmidt/broadcast.json5"], - }, - - channels: { whatsapp: { groupPolicy: "allowlist" } }, -} -``` - -```json5 -// ~/.openclaw/clients/mueller/agents.json5 -[ - { id: "mueller-transcribe", workspace: "~/clients/mueller/transcribe" }, - { id: "mueller-docs", workspace: "~/clients/mueller/docs" }, -] -``` - -```json5 -// ~/.openclaw/clients/mueller/broadcast.json5 -{ - "120363403215116621@g.us": ["mueller-transcribe", "mueller-docs"], -} -``` - -## Common options - -### Env vars + `.env` - -OpenClaw reads env vars from the parent process (shell, launchd/systemd, CI, etc.). - -Additionally, it loads: +OpenClaw reads env vars from the parent process plus: - `.env` from the current working directory (if present) -- a global fallback `.env` from `~/.openclaw/.env` (aka `$OPENCLAW_STATE_DIR/.env`) +- `~/.openclaw/.env` (global fallback) -Neither `.env` file overrides existing env vars. - -You can also provide inline env vars in config. These are only applied if the -process env is missing the key (same non-overriding rule): +Neither file overrides existing env vars. You can also set inline env vars in config: ```json5 { env: { OPENROUTER_API_KEY: "sk-or-...", - vars: { - GROQ_API_KEY: "gsk-...", - }, + vars: { GROQ_API_KEY: "gsk-..." }, }, } ``` -See [/environment](/help/environment) for full precedence and sources. - -### `env.shellEnv` (optional) - -Opt-in convenience: if enabled and none of the expected keys are set yet, OpenClaw runs your login shell and imports only the missing expected keys (never overrides). -This effectively sources your shell profile. + + If enabled and expected keys aren't set, OpenClaw runs your login shell and imports only the missing keys: ```json5 { env: { - shellEnv: { - enabled: true, - timeoutMs: 15000, - }, + shellEnv: { enabled: true, timeoutMs: 15000 }, }, } ``` -Env var equivalent: +Env var equivalent: `OPENCLAW_LOAD_SHELL_ENV=1` + -- `OPENCLAW_LOAD_SHELL_ENV=1` -- `OPENCLAW_SHELL_ENV_TIMEOUT_MS=15000` - -### Env var substitution in config - -You can reference environment variables directly in any config string value using -`${VAR_NAME}` syntax. Variables are substituted at config load time, before validation. - -```json5 -{ - models: { - providers: { - "vercel-gateway": { - apiKey: "${VERCEL_GATEWAY_API_KEY}", - }, - }, - }, - gateway: { - auth: { - token: "${OPENCLAW_GATEWAY_TOKEN}", - }, - }, -} -``` - -**Rules:** - -- Only uppercase env var names are matched: `[A-Z_][A-Z0-9_]*` -- Missing or empty env vars throw an error at config load -- Escape with `$${VAR}` to output a literal `${VAR}` -- Works with `$include` (included files also get substitution) - -**Inline substitution:** - -```json5 -{ - models: { - providers: { - custom: { - baseUrl: "${CUSTOM_API_BASE}/v1", // → "https://api.example.com/v1" - }, - }, - }, -} -``` - -### Auth storage (OAuth + API keys) - -OpenClaw stores **per-agent** auth profiles (OAuth + API keys) in: - -- `/auth-profiles.json` (default: `~/.openclaw/agents//agent/auth-profiles.json`) - -See also: [/concepts/oauth](/concepts/oauth) - -Legacy OAuth imports: - -- `~/.openclaw/credentials/oauth.json` (or `$OPENCLAW_STATE_DIR/credentials/oauth.json`) - -The embedded Pi agent maintains a runtime cache at: - -- `/auth.json` (managed automatically; don’t edit manually) - -Legacy agent dir (pre multi-agent): - -- `~/.openclaw/agent/*` (migrated by `openclaw doctor` into `~/.openclaw/agents//agent/*`) - -Overrides: - -- OAuth dir (legacy import only): `OPENCLAW_OAUTH_DIR` -- Agent dir (default agent root override): `OPENCLAW_AGENT_DIR` (preferred), `PI_CODING_AGENT_DIR` (legacy) - -On first use, OpenClaw imports `oauth.json` entries into `auth-profiles.json`. - -### `auth` - -Optional metadata for auth profiles. This does **not** store secrets; it maps -profile IDs to a provider + mode (and optional email) and defines the provider -rotation order used for failover. - -```json5 -{ - auth: { - profiles: { - "anthropic:me@example.com": { provider: "anthropic", mode: "oauth", email: "me@example.com" }, - "anthropic:work": { provider: "anthropic", mode: "api_key" }, - }, - order: { - anthropic: ["anthropic:me@example.com", "anthropic:work"], - }, - }, -} -``` - -### `agents.list[].identity` - -Optional per-agent identity used for defaults and UX. This is written by the macOS onboarding assistant. - -If set, OpenClaw derives defaults (only when you haven’t set them explicitly): - -- `messages.ackReaction` from the **active agent**’s `identity.emoji` (falls back to 👀) -- `agents.list[].groupChat.mentionPatterns` from the agent’s `identity.name`/`identity.emoji` (so “@Samantha” works in groups across Telegram/Slack/Discord/Google Chat/iMessage/WhatsApp) -- `identity.avatar` accepts a workspace-relative image path or a remote URL/data URL. Local files must live inside the agent workspace. - -`identity.avatar` accepts: - -- Workspace-relative path (must stay within the agent workspace) -- `http(s)` URL -- `data:` URI - -```json5 -{ - agents: { - list: [ - { - id: "main", - identity: { - name: "Samantha", - theme: "helpful sloth", - emoji: "🦥", - avatar: "avatars/samantha.png", - }, - }, - ], - }, -} -``` - -### `wizard` - -Metadata written by CLI wizards (`onboard`, `configure`, `doctor`). - -```json5 -{ - wizard: { - lastRunAt: "2026-01-01T00:00:00.000Z", - lastRunVersion: "2026.1.4", - lastRunCommit: "abc1234", - lastRunCommand: "configure", - lastRunMode: "local", - }, -} -``` - -### `logging` - -- Default log file: `/tmp/openclaw/openclaw-YYYY-MM-DD.log` -- If you want a stable path, set `logging.file` to `/tmp/openclaw/openclaw.log`. -- Console output can be tuned separately via: - - `logging.consoleLevel` (defaults to `info`, bumps to `debug` when `--verbose`) - - `logging.consoleStyle` (`pretty` | `compact` | `json`) -- Tool summaries can be redacted to avoid leaking secrets: - - `logging.redactSensitive` (`off` | `tools`, default: `tools`) - - `logging.redactPatterns` (array of regex strings; overrides defaults) - -```json5 -{ - logging: { - level: "info", - file: "/tmp/openclaw/openclaw.log", - consoleLevel: "info", - consoleStyle: "pretty", - redactSensitive: "tools", - redactPatterns: [ - // Example: override defaults with your own rules. - "\\bTOKEN\\b\\s*[=:]\\s*([\"']?)([^\\s\"']+)\\1", - "/\\bsk-[A-Za-z0-9_-]{8,}\\b/gi", - ], - }, -} -``` - -### `channels.whatsapp.dmPolicy` - -Controls how WhatsApp direct chats (DMs) are handled: - -- `"pairing"` (default): unknown senders get a pairing code; owner must approve -- `"allowlist"`: only allow senders in `channels.whatsapp.allowFrom` (or paired allow store) -- `"open"`: allow all inbound DMs (**requires** `channels.whatsapp.allowFrom` to include `"*"`) -- `"disabled"`: ignore all inbound DMs - -Pairing codes expire after 1 hour; the bot only sends a pairing code when a new request is created. Pending DM pairing requests are capped at **3 per channel** by default. - -Pairing approvals: - -- `openclaw pairing list whatsapp` -- `openclaw pairing approve whatsapp ` - -### `channels.whatsapp.allowFrom` - -Allowlist of E.164 phone numbers that may trigger WhatsApp auto-replies (**DMs only**). -If empty and `channels.whatsapp.dmPolicy="pairing"`, unknown senders will receive a pairing code. -For groups, use `channels.whatsapp.groupPolicy` + `channels.whatsapp.groupAllowFrom`. - -```json5 -{ - channels: { - whatsapp: { - dmPolicy: "pairing", // pairing | allowlist | open | disabled - allowFrom: ["+15555550123", "+447700900123"], - textChunkLimit: 4000, // optional outbound chunk size (chars) - chunkMode: "length", // optional chunking mode (length | newline) - mediaMaxMb: 50, // optional inbound media cap (MB) - }, - }, -} -``` - -### `channels.whatsapp.sendReadReceipts` - -Controls whether inbound WhatsApp messages are marked as read (blue ticks). Default: `true`. - -Self-chat mode always skips read receipts, even when enabled. - -Per-account override: `channels.whatsapp.accounts..sendReadReceipts`. - -```json5 -{ - channels: { - whatsapp: { sendReadReceipts: false }, - }, -} -``` - -### `channels.whatsapp.accounts` (multi-account) - -Run multiple WhatsApp accounts in one gateway: - -```json5 -{ - channels: { - whatsapp: { - accounts: { - default: {}, // optional; keeps the default id stable - personal: {}, - biz: { - // Optional override. Default: ~/.openclaw/credentials/whatsapp/biz - // authDir: "~/.openclaw/credentials/whatsapp/biz", - }, - }, - }, - }, -} -``` - -Notes: - -- Outbound commands default to account `default` if present; otherwise the first configured account id (sorted). -- The legacy single-account Baileys auth dir is migrated by `openclaw doctor` into `whatsapp/default`. - -### `channels.telegram.accounts` / `channels.discord.accounts` / `channels.googlechat.accounts` / `channels.slack.accounts` / `channels.mattermost.accounts` / `channels.signal.accounts` / `channels.imessage.accounts` - -Run multiple accounts per channel (each account has its own `accountId` and optional `name`): - -```json5 -{ - channels: { - telegram: { - accounts: { - default: { - name: "Primary bot", - botToken: "123456:ABC...", - }, - alerts: { - name: "Alerts bot", - botToken: "987654:XYZ...", - }, - }, - }, - }, -} -``` - -Notes: - -- `default` is used when `accountId` is omitted (CLI + routing). -- Env tokens only apply to the **default** account. -- Base channel settings (group policy, mention gating, etc.) apply to all accounts unless overridden per account. -- Use `bindings[].match.accountId` to route each account to a different agents.defaults. - -### Group chat mention gating (`agents.list[].groupChat` + `messages.groupChat`) - -Group messages default to **require mention** (either metadata mention or regex patterns). Applies to WhatsApp, Telegram, Discord, Google Chat, and iMessage group chats. - -**Mention types:** - -- **Metadata mentions**: Native platform @-mentions (e.g., WhatsApp tap-to-mention). Ignored in WhatsApp self-chat mode (see `channels.whatsapp.allowFrom`). -- **Text patterns**: Regex patterns defined in `agents.list[].groupChat.mentionPatterns`. Always checked regardless of self-chat mode. -- Mention gating is enforced only when mention detection is possible (native mentions or at least one `mentionPattern`). - -```json5 -{ - messages: { - groupChat: { historyLimit: 50 }, - }, - agents: { - list: [{ id: "main", groupChat: { mentionPatterns: ["@openclaw", "openclaw"] } }], - }, -} -``` - -`messages.groupChat.historyLimit` sets the global default for group history context. Channels can override with `channels..historyLimit` (or `channels..accounts.*.historyLimit` for multi-account). Set `0` to disable history wrapping. - -#### DM history limits - -DM conversations use session-based history managed by the agent. You can limit the number of user turns retained per DM session: - -```json5 -{ - channels: { - telegram: { - dmHistoryLimit: 30, // limit DM sessions to 30 user turns - dms: { - "123456789": { historyLimit: 50 }, // per-user override (user ID) - }, - }, - }, -} -``` - -Resolution order: - -1. Per-DM override: `channels..dms[userId].historyLimit` -2. Provider default: `channels..dmHistoryLimit` -3. No limit (all history retained) - -Supported providers: `telegram`, `whatsapp`, `discord`, `slack`, `signal`, `imessage`, `msteams`. - -Per-agent override (takes precedence when set, even `[]`): - -```json5 -{ - agents: { - list: [ - { id: "work", groupChat: { mentionPatterns: ["@workbot", "\\+15555550123"] } }, - { id: "personal", groupChat: { mentionPatterns: ["@homebot", "\\+15555550999"] } }, - ], - }, -} -``` - -Mention gating defaults live per channel (`channels.whatsapp.groups`, `channels.telegram.groups`, `channels.imessage.groups`, `channels.discord.guilds`). When `*.groups` is set, it also acts as a group allowlist; include `"*"` to allow all groups. - -To respond **only** to specific text triggers (ignoring native @-mentions): - -```json5 -{ - channels: { - whatsapp: { - // Include your own number to enable self-chat mode (ignore native @-mentions). - allowFrom: ["+15555550123"], - groups: { "*": { requireMention: true } }, - }, - }, - agents: { - list: [ - { - id: "main", - groupChat: { - // Only these text patterns will trigger responses - mentionPatterns: ["reisponde", "@openclaw"], - }, - }, - ], - }, -} -``` - -### Group policy (per channel) - -Use `channels.*.groupPolicy` to control whether group/room messages are accepted at all: - -```json5 -{ - channels: { - whatsapp: { - groupPolicy: "allowlist", - groupAllowFrom: ["+15551234567"], - }, - telegram: { - groupPolicy: "allowlist", - groupAllowFrom: ["tg:123456789", "@alice"], - }, - signal: { - groupPolicy: "allowlist", - groupAllowFrom: ["+15551234567"], - }, - imessage: { - groupPolicy: "allowlist", - groupAllowFrom: ["chat_id:123"], - }, - msteams: { - groupPolicy: "allowlist", - groupAllowFrom: ["user@org.com"], - }, - discord: { - groupPolicy: "allowlist", - guilds: { - GUILD_ID: { - channels: { help: { allow: true } }, - }, - }, - }, - slack: { - groupPolicy: "allowlist", - channels: { "#general": { allow: true } }, - }, - }, -} -``` - -Notes: - -- `"open"`: groups bypass allowlists; mention-gating still applies. -- `"disabled"`: block all group/room messages. -- `"allowlist"`: only allow groups/rooms that match the configured allowlist. -- `channels.defaults.groupPolicy` sets the default when a provider’s `groupPolicy` is unset. -- WhatsApp/Telegram/Signal/iMessage/Microsoft Teams use `groupAllowFrom` (fallback: explicit `allowFrom`). -- Discord/Slack use channel allowlists (`channels.discord.guilds.*.channels`, `channels.slack.channels`). -- Group DMs (Discord/Slack) are still controlled by `dm.groupEnabled` + `dm.groupChannels`. -- Default is `groupPolicy: "allowlist"` (unless overridden by `channels.defaults.groupPolicy`); if no allowlist is configured, group messages are blocked. - -### Multi-agent routing (`agents.list` + `bindings`) - -Run multiple isolated agents (separate workspace, `agentDir`, sessions) inside one Gateway. -Inbound messages are routed to an agent via bindings. - -- `agents.list[]`: per-agent overrides. - - `id`: stable agent id (required). - - `default`: optional; when multiple are set, the first wins and a warning is logged. - If none are set, the **first entry** in the list is the default agent. - - `name`: display name for the agent. - - `workspace`: default `~/.openclaw/workspace-` (for `main`, falls back to `agents.defaults.workspace`). - - `agentDir`: default `~/.openclaw/agents//agent`. - - `model`: per-agent default model, overrides `agents.defaults.model` for that agent. - - string form: `"provider/model"`, overrides only `agents.defaults.model.primary` - - object form: `{ primary, fallbacks }` (fallbacks override `agents.defaults.model.fallbacks`; `[]` disables global fallbacks for that agent) - - `identity`: per-agent name/theme/emoji (used for mention patterns + ack reactions). - - `groupChat`: per-agent mention-gating (`mentionPatterns`). - - `sandbox`: per-agent sandbox config (overrides `agents.defaults.sandbox`). - - `mode`: `"off"` | `"non-main"` | `"all"` - - `workspaceAccess`: `"none"` | `"ro"` | `"rw"` - - `scope`: `"session"` | `"agent"` | `"shared"` - - `workspaceRoot`: custom sandbox workspace root - - `docker`: per-agent docker overrides (e.g. `image`, `network`, `env`, `setupCommand`, limits; ignored when `scope: "shared"`) - - `browser`: per-agent sandboxed browser overrides (ignored when `scope: "shared"`) - - `prune`: per-agent sandbox pruning overrides (ignored when `scope: "shared"`) - - `subagents`: per-agent sub-agent defaults. - - `allowAgents`: allowlist of agent ids for `sessions_spawn` from this agent (`["*"]` = allow any; default: only same agent) - - `tools`: per-agent tool restrictions (applied before sandbox tool policy). - - `profile`: base tool profile (applied before allow/deny) - - `allow`: array of allowed tool names - - `deny`: array of denied tool names (deny wins) -- `agents.defaults`: shared agent defaults (model, workspace, sandbox, etc.). -- `bindings[]`: routes inbound messages to an `agentId`. - - `match.channel` (required) - - `match.accountId` (optional; `*` = any account; omitted = default account) - - `match.peer` (optional; `{ kind: direct|group|channel, id }`) - - `match.guildId` / `match.teamId` (optional; channel-specific) - -Deterministic match order: - -1. `match.peer` -2. `match.guildId` -3. `match.teamId` -4. `match.accountId` (exact, no peer/guild/team) -5. `match.accountId: "*"` (channel-wide, no peer/guild/team) -6. default agent (`agents.list[].default`, else first list entry, else `"main"`) - -Within each match tier, the first matching entry in `bindings` wins. - -#### Per-agent access profiles (multi-agent) - -Each agent can carry its own sandbox + tool policy. Use this to mix access -levels in one gateway: - -- **Full access** (personal agent) -- **Read-only** tools + workspace -- **No filesystem access** (messaging/session tools only) - -See [Multi-Agent Sandbox & Tools](/tools/multi-agent-sandbox-tools) for precedence and -additional examples. - -Full access (no sandbox): - -```json5 -{ - agents: { - list: [ - { - id: "personal", - workspace: "~/.openclaw/workspace-personal", - sandbox: { mode: "off" }, - }, - ], - }, -} -``` - -Read-only tools + read-only workspace: - -```json5 -{ - agents: { - list: [ - { - id: "family", - workspace: "~/.openclaw/workspace-family", - sandbox: { - mode: "all", - scope: "agent", - workspaceAccess: "ro", - }, - tools: { - allow: [ - "read", - "sessions_list", - "sessions_history", - "sessions_send", - "sessions_spawn", - "session_status", - ], - deny: ["write", "edit", "apply_patch", "exec", "process", "browser"], - }, - }, - ], - }, -} -``` - -No filesystem access (messaging/session tools enabled): - -```json5 -{ - agents: { - list: [ - { - id: "public", - workspace: "~/.openclaw/workspace-public", - sandbox: { - mode: "all", - scope: "agent", - workspaceAccess: "none", - }, - tools: { - allow: [ - "sessions_list", - "sessions_history", - "sessions_send", - "sessions_spawn", - "session_status", - "whatsapp", - "telegram", - "slack", - "discord", - "gateway", - ], - deny: [ - "read", - "write", - "edit", - "apply_patch", - "exec", - "process", - "browser", - "canvas", - "nodes", - "cron", - "gateway", - "image", - ], - }, - }, - ], - }, -} -``` - -Example: two WhatsApp accounts → two agents: - -```json5 -{ - agents: { - list: [ - { id: "home", default: true, workspace: "~/.openclaw/workspace-home" }, - { id: "work", workspace: "~/.openclaw/workspace-work" }, - ], - }, - bindings: [ - { agentId: "home", match: { channel: "whatsapp", accountId: "personal" } }, - { agentId: "work", match: { channel: "whatsapp", accountId: "biz" } }, - ], - channels: { - whatsapp: { - accounts: { - personal: {}, - biz: {}, - }, - }, - }, -} -``` - -### `tools.agentToAgent` (optional) - -Agent-to-agent messaging is opt-in: - -```json5 -{ - tools: { - agentToAgent: { - enabled: false, - allow: ["home", "work"], - }, - }, -} -``` - -### `messages.queue` - -Controls how inbound messages behave when an agent run is already active. - -```json5 -{ - messages: { - queue: { - mode: "collect", // steer | followup | collect | steer-backlog (steer+backlog ok) | interrupt (queue=steer legacy) - debounceMs: 1000, - cap: 20, - drop: "summarize", // old | new | summarize - byChannel: { - whatsapp: "collect", - telegram: "collect", - discord: "collect", - imessage: "collect", - webchat: "collect", - }, - }, - }, -} -``` - -### `messages.inbound` - -Debounce rapid inbound messages from the **same sender** so multiple back-to-back -messages become a single agent turn. Debouncing is scoped per channel + conversation -and uses the most recent message for reply threading/IDs. - -```json5 -{ - messages: { - inbound: { - debounceMs: 2000, // 0 disables - byChannel: { - whatsapp: 5000, - slack: 1500, - discord: 1500, - }, - }, - }, -} -``` - -Notes: - -- Debounce batches **text-only** messages; media/attachments flush immediately. -- Control commands (e.g. `/queue`, `/new`) bypass debouncing so they stay standalone. - -### `commands` (chat command handling) - -Controls how chat commands are enabled across connectors. - -```json5 -{ - commands: { - native: "auto", // register native commands when supported (auto) - text: true, // parse slash commands in chat messages - bash: false, // allow ! (alias: /bash) (host-only; requires tools.elevated allowlists) - bashForegroundMs: 2000, // bash foreground window (0 backgrounds immediately) - config: false, // allow /config (writes to disk) - debug: false, // allow /debug (runtime-only overrides) - restart: false, // allow /restart + gateway restart tool - allowFrom: { - "*": ["user1"], // optional per-provider command allowlist - discord: ["user:123"], - }, - useAccessGroups: true, // enforce access-group allowlists/policies for commands - }, -} -``` - -Notes: - -- Text commands must be sent as a **standalone** message and use the leading `/` (no plain-text aliases). -- `commands.text: false` disables parsing chat messages for commands. -- `commands.native: "auto"` (default) turns on native commands for Discord/Telegram and leaves Slack off; unsupported channels stay text-only. -- Set `commands.native: true|false` to force all, or override per channel with `channels.discord.commands.native`, `channels.telegram.commands.native`, `channels.slack.commands.native` (bool or `"auto"`). `false` clears previously registered commands on Discord/Telegram at startup; Slack commands are managed in the Slack app. -- `channels.telegram.customCommands` adds extra Telegram bot menu entries. Names are normalized; conflicts with native commands are ignored. -- `commands.bash: true` enables `! ` to run host shell commands (`/bash ` also works as an alias). Requires `tools.elevated.enabled` and allowlisting the sender in `tools.elevated.allowFrom.`. -- `commands.bashForegroundMs` controls how long bash waits before backgrounding. While a bash job is running, new `! ` requests are rejected (one at a time). -- `commands.config: true` enables `/config` (reads/writes `openclaw.json`). -- `channels..configWrites` gates config mutations initiated by that channel (default: true). This applies to `/config set|unset` plus provider-specific auto-migrations (Telegram supergroup ID changes, Slack channel ID changes). -- `commands.debug: true` enables `/debug` (runtime-only overrides). -- `commands.restart: true` enables `/restart` and the gateway tool restart action. -- `commands.allowFrom` sets a per-provider allowlist for command execution. When configured, it is the **only** - authorization source for commands and directives (channel allowlists/pairing and `commands.useAccessGroups` are ignored). - Use `"*"` for a global default; provider-specific keys (for example `discord`) override it. -- `commands.useAccessGroups: false` allows commands to bypass access-group allowlists/policies when `commands.allowFrom` - is not set. -- Slash commands and directives are only honored for **authorized senders**. If `commands.allowFrom` is set, - authorization comes solely from that list; otherwise it is derived from channel allowlists/pairing plus - `commands.useAccessGroups`. - -### `web` (WhatsApp web channel runtime) - -WhatsApp runs through the gateway’s web channel (Baileys Web). It starts automatically when a linked session exists. -Set `web.enabled: false` to keep it off by default. - -```json5 -{ - web: { - enabled: true, - heartbeatSeconds: 60, - reconnect: { - initialMs: 2000, - maxMs: 120000, - factor: 1.4, - jitter: 0.2, - maxAttempts: 0, - }, - }, -} -``` - -### `channels.telegram` (bot transport) - -OpenClaw starts Telegram only when a `channels.telegram` config section exists. The bot token is resolved from `channels.telegram.botToken` (or `channels.telegram.tokenFile`), with `TELEGRAM_BOT_TOKEN` as a fallback for the default account. -Set `channels.telegram.enabled: false` to disable automatic startup. -Multi-account support lives under `channels.telegram.accounts` (see the multi-account section above). Env tokens only apply to the default account. -Set `channels.telegram.configWrites: false` to block Telegram-initiated config writes (including supergroup ID migrations and `/config set|unset`). - -```json5 -{ - channels: { - telegram: { - enabled: true, - botToken: "your-bot-token", - dmPolicy: "pairing", // pairing | allowlist | open | disabled - allowFrom: ["tg:123456789"], // optional; "open" requires ["*"] - groups: { - "*": { requireMention: true }, - "-1001234567890": { - allowFrom: ["@admin"], - systemPrompt: "Keep answers brief.", - topics: { - "99": { - requireMention: false, - skills: ["search"], - systemPrompt: "Stay on topic.", - }, - }, - }, - }, - customCommands: [ - { command: "backup", description: "Git backup" }, - { command: "generate", description: "Create an image" }, - ], - historyLimit: 50, // include last N group messages as context (0 disables) - replyToMode: "first", // off | first | all - linkPreview: true, // toggle outbound link previews - streamMode: "partial", // off | partial | block (draft streaming; separate from block streaming) - draftChunk: { - // optional; only for streamMode=block - minChars: 200, - maxChars: 800, - breakPreference: "paragraph", // paragraph | newline | sentence - }, - actions: { reactions: true, sendMessage: true }, // tool action gates (false disables) - reactionNotifications: "own", // off | own | all - mediaMaxMb: 5, - retry: { - // outbound retry policy - attempts: 3, - minDelayMs: 400, - maxDelayMs: 30000, - jitter: 0.1, - }, - network: { - // transport overrides - autoSelectFamily: false, - }, - proxy: "socks5://localhost:9050", - webhookUrl: "https://example.com/telegram-webhook", // requires webhookSecret - webhookSecret: "secret", - webhookPath: "/telegram-webhook", - }, - }, -} -``` - -Draft streaming notes: - -- Uses Telegram `sendMessageDraft` (draft bubble, not a real message). -- Requires **private chat topics** (message_thread_id in DMs; bot has topics enabled). -- `/reasoning stream` streams reasoning into the draft, then sends the final answer. - Retry policy defaults and behavior are documented in [Retry policy](/concepts/retry). - -### `channels.discord` (bot transport) - -Configure the Discord bot by setting the bot token and optional gating: -Multi-account support lives under `channels.discord.accounts` (see the multi-account section above). Env tokens only apply to the default account. - -```json5 -{ - channels: { - discord: { - enabled: true, - token: "your-bot-token", - mediaMaxMb: 8, // clamp inbound media size - allowBots: false, // allow bot-authored messages - actions: { - // tool action gates (false disables) - reactions: true, - stickers: true, - polls: true, - permissions: true, - messages: true, - threads: true, - pins: true, - search: true, - memberInfo: true, - roleInfo: true, - roles: false, - channelInfo: true, - voiceStatus: true, - events: true, - moderation: false, - }, - replyToMode: "off", // off | first | all - dm: { - enabled: true, // disable all DMs when false - policy: "pairing", // pairing | allowlist | open | disabled - allowFrom: ["1234567890", "steipete"], // optional DM allowlist ("open" requires ["*"]) - groupEnabled: false, // enable group DMs - groupChannels: ["openclaw-dm"], // optional group DM allowlist - }, - guilds: { - "123456789012345678": { - // guild id (preferred) or slug - slug: "friends-of-openclaw", - requireMention: false, // per-guild default - reactionNotifications: "own", // off | own | all | allowlist - users: ["987654321098765432"], // optional per-guild user allowlist - channels: { - general: { allow: true }, - help: { - allow: true, - requireMention: true, - users: ["987654321098765432"], - skills: ["docs"], - systemPrompt: "Short answers only.", - }, - }, - }, - }, - historyLimit: 20, // include last N guild messages as context - textChunkLimit: 2000, // optional outbound text chunk size (chars) - chunkMode: "length", // optional chunking mode (length | newline) - maxLinesPerMessage: 17, // soft max lines per message (Discord UI clipping) - retry: { - // outbound retry policy - attempts: 3, - minDelayMs: 500, - maxDelayMs: 30000, - jitter: 0.1, - }, - }, - }, -} -``` - -OpenClaw starts Discord only when a `channels.discord` config section exists. The token is resolved from `channels.discord.token`, with `DISCORD_BOT_TOKEN` as a fallback for the default account (unless `channels.discord.enabled` is `false`). Use `user:` (DM) or `channel:` (guild channel) when specifying delivery targets for cron/CLI commands; bare numeric IDs are ambiguous and rejected. -Guild slugs are lowercase with spaces replaced by `-`; channel keys use the slugged channel name (no leading `#`). Prefer guild ids as keys to avoid rename ambiguity. -Bot-authored messages are ignored by default. Enable with `channels.discord.allowBots` (own messages are still filtered to prevent self-reply loops). -Reaction notification modes: - -- `off`: no reaction events. -- `own`: reactions on the bot's own messages (default). -- `all`: all reactions on all messages. -- `allowlist`: reactions from `guilds..users` on all messages (empty list disables). - Outbound text is chunked by `channels.discord.textChunkLimit` (default 2000). Set `channels.discord.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking. Discord clients can clip very tall messages, so `channels.discord.maxLinesPerMessage` (default 17) splits long multi-line replies even when under 2000 chars. - Retry policy defaults and behavior are documented in [Retry policy](/concepts/retry). - -### `channels.googlechat` (Chat API webhook) - -Google Chat runs over HTTP webhooks with app-level auth (service account). -Multi-account support lives under `channels.googlechat.accounts` (see the multi-account section above). Env vars only apply to the default account. - -```json5 -{ - channels: { - googlechat: { - enabled: true, - serviceAccountFile: "/path/to/service-account.json", - audienceType: "app-url", // app-url | project-number - audience: "https://gateway.example.com/googlechat", - webhookPath: "/googlechat", - botUser: "users/1234567890", // optional; improves mention detection - dm: { - enabled: true, - policy: "pairing", // pairing | allowlist | open | disabled - allowFrom: ["users/1234567890"], // optional; "open" requires ["*"] - }, - groupPolicy: "allowlist", - groups: { - "spaces/AAAA": { allow: true, requireMention: true }, - }, - actions: { reactions: true }, - typingIndicator: "message", - mediaMaxMb: 20, - }, - }, -} -``` - -Notes: - -- Service account JSON can be inline (`serviceAccount`) or file-based (`serviceAccountFile`). -- Env fallbacks for the default account: `GOOGLE_CHAT_SERVICE_ACCOUNT` or `GOOGLE_CHAT_SERVICE_ACCOUNT_FILE`. -- `audienceType` + `audience` must match the Chat app’s webhook auth config. -- Use `spaces/` or `users/` when setting delivery targets. - -### `channels.slack` (socket mode) - -Slack runs in Socket Mode and requires both a bot token and app token: - -```json5 -{ - channels: { - slack: { - enabled: true, - botToken: "xoxb-...", - appToken: "xapp-...", - dm: { - enabled: true, - policy: "pairing", // pairing | allowlist | open | disabled - allowFrom: ["U123", "U456", "*"], // optional; "open" requires ["*"] - groupEnabled: false, - groupChannels: ["G123"], - }, - channels: { - C123: { allow: true, requireMention: true, allowBots: false }, - "#general": { - allow: true, - requireMention: true, - allowBots: false, - users: ["U123"], - skills: ["docs"], - systemPrompt: "Short answers only.", - }, - }, - historyLimit: 50, // include last N channel/group messages as context (0 disables) - allowBots: false, - reactionNotifications: "own", // off | own | all | allowlist - reactionAllowlist: ["U123"], - replyToMode: "off", // off | first | all - thread: { - historyScope: "thread", // thread | channel - inheritParent: false, - }, - actions: { - reactions: true, - messages: true, - pins: true, - memberInfo: true, - emojiList: true, - }, - slashCommand: { - enabled: true, - name: "openclaw", - sessionPrefix: "slack:slash", - ephemeral: true, - }, - textChunkLimit: 4000, - chunkMode: "length", - mediaMaxMb: 20, - }, - }, -} -``` - -Multi-account support lives under `channels.slack.accounts` (see the multi-account section above). Env tokens only apply to the default account. - -OpenClaw starts Slack when the provider is enabled and both tokens are set (via config or `SLACK_BOT_TOKEN` + `SLACK_APP_TOKEN`). Use `user:` (DM) or `channel:` when specifying delivery targets for cron/CLI commands. -Set `channels.slack.configWrites: false` to block Slack-initiated config writes (including channel ID migrations and `/config set|unset`). - -Bot-authored messages are ignored by default. Enable with `channels.slack.allowBots` or `channels.slack.channels..allowBots`. - -Reaction notification modes: - -- `off`: no reaction events. -- `own`: reactions on the bot's own messages (default). -- `all`: all reactions on all messages. -- `allowlist`: reactions from `channels.slack.reactionAllowlist` on all messages (empty list disables). - -Thread session isolation: - -- `channels.slack.thread.historyScope` controls whether thread history is per-thread (`thread`, default) or shared across the channel (`channel`). -- `channels.slack.thread.inheritParent` controls whether new thread sessions inherit the parent channel transcript (default: false). - -Slack action groups (gate `slack` tool actions): - -| Action group | Default | Notes | -| ------------ | ------- | ---------------------- | -| reactions | enabled | React + list reactions | -| messages | enabled | Read/send/edit/delete | -| pins | enabled | Pin/unpin/list | -| memberInfo | enabled | Member info | -| emojiList | enabled | Custom emoji list | - -### `channels.mattermost` (bot token) - -Mattermost ships as a plugin and is not bundled with the core install. -Install it first: `openclaw plugins install @openclaw/mattermost` (or `./extensions/mattermost` from a git checkout). - -Mattermost requires a bot token plus the base URL for your server: - -```json5 -{ - channels: { - mattermost: { - enabled: true, - botToken: "mm-token", - baseUrl: "https://chat.example.com", - dmPolicy: "pairing", - chatmode: "oncall", // oncall | onmessage | onchar - oncharPrefixes: [">", "!"], - textChunkLimit: 4000, - chunkMode: "length", - }, - }, -} -``` - -OpenClaw starts Mattermost when the account is configured (bot token + base URL) and enabled. The token + base URL are resolved from `channels.mattermost.botToken` + `channels.mattermost.baseUrl` or `MATTERMOST_BOT_TOKEN` + `MATTERMOST_URL` for the default account (unless `channels.mattermost.enabled` is `false`). - -Chat modes: - -- `oncall` (default): respond to channel messages only when @mentioned. -- `onmessage`: respond to every channel message. -- `onchar`: respond when a message starts with a trigger prefix (`channels.mattermost.oncharPrefixes`, default `[">", "!"]`). - -Access control: - -- Default DMs: `channels.mattermost.dmPolicy="pairing"` (unknown senders get a pairing code). -- Public DMs: `channels.mattermost.dmPolicy="open"` plus `channels.mattermost.allowFrom=["*"]`. -- Groups: `channels.mattermost.groupPolicy="allowlist"` by default (mention-gated). Use `channels.mattermost.groupAllowFrom` to restrict senders. - -Multi-account support lives under `channels.mattermost.accounts` (see the multi-account section above). Env vars only apply to the default account. -Use `channel:` or `user:` (or `@username`) when specifying delivery targets; bare ids are treated as channel ids. - -### `channels.signal` (signal-cli) - -Signal reactions can emit system events (shared reaction tooling): - -```json5 -{ - channels: { - signal: { - reactionNotifications: "own", // off | own | all | allowlist - reactionAllowlist: ["+15551234567", "uuid:123e4567-e89b-12d3-a456-426614174000"], - historyLimit: 50, // include last N group messages as context (0 disables) - }, - }, -} -``` - -Reaction notification modes: - -- `off`: no reaction events. -- `own`: reactions on the bot's own messages (default). -- `all`: all reactions on all messages. -- `allowlist`: reactions from `channels.signal.reactionAllowlist` on all messages (empty list disables). - -### `channels.imessage` (imsg CLI) - -OpenClaw spawns `imsg rpc` (JSON-RPC over stdio). No daemon or port required. - -```json5 -{ - channels: { - imessage: { - enabled: true, - cliPath: "imsg", - dbPath: "~/Library/Messages/chat.db", - remoteHost: "user@gateway-host", // SCP for remote attachments when using SSH wrapper - dmPolicy: "pairing", // pairing | allowlist | open | disabled - allowFrom: ["+15555550123", "user@example.com", "chat_id:123"], - historyLimit: 50, // include last N group messages as context (0 disables) - includeAttachments: false, - mediaMaxMb: 16, - service: "auto", - region: "US", - }, - }, -} -``` - -Multi-account support lives under `channels.imessage.accounts` (see the multi-account section above). - -Notes: - -- Requires Full Disk Access to the Messages DB. -- The first send will prompt for Messages automation permission. -- Prefer `chat_id:` targets. Use `imsg chats --limit 20` to list chats. -- `channels.imessage.cliPath` can point to a wrapper script (e.g. `ssh` to another Mac that runs `imsg rpc`); use SSH keys to avoid password prompts. -- For remote SSH wrappers, set `channels.imessage.remoteHost` to fetch attachments via SCP when `includeAttachments` is enabled. - -Example wrapper: - -```bash -#!/usr/bin/env bash -exec ssh -T gateway-host imsg "$@" -``` - -### `agents.defaults.workspace` - -Sets the **single global workspace directory** used by the agent for file operations. - -Default: `~/.openclaw/workspace`. - -```json5 -{ - agents: { defaults: { workspace: "~/.openclaw/workspace" } }, -} -``` - -If `agents.defaults.sandbox` is enabled, non-main sessions can override this with their -own per-scope workspaces under `agents.defaults.sandbox.workspaceRoot`. - -### `agents.defaults.repoRoot` - -Optional repository root to show in the system prompt’s Runtime line. If unset, OpenClaw -tries to detect a `.git` directory by walking upward from the workspace (and current -working directory). The path must exist to be used. - -```json5 -{ - agents: { defaults: { repoRoot: "~/Projects/openclaw" } }, -} -``` - -### `agents.defaults.skipBootstrap` - -Disables automatic creation of the workspace bootstrap files (`AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, and `BOOTSTRAP.md`). - -Use this for pre-seeded deployments where your workspace files come from a repo. - -```json5 -{ - agents: { defaults: { skipBootstrap: true } }, -} -``` - -### `agents.defaults.bootstrapMaxChars` - -Max characters of each workspace bootstrap file injected into the system prompt -before truncation. Default: `20000`. - -When a file exceeds this limit, OpenClaw logs a warning and injects a truncated -head/tail with a marker. - -```json5 -{ - agents: { defaults: { bootstrapMaxChars: 20000 } }, -} -``` - -### `agents.defaults.userTimezone` - -Sets the user’s timezone for **system prompt context** (not for timestamps in -message envelopes). If unset, OpenClaw uses the host timezone at runtime. - -```json5 -{ - agents: { defaults: { userTimezone: "America/Chicago" } }, -} -``` - -### `agents.defaults.timeFormat` - -Controls the **time format** shown in the system prompt’s Current Date & Time section. -Default: `auto` (OS preference). - -```json5 -{ - agents: { defaults: { timeFormat: "auto" } }, // auto | 12 | 24 -} -``` - -### `messages` - -Controls inbound/outbound prefixes and optional ack reactions. -See [Messages](/concepts/messages) for queueing, sessions, and streaming context. - -```json5 -{ - messages: { - responsePrefix: "🦞", // or "auto" - ackReaction: "👀", - ackReactionScope: "group-mentions", - removeAckAfterReply: false, - }, -} -``` - -`responsePrefix` is applied to **all outbound replies** (tool summaries, block -streaming, final replies) across channels unless already present. - -Overrides can be configured per channel and per account: - -- `channels..responsePrefix` -- `channels..accounts..responsePrefix` - -Resolution order (most specific wins): - -1. `channels..accounts..responsePrefix` -2. `channels..responsePrefix` -3. `messages.responsePrefix` - -Semantics: - -- `undefined` falls through to the next level. -- `""` explicitly disables the prefix and stops the cascade. -- `"auto"` derives `[{identity.name}]` for the routed agent. - -Overrides apply to all channels, including extensions, and to every outbound reply kind. - -If `messages.responsePrefix` is unset, no prefix is applied by default. WhatsApp self-chat -replies are the exception: they default to `[{identity.name}]` when set, otherwise -`[openclaw]`, so same-phone conversations stay legible. -Set it to `"auto"` to derive `[{identity.name}]` for the routed agent (when set). - -#### Template variables - -The `responsePrefix` string can include template variables that resolve dynamically: - -| Variable | Description | Example | -| ----------------- | ---------------------- | --------------------------- | -| `{model}` | Short model name | `claude-opus-4-6`, `gpt-4o` | -| `{modelFull}` | Full model identifier | `anthropic/claude-opus-4-6` | -| `{provider}` | Provider name | `anthropic`, `openai` | -| `{thinkingLevel}` | Current thinking level | `high`, `low`, `off` | -| `{identity.name}` | Agent identity name | (same as `"auto"` mode) | - -Variables are case-insensitive (`{MODEL}` = `{model}`). `{think}` is an alias for `{thinkingLevel}`. -Unresolved variables remain as literal text. - -```json5 -{ - messages: { - responsePrefix: "[{model} | think:{thinkingLevel}]", - }, -} -``` - -Example output: `[claude-opus-4-6 | think:high] Here's my response...` - -WhatsApp inbound prefix is configured via `channels.whatsapp.messagePrefix` (deprecated: -`messages.messagePrefix`). Default stays **unchanged**: `"[openclaw]"` when -`channels.whatsapp.allowFrom` is empty, otherwise `""` (no prefix). When using -`"[openclaw]"`, OpenClaw will instead use `[{identity.name}]` when the routed -agent has `identity.name` set. - -`ackReaction` sends a best-effort emoji reaction to acknowledge inbound messages -on channels that support reactions (Slack/Discord/Telegram/Google Chat). Defaults to the -active agent’s `identity.emoji` when set, otherwise `"👀"`. Set it to `""` to disable. - -`ackReactionScope` controls when reactions fire: - -- `group-mentions` (default): only when a group/room requires mentions **and** the bot was mentioned -- `group-all`: all group/room messages -- `direct`: direct messages only -- `all`: all messages - -`removeAckAfterReply` removes the bot’s ack reaction after a reply is sent -(Slack/Discord/Telegram/Google Chat only). Default: `false`. - -#### `messages.tts` - -Enable text-to-speech for outbound replies. When on, OpenClaw generates audio -using ElevenLabs or OpenAI and attaches it to responses. Telegram uses Opus -voice notes; other channels send MP3 audio. - -```json5 -{ - messages: { - tts: { - auto: "always", // off | always | inbound | tagged - mode: "final", // final | all (include tool/block replies) - provider: "elevenlabs", - summaryModel: "openai/gpt-4.1-mini", - modelOverrides: { - enabled: true, - }, - maxTextLength: 4000, - timeoutMs: 30000, - prefsPath: "~/.openclaw/settings/tts.json", - elevenlabs: { - apiKey: "elevenlabs_api_key", - baseUrl: "https://api.elevenlabs.io", - voiceId: "voice_id", - modelId: "eleven_multilingual_v2", - seed: 42, - applyTextNormalization: "auto", - languageCode: "en", - voiceSettings: { - stability: 0.5, - similarityBoost: 0.75, - style: 0.0, - useSpeakerBoost: true, - speed: 1.0, - }, - }, - openai: { - apiKey: "openai_api_key", - model: "gpt-4o-mini-tts", - voice: "alloy", - }, - }, - }, -} -``` - -Notes: - -- `messages.tts.auto` controls auto‑TTS (`off`, `always`, `inbound`, `tagged`). -- `/tts off|always|inbound|tagged` sets the per‑session auto mode (overrides config). -- `messages.tts.enabled` is legacy; doctor migrates it to `messages.tts.auto`. -- `prefsPath` stores local overrides (provider/limit/summarize). -- `maxTextLength` is a hard cap for TTS input; summaries are truncated to fit. -- `summaryModel` overrides `agents.defaults.model.primary` for auto-summary. - - Accepts `provider/model` or an alias from `agents.defaults.models`. -- `modelOverrides` enables model-driven overrides like `[[tts:...]]` tags (on by default). -- `/tts limit` and `/tts summary` control per-user summarization settings. -- `apiKey` values fall back to `ELEVENLABS_API_KEY`/`XI_API_KEY` and `OPENAI_API_KEY`. -- `elevenlabs.baseUrl` overrides the ElevenLabs API base URL. -- `elevenlabs.voiceSettings` supports `stability`/`similarityBoost`/`style` (0..1), - `useSpeakerBoost`, and `speed` (0.5..2.0). - -### `talk` - -Defaults for Talk mode (macOS/iOS/Android). Voice IDs fall back to `ELEVENLABS_VOICE_ID` or `SAG_VOICE_ID` when unset. -`apiKey` falls back to `ELEVENLABS_API_KEY` (or the gateway’s shell profile) when unset. -`voiceAliases` lets Talk directives use friendly names (e.g. `"voice":"Clawd"`). - -```json5 -{ - talk: { - voiceId: "elevenlabs_voice_id", - voiceAliases: { - Clawd: "EXAVITQu4vr4xnSDxMaL", - Roger: "CwhRBWXzGAHq8TQ4Fs17", - }, - modelId: "eleven_v3", - outputFormat: "mp3_44100_128", - apiKey: "elevenlabs_api_key", - interruptOnSpeech: true, - }, -} -``` - -### `agents.defaults` - -Controls the embedded agent runtime (model/thinking/verbose/timeouts). -`agents.defaults.models` defines the configured model catalog (and acts as the allowlist for `/model`). -`agents.defaults.model.primary` sets the default model; `agents.defaults.model.fallbacks` are global failovers. -`agents.defaults.imageModel` is optional and is **only used if the primary model lacks image input**. -Each `agents.defaults.models` entry can include: - -- `alias` (optional model shortcut, e.g. `/opus`). -- `params` (optional provider-specific API params passed through to the model request). - -`params` is also applied to streaming runs (embedded agent + compaction). Supported keys today: `temperature`, `maxTokens`. These merge with call-time options; caller-supplied values win. `temperature` is an advanced knob—leave unset unless you know the model’s defaults and need a change. - -Example: - -```json5 -{ - agents: { - defaults: { - models: { - "anthropic/claude-sonnet-4-5-20250929": { - params: { temperature: 0.6 }, - }, - "openai/gpt-5.2": { - params: { maxTokens: 8192 }, - }, - }, - }, - }, -} -``` - -Z.AI GLM-4.x models automatically enable thinking mode unless you: - -- set `--thinking off`, or -- define `agents.defaults.models["zai/"].params.thinking` yourself. - -OpenClaw also ships a few built-in alias shorthands. Defaults only apply when the model -is already present in `agents.defaults.models`: - -- `opus` -> `anthropic/claude-opus-4-6` -- `sonnet` -> `anthropic/claude-sonnet-4-5` -- `gpt` -> `openai/gpt-5.2` -- `gpt-mini` -> `openai/gpt-5-mini` -- `gemini` -> `google/gemini-3-pro-preview` -- `gemini-flash` -> `google/gemini-3-flash-preview` - -If you configure the same alias name (case-insensitive) yourself, your value wins (defaults never override). - -Example: Opus 4.6 primary with MiniMax M2.1 fallback (hosted MiniMax): - -```json5 -{ - agents: { - defaults: { - models: { - "anthropic/claude-opus-4-6": { alias: "opus" }, - "minimax/MiniMax-M2.1": { alias: "minimax" }, - }, - model: { - primary: "anthropic/claude-opus-4-6", - fallbacks: ["minimax/MiniMax-M2.1"], - }, - }, - }, -} -``` - -MiniMax auth: set `MINIMAX_API_KEY` (env) or configure `models.providers.minimax`. - -#### `agents.defaults.cliBackends` (CLI fallback) - -Optional CLI backends for text-only fallback runs (no tool calls). These are useful as a -backup path when API providers fail. Image pass-through is supported when you configure -an `imageArg` that accepts file paths. - -Notes: - -- CLI backends are **text-first**; tools are always disabled. -- Sessions are supported when `sessionArg` is set; session ids are persisted per backend. -- For `claude-cli`, defaults are wired in. Override the command path if PATH is minimal - (launchd/systemd). - -Example: - -```json5 -{ - agents: { - defaults: { - cliBackends: { - "claude-cli": { - command: "/opt/homebrew/bin/claude", - }, - "my-cli": { - command: "my-cli", - args: ["--json"], - output: "json", - modelArg: "--model", - sessionArg: "--session", - sessionMode: "existing", - systemPromptArg: "--system", - systemPromptWhen: "first", - imageArg: "--image", - imageMode: "repeat", - }, - }, - }, - }, -} -``` - -```json5 -{ - agents: { - defaults: { - models: { - "anthropic/claude-opus-4-6": { alias: "Opus" }, - "anthropic/claude-sonnet-4-1": { alias: "Sonnet" }, - "openrouter/deepseek/deepseek-r1:free": {}, - "zai/glm-4.7": { - alias: "GLM", - params: { - thinking: { - type: "enabled", - clear_thinking: false, - }, - }, - }, - }, - model: { - primary: "anthropic/claude-opus-4-6", - fallbacks: [ - "openrouter/deepseek/deepseek-r1:free", - "openrouter/meta-llama/llama-3.3-70b-instruct:free", - ], - }, - imageModel: { - primary: "openrouter/qwen/qwen-2.5-vl-72b-instruct:free", - fallbacks: ["openrouter/google/gemini-2.0-flash-vision:free"], - }, - thinkingDefault: "low", - verboseDefault: "off", - elevatedDefault: "on", - timeoutSeconds: 600, - mediaMaxMb: 5, - heartbeat: { - every: "30m", - target: "last", - }, - maxConcurrent: 3, - subagents: { - model: "minimax/MiniMax-M2.1", - maxConcurrent: 1, - archiveAfterMinutes: 60, - }, - exec: { - backgroundMs: 10000, - timeoutSec: 1800, - cleanupMs: 1800000, - }, - contextTokens: 200000, - }, - }, -} -``` - -#### `agents.defaults.contextPruning` (tool-result pruning) - -`agents.defaults.contextPruning` prunes **old tool results** from the in-memory context right before a request is sent to the LLM. -It does **not** modify the session history on disk (`*.jsonl` remains complete). - -This is intended to reduce token usage for chatty agents that accumulate large tool outputs over time. - -High level: - -- Never touches user/assistant messages. -- Protects the last `keepLastAssistants` assistant messages (no tool results after that point are pruned). -- Protects the bootstrap prefix (nothing before the first user message is pruned). -- Modes: - - `adaptive`: soft-trims oversized tool results (keep head/tail) when the estimated context ratio crosses `softTrimRatio`. - Then hard-clears the oldest eligible tool results when the estimated context ratio crosses `hardClearRatio` **and** - there’s enough prunable tool-result bulk (`minPrunableToolChars`). - - `aggressive`: always replaces eligible tool results before the cutoff with the `hardClear.placeholder` (no ratio checks). - -Soft vs hard pruning (what changes in the context sent to the LLM): - -- **Soft-trim**: only for _oversized_ tool results. Keeps the beginning + end and inserts `...` in the middle. - - Before: `toolResult("…very long output…")` - - After: `toolResult("HEAD…\n...\n…TAIL\n\n[Tool result trimmed: …]")` -- **Hard-clear**: replaces the entire tool result with the placeholder. - - Before: `toolResult("…very long output…")` - - After: `toolResult("[Old tool result content cleared]")` - -Notes / current limitations: - -- Tool results containing **image blocks are skipped** (never trimmed/cleared) right now. -- The estimated “context ratio” is based on **characters** (approximate), not exact tokens. -- If the session doesn’t contain at least `keepLastAssistants` assistant messages yet, pruning is skipped. -- In `aggressive` mode, `hardClear.enabled` is ignored (eligible tool results are always replaced with `hardClear.placeholder`). - -Default (adaptive): - -```json5 -{ - agents: { defaults: { contextPruning: { mode: "adaptive" } } }, -} -``` - -To disable: - -```json5 -{ - agents: { defaults: { contextPruning: { mode: "off" } } }, -} -``` - -Defaults (when `mode` is `"adaptive"` or `"aggressive"`): - -- `keepLastAssistants`: `3` -- `softTrimRatio`: `0.3` (adaptive only) -- `hardClearRatio`: `0.5` (adaptive only) -- `minPrunableToolChars`: `50000` (adaptive only) -- `softTrim`: `{ maxChars: 4000, headChars: 1500, tailChars: 1500 }` (adaptive only) -- `hardClear`: `{ enabled: true, placeholder: "[Old tool result content cleared]" }` - -Example (aggressive, minimal): - -```json5 -{ - agents: { defaults: { contextPruning: { mode: "aggressive" } } }, -} -``` - -Example (adaptive tuned): - -```json5 -{ - agents: { - defaults: { - contextPruning: { - mode: "adaptive", - keepLastAssistants: 3, - softTrimRatio: 0.3, - hardClearRatio: 0.5, - minPrunableToolChars: 50000, - softTrim: { maxChars: 4000, headChars: 1500, tailChars: 1500 }, - hardClear: { enabled: true, placeholder: "[Old tool result content cleared]" }, - // Optional: restrict pruning to specific tools (deny wins; supports "*" wildcards) - tools: { deny: ["browser", "canvas"] }, - }, - }, - }, -} -``` - -See [/concepts/session-pruning](/concepts/session-pruning) for behavior details. - -#### `agents.defaults.compaction` (reserve headroom + memory flush) - -`agents.defaults.compaction.mode` selects the compaction summarization strategy. Defaults to `default`; set `safeguard` to enable chunked summarization for very long histories. See [/concepts/compaction](/concepts/compaction). - -`agents.defaults.compaction.reserveTokensFloor` enforces a minimum `reserveTokens` -value for Pi compaction (default: `20000`). Set it to `0` to disable the floor. - -`agents.defaults.compaction.memoryFlush` runs a **silent** agentic turn before -auto-compaction, instructing the model to store durable memories on disk (e.g. -`memory/YYYY-MM-DD.md`). It triggers when the session token estimate crosses a -soft threshold below the compaction limit. - -Legacy defaults: - -- `memoryFlush.enabled`: `true` -- `memoryFlush.softThresholdTokens`: `4000` -- `memoryFlush.prompt` / `memoryFlush.systemPrompt`: built-in defaults with `NO_REPLY` -- Note: memory flush is skipped when the session workspace is read-only - (`agents.defaults.sandbox.workspaceAccess: "ro"` or `"none"`). - -Example (tuned): - -```json5 -{ - agents: { - defaults: { - compaction: { - mode: "safeguard", - reserveTokensFloor: 24000, - memoryFlush: { - enabled: true, - softThresholdTokens: 6000, - systemPrompt: "Session nearing compaction. Store durable memories now.", - prompt: "Write any lasting notes to memory/YYYY-MM-DD.md; reply with NO_REPLY if nothing to store.", - }, - }, - }, - }, -} -``` - -Block streaming: - -- `agents.defaults.blockStreamingDefault`: `"on"`/`"off"` (default off). -- Channel overrides: `*.blockStreaming` (and per-account variants) to force block streaming on/off. - Non-Telegram channels require an explicit `*.blockStreaming: true` to enable block replies. -- `agents.defaults.blockStreamingBreak`: `"text_end"` or `"message_end"` (default: text_end). -- `agents.defaults.blockStreamingChunk`: soft chunking for streamed blocks. Defaults to - 800–1200 chars, prefers paragraph breaks (`\n\n`), then newlines, then sentences. - Example: - - ```json5 - { - agents: { defaults: { blockStreamingChunk: { minChars: 800, maxChars: 1200 } } }, - } - ``` - -- `agents.defaults.blockStreamingCoalesce`: merge streamed blocks before sending. - Defaults to `{ idleMs: 1000 }` and inherits `minChars` from `blockStreamingChunk` - with `maxChars` capped to the channel text limit. Signal/Slack/Discord/Google Chat default - to `minChars: 1500` unless overridden. - Channel overrides: `channels.whatsapp.blockStreamingCoalesce`, `channels.telegram.blockStreamingCoalesce`, - `channels.discord.blockStreamingCoalesce`, `channels.slack.blockStreamingCoalesce`, `channels.mattermost.blockStreamingCoalesce`, - `channels.signal.blockStreamingCoalesce`, `channels.imessage.blockStreamingCoalesce`, `channels.msteams.blockStreamingCoalesce`, - `channels.googlechat.blockStreamingCoalesce` - (and per-account variants). -- `agents.defaults.humanDelay`: randomized pause between **block replies** after the first. - Modes: `off` (default), `natural` (800–2500ms), `custom` (use `minMs`/`maxMs`). - Per-agent override: `agents.list[].humanDelay`. - Example: - - ```json5 - { - agents: { defaults: { humanDelay: { mode: "natural" } } }, - } - ``` - - See [/concepts/streaming](/concepts/streaming) for behavior + chunking details. - -Typing indicators: - -- `agents.defaults.typingMode`: `"never" | "instant" | "thinking" | "message"`. Defaults to - `instant` for direct chats / mentions and `message` for unmentioned group chats. -- `session.typingMode`: per-session override for the mode. -- `agents.defaults.typingIntervalSeconds`: how often the typing signal is refreshed (default: 6s). -- `session.typingIntervalSeconds`: per-session override for the refresh interval. - See [/concepts/typing-indicators](/concepts/typing-indicators) for behavior details. - -`agents.defaults.model.primary` should be set as `provider/model` (e.g. `anthropic/claude-opus-4-6`). -Aliases come from `agents.defaults.models.*.alias` (e.g. `Opus`). -If you omit the provider, OpenClaw currently assumes `anthropic` as a temporary -deprecation fallback. -Z.AI models are available as `zai/` (e.g. `zai/glm-4.7`) and require -`ZAI_API_KEY` (or legacy `Z_AI_API_KEY`) in the environment. - -`agents.defaults.heartbeat` configures periodic heartbeat runs: - -- `every`: duration string (`ms`, `s`, `m`, `h`); default unit minutes. Default: - `30m`. Set `0m` to disable. -- `model`: optional override model for heartbeat runs (`provider/model`). -- `includeReasoning`: when `true`, heartbeats will also deliver the separate `Reasoning:` message when available (same shape as `/reasoning on`). Default: `false`. -- `session`: optional session key to control which session the heartbeat runs in. Default: `main`. -- `to`: optional recipient override (channel-specific id, e.g. E.164 for WhatsApp, chat id for Telegram). -- `target`: optional delivery channel (`last`, `whatsapp`, `telegram`, `discord`, `slack`, `msteams`, `signal`, `imessage`, `none`). Default: `last`. -- `prompt`: optional override for the heartbeat body (default: `Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.`). Overrides are sent verbatim; include a `Read HEARTBEAT.md` line if you still want the file read. -- `ackMaxChars`: max chars allowed after `HEARTBEAT_OK` before delivery (default: 300). - -Per-agent heartbeats: - -- Set `agents.list[].heartbeat` to enable or override heartbeat settings for a specific agent. -- If any agent entry defines `heartbeat`, **only those agents** run heartbeats; defaults - become the shared baseline for those agents. - -Heartbeats run full agent turns. Shorter intervals burn more tokens; be mindful -of `every`, keep `HEARTBEAT.md` tiny, and/or choose a cheaper `model`. - -`tools.exec` configures background exec defaults: - -- `backgroundMs`: time before auto-background (ms, default 10000) -- `timeoutSec`: auto-kill after this runtime (seconds, default 1800) -- `cleanupMs`: how long to keep finished sessions in memory (ms, default 1800000) -- `notifyOnExit`: enqueue a system event + request heartbeat when backgrounded exec exits (default true) -- `applyPatch.enabled`: enable experimental `apply_patch` (OpenAI/OpenAI Codex only; default false) -- `applyPatch.allowModels`: optional allowlist of model ids (e.g. `gpt-5.2` or `openai/gpt-5.2`) - Note: `applyPatch` is only under `tools.exec`. - -`tools.web` configures web search + fetch tools: - -- `tools.web.search.enabled` (default: true when key is present) -- `tools.web.search.apiKey` (recommended: set via `openclaw configure --section web`, or use `BRAVE_API_KEY` env var) -- `tools.web.search.maxResults` (1–10, default 5) -- `tools.web.search.timeoutSeconds` (default 30) -- `tools.web.search.cacheTtlMinutes` (default 15) -- `tools.web.fetch.enabled` (default true) -- `tools.web.fetch.maxChars` (default 50000) -- `tools.web.fetch.maxCharsCap` (default 50000; clamps maxChars from config/tool calls) -- `tools.web.fetch.timeoutSeconds` (default 30) -- `tools.web.fetch.cacheTtlMinutes` (default 15) -- `tools.web.fetch.userAgent` (optional override) -- `tools.web.fetch.readability` (default true; disable to use basic HTML cleanup only) -- `tools.web.fetch.firecrawl.enabled` (default true when an API key is set) -- `tools.web.fetch.firecrawl.apiKey` (optional; defaults to `FIRECRAWL_API_KEY`) -- `tools.web.fetch.firecrawl.baseUrl` (default [https://api.firecrawl.dev](https://api.firecrawl.dev)) -- `tools.web.fetch.firecrawl.onlyMainContent` (default true) -- `tools.web.fetch.firecrawl.maxAgeMs` (optional) -- `tools.web.fetch.firecrawl.timeoutSeconds` (optional) - -`tools.media` configures inbound media understanding (image/audio/video): - -- `tools.media.models`: shared model list (capability-tagged; used after per-cap lists). -- `tools.media.concurrency`: max concurrent capability runs (default 2). -- `tools.media.image` / `tools.media.audio` / `tools.media.video`: - - `enabled`: opt-out switch (default true when models are configured). - - `prompt`: optional prompt override (image/video append a `maxChars` hint automatically). - - `maxChars`: max output characters (default 500 for image/video; unset for audio). - - `maxBytes`: max media size to send (defaults: image 10MB, audio 20MB, video 50MB). - - `timeoutSeconds`: request timeout (defaults: image 60s, audio 60s, video 120s). - - `language`: optional audio hint. - - `attachments`: attachment policy (`mode`, `maxAttachments`, `prefer`). - - `scope`: optional gating (first match wins) with `match.channel`, `match.chatType`, or `match.keyPrefix`. - - `models`: ordered list of model entries; failures or oversize media fall back to the next entry. -- Each `models[]` entry: - - Provider entry (`type: "provider"` or omitted): - - `provider`: API provider id (`openai`, `anthropic`, `google`/`gemini`, `groq`, etc). - - `model`: model id override (required for image; defaults to `gpt-4o-mini-transcribe`/`whisper-large-v3-turbo` for audio providers, and `gemini-3-flash-preview` for video). - - `profile` / `preferredProfile`: auth profile selection. - - CLI entry (`type: "cli"`): - - `command`: executable to run. - - `args`: templated args (supports `{{MediaPath}}`, `{{Prompt}}`, `{{MaxChars}}`, etc). - - `capabilities`: optional list (`image`, `audio`, `video`) to gate a shared entry. Defaults when omitted: `openai`/`anthropic`/`minimax` → image, `google` → image+audio+video, `groq` → audio. - - `prompt`, `maxChars`, `maxBytes`, `timeoutSeconds`, `language` can be overridden per entry. - -If no models are configured (or `enabled: false`), understanding is skipped; the model still receives the original attachments. - -Provider auth follows the standard model auth order (auth profiles, env vars like `OPENAI_API_KEY`/`GROQ_API_KEY`/`GEMINI_API_KEY`, or `models.providers.*.apiKey`). - -Example: - -```json5 -{ - tools: { - media: { - audio: { - enabled: true, - maxBytes: 20971520, - scope: { - default: "deny", - rules: [{ action: "allow", match: { chatType: "direct" } }], - }, - models: [ - { provider: "openai", model: "gpt-4o-mini-transcribe" }, - { type: "cli", command: "whisper", args: ["--model", "base", "{{MediaPath}}"] }, - ], - }, - video: { - enabled: true, - maxBytes: 52428800, - models: [{ provider: "google", model: "gemini-3-flash-preview" }], - }, - }, - }, -} -``` - -`agents.defaults.subagents` configures sub-agent defaults: - -- `model`: default model for spawned sub-agents (string or `{ primary, fallbacks }`). If omitted, sub-agents inherit the caller’s model unless overridden per agent or per call. -- `maxConcurrent`: max concurrent sub-agent runs (default 1) -- `archiveAfterMinutes`: auto-archive sub-agent sessions after N minutes (default 60; set `0` to disable) -- Per-subagent tool policy: `tools.subagents.tools.allow` / `tools.subagents.tools.deny` (deny wins) - -`tools.profile` sets a **base tool allowlist** before `tools.allow`/`tools.deny`: - -- `minimal`: `session_status` only -- `coding`: `group:fs`, `group:runtime`, `group:sessions`, `group:memory`, `image` -- `messaging`: `group:messaging`, `sessions_list`, `sessions_history`, `sessions_send`, `session_status` -- `full`: no restriction (same as unset) - -Per-agent override: `agents.list[].tools.profile`. - -Example (messaging-only by default, allow Slack + Discord tools too): - -```json5 -{ - tools: { - profile: "messaging", - allow: ["slack", "discord"], - }, -} -``` - -Example (coding profile, but deny exec/process everywhere): - -```json5 -{ - tools: { - profile: "coding", - deny: ["group:runtime"], - }, -} -``` - -`tools.byProvider` lets you **further restrict** tools for specific providers (or a single `provider/model`). -Per-agent override: `agents.list[].tools.byProvider`. - -Order: base profile → provider profile → allow/deny policies. -Provider keys accept either `provider` (e.g. `google-antigravity`) or `provider/model` -(e.g. `openai/gpt-5.2`). - -Example (keep global coding profile, but minimal tools for Google Antigravity): - -```json5 -{ - tools: { - profile: "coding", - byProvider: { - "google-antigravity": { profile: "minimal" }, - }, - }, -} -``` - -Example (provider/model-specific allowlist): - -```json5 -{ - tools: { - allow: ["group:fs", "group:runtime", "sessions_list"], - byProvider: { - "openai/gpt-5.2": { allow: ["group:fs", "sessions_list"] }, - }, - }, -} -``` - -`tools.allow` / `tools.deny` configure a global tool allow/deny policy (deny wins). -Matching is case-insensitive and supports `*` wildcards (`"*"` means all tools). -This is applied even when the Docker sandbox is **off**. - -Example (disable browser/canvas everywhere): - -```json5 -{ - tools: { deny: ["browser", "canvas"] }, -} -``` - -Tool groups (shorthands) work in **global** and **per-agent** tool policies: - -- `group:runtime`: `exec`, `bash`, `process` -- `group:fs`: `read`, `write`, `edit`, `apply_patch` -- `group:sessions`: `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn`, `session_status` -- `group:memory`: `memory_search`, `memory_get` -- `group:web`: `web_search`, `web_fetch` -- `group:ui`: `browser`, `canvas` -- `group:automation`: `cron`, `gateway` -- `group:messaging`: `message` -- `group:nodes`: `nodes` -- `group:openclaw`: all built-in OpenClaw tools (excludes provider plugins) - -`tools.elevated` controls elevated (host) exec access: - -- `enabled`: allow elevated mode (default true) -- `allowFrom`: per-channel allowlists (empty = disabled) - - `whatsapp`: E.164 numbers - - `telegram`: chat ids or usernames - - `discord`: user ids or usernames (falls back to `channels.discord.dm.allowFrom` if omitted) - - `signal`: E.164 numbers - - `imessage`: handles/chat ids - - `webchat`: session ids or usernames - -Example: - -```json5 -{ - tools: { - elevated: { - enabled: true, - allowFrom: { - whatsapp: ["+15555550123"], - discord: ["steipete", "1234567890123"], - }, - }, - }, -} -``` - -Per-agent override (further restrict): - -```json5 -{ - agents: { - list: [ - { - id: "family", - tools: { - elevated: { enabled: false }, - }, - }, - ], - }, -} -``` - -Notes: - -- `tools.elevated` is the global baseline. `agents.list[].tools.elevated` can only further restrict (both must allow). -- `/elevated on|off|ask|full` stores state per session key; inline directives apply to a single message. -- Elevated `exec` runs on the host and bypasses sandboxing. -- Tool policy still applies; if `exec` is denied, elevated cannot be used. - -`agents.defaults.maxConcurrent` sets the maximum number of embedded agent runs that can -execute in parallel across sessions. Each session is still serialized (one run -per session key at a time). Default: 1. - -### `agents.defaults.sandbox` - -Optional **Docker sandboxing** for the embedded agent. Intended for non-main -sessions so they cannot access your host system. - -Details: [Sandboxing](/gateway/sandboxing) - -Defaults (if enabled): - -- scope: `"agent"` (one container + workspace per agent) -- Debian bookworm-slim based image -- agent workspace access: `workspaceAccess: "none"` (default) - - `"none"`: use a per-scope sandbox workspace under `~/.openclaw/sandboxes` -- `"ro"`: keep the sandbox workspace at `/workspace`, and mount the agent workspace read-only at `/agent` (disables `write`/`edit`/`apply_patch`) - - `"rw"`: mount the agent workspace read/write at `/workspace` -- auto-prune: idle > 24h OR age > 7d -- tool policy: allow only `exec`, `process`, `read`, `write`, `edit`, `apply_patch`, `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn`, `session_status` (deny wins) - - configure via `tools.sandbox.tools`, override per-agent via `agents.list[].tools.sandbox.tools` - - tool group shorthands supported in sandbox policy: `group:runtime`, `group:fs`, `group:sessions`, `group:memory` (see [Sandbox vs Tool Policy vs Elevated](/gateway/sandbox-vs-tool-policy-vs-elevated#tool-groups-shorthands)) -- optional sandboxed browser (Chromium + CDP, noVNC observer) -- hardening knobs: `network`, `user`, `pidsLimit`, `memory`, `cpus`, `ulimits`, `seccompProfile`, `apparmorProfile` - -Warning: `scope: "shared"` means a shared container and shared workspace. No -cross-session isolation. Use `scope: "session"` for per-session isolation. - -Legacy: `perSession` is still supported (`true` → `scope: "session"`, -`false` → `scope: "shared"`). - -`setupCommand` runs **once** after the container is created (inside the container via `sh -lc`). -For package installs, ensure network egress, a writable root FS, and a root user. - -```json5 -{ - agents: { - defaults: { - sandbox: { - mode: "non-main", // off | non-main | all - scope: "agent", // session | agent | shared (agent is default) - workspaceAccess: "none", // none | ro | rw - workspaceRoot: "~/.openclaw/sandboxes", - docker: { - image: "openclaw-sandbox:bookworm-slim", - containerPrefix: "openclaw-sbx-", - workdir: "/workspace", - readOnlyRoot: true, - tmpfs: ["/tmp", "/var/tmp", "/run"], - network: "none", - user: "1000:1000", - capDrop: ["ALL"], - env: { LANG: "C.UTF-8" }, - setupCommand: "apt-get update && apt-get install -y git curl jq", - // Per-agent override (multi-agent): agents.list[].sandbox.docker.* - pidsLimit: 256, - memory: "1g", - memorySwap: "2g", - cpus: 1, - ulimits: { - nofile: { soft: 1024, hard: 2048 }, - nproc: 256, - }, - seccompProfile: "/path/to/seccomp.json", - apparmorProfile: "openclaw-sandbox", - dns: ["1.1.1.1", "8.8.8.8"], - extraHosts: ["internal.service:10.0.0.5"], - binds: ["/var/run/docker.sock:/var/run/docker.sock", "/home/user/source:/source:rw"], - }, - browser: { - enabled: false, - image: "openclaw-sandbox-browser:bookworm-slim", - containerPrefix: "openclaw-sbx-browser-", - cdpPort: 9222, - vncPort: 5900, - noVncPort: 6080, - headless: false, - enableNoVnc: true, - allowHostControl: false, - allowedControlUrls: ["http://10.0.0.42:18791"], - allowedControlHosts: ["browser.lab.local", "10.0.0.42"], - allowedControlPorts: [18791], - autoStart: true, - autoStartTimeoutMs: 12000, - }, - prune: { - idleHours: 24, // 0 disables idle pruning - maxAgeDays: 7, // 0 disables max-age pruning - }, - }, - }, - }, - tools: { - sandbox: { - tools: { - allow: [ - "exec", - "process", - "read", - "write", - "edit", - "apply_patch", - "sessions_list", - "sessions_history", - "sessions_send", - "sessions_spawn", - "session_status", - ], - deny: ["browser", "canvas", "nodes", "cron", "discord", "gateway"], - }, - }, - }, -} -``` - -Build the default sandbox image once with: - -```bash -scripts/sandbox-setup.sh -``` - -Note: sandbox containers default to `network: "none"`; set `agents.defaults.sandbox.docker.network` -to `"bridge"` (or your custom network) if the agent needs outbound access. - -Note: inbound attachments are staged into the active workspace at `media/inbound/*`. With `workspaceAccess: "rw"`, that means files are written into the agent workspace. - -Note: `docker.binds` mounts additional host directories; global and per-agent binds are merged. - -Build the optional browser image with: - -```bash -scripts/sandbox-browser-setup.sh -``` - -When `agents.defaults.sandbox.browser.enabled=true`, the browser tool uses a sandboxed -Chromium instance (CDP). If noVNC is enabled (default when headless=false), -the noVNC URL is injected into the system prompt so the agent can reference it. -This does not require `browser.enabled` in the main config; the sandbox control -URL is injected per session. - -`agents.defaults.sandbox.browser.allowHostControl` (default: false) allows -sandboxed sessions to explicitly target the **host** browser control server -via the browser tool (`target: "host"`). Leave this off if you want strict -sandbox isolation. - -Allowlists for remote control: - -- `allowedControlUrls`: exact control URLs permitted for `target: "custom"`. -- `allowedControlHosts`: hostnames permitted (hostname only, no port). -- `allowedControlPorts`: ports permitted (defaults: http=80, https=443). - Defaults: all allowlists are unset (no restriction). `allowHostControl` defaults to false. - -### `models` (custom providers + base URLs) - -OpenClaw uses the **pi-coding-agent** model catalog. You can add custom providers -(LiteLLM, local OpenAI-compatible servers, Anthropic proxies, etc.) by writing -`~/.openclaw/agents//agent/models.json` or by defining the same schema inside your -OpenClaw config under `models.providers`. -Provider-by-provider overview + examples: [/concepts/model-providers](/concepts/model-providers). - -When `models.providers` is present, OpenClaw writes/merges a `models.json` into -`~/.openclaw/agents//agent/` on startup: - -- default behavior: **merge** (keeps existing providers, overrides on name) -- set `models.mode: "replace"` to overwrite the file contents - -Select the model via `agents.defaults.model.primary` (provider/model). - -```json5 -{ - agents: { - defaults: { - model: { primary: "custom-proxy/llama-3.1-8b" }, - models: { - "custom-proxy/llama-3.1-8b": {}, - }, - }, - }, - models: { - mode: "merge", - providers: { - "custom-proxy": { - baseUrl: "http://localhost:4000/v1", - apiKey: "LITELLM_KEY", - api: "openai-completions", - models: [ - { - id: "llama-3.1-8b", - name: "Llama 3.1 8B", - reasoning: false, - input: ["text"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: 128000, - maxTokens: 32000, - }, - ], - }, - }, - }, -} -``` - -### OpenCode Zen (multi-model proxy) - -OpenCode Zen is a multi-model gateway with per-model endpoints. OpenClaw uses -the built-in `opencode` provider from pi-ai; set `OPENCODE_API_KEY` (or -`OPENCODE_ZEN_API_KEY`) from [https://opencode.ai/auth](https://opencode.ai/auth). - -Notes: - -- Model refs use `opencode/` (example: `opencode/claude-opus-4-6`). -- If you enable an allowlist via `agents.defaults.models`, add each model you plan to use. -- Shortcut: `openclaw onboard --auth-choice opencode-zen`. - -```json5 -{ - agents: { - defaults: { - model: { primary: "opencode/claude-opus-4-6" }, - models: { "opencode/claude-opus-4-6": { alias: "Opus" } }, - }, - }, -} -``` - -### Z.AI (GLM-4.7) — provider alias support - -Z.AI models are available via the built-in `zai` provider. Set `ZAI_API_KEY` -in your environment and reference the model by provider/model. - -Shortcut: `openclaw onboard --auth-choice zai-api-key`. - -```json5 -{ - agents: { - defaults: { - model: { primary: "zai/glm-4.7" }, - models: { "zai/glm-4.7": {} }, - }, - }, -} -``` - -Notes: - -- `z.ai/*` and `z-ai/*` are accepted aliases and normalize to `zai/*`. -- If `ZAI_API_KEY` is missing, requests to `zai/*` will fail with an auth error at runtime. -- Example error: `No API key found for provider "zai".` -- Z.AI’s general API endpoint is `https://api.z.ai/api/paas/v4`. GLM coding - requests use the dedicated Coding endpoint `https://api.z.ai/api/coding/paas/v4`. - The built-in `zai` provider uses the Coding endpoint. If you need the general - endpoint, define a custom provider in `models.providers` with the base URL - override (see the custom providers section above). -- Use a fake placeholder in docs/configs; never commit real API keys. - -### Moonshot AI (Kimi) - -Use Moonshot's OpenAI-compatible endpoint: - -```json5 -{ - env: { MOONSHOT_API_KEY: "sk-..." }, - agents: { - defaults: { - model: { primary: "moonshot/kimi-k2.5" }, - models: { "moonshot/kimi-k2.5": { alias: "Kimi K2.5" } }, - }, - }, - models: { - mode: "merge", - providers: { - moonshot: { - baseUrl: "https://api.moonshot.ai/v1", - apiKey: "${MOONSHOT_API_KEY}", - api: "openai-completions", - models: [ - { - id: "kimi-k2.5", - name: "Kimi K2.5", - reasoning: false, - input: ["text"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: 256000, - maxTokens: 8192, - }, - ], - }, - }, - }, -} -``` - -Notes: - -- Set `MOONSHOT_API_KEY` in the environment or use `openclaw onboard --auth-choice moonshot-api-key`. -- Model ref: `moonshot/kimi-k2.5`. -- For the China endpoint, either: - - Run `openclaw onboard --auth-choice moonshot-api-key-cn` (wizard will set `https://api.moonshot.cn/v1`), or - - Manually set `baseUrl: "https://api.moonshot.cn/v1"` in `models.providers.moonshot`. - -### Kimi Coding - -Use Moonshot AI's Kimi Coding endpoint (Anthropic-compatible, built-in provider): - -```json5 -{ - env: { KIMI_API_KEY: "sk-..." }, - agents: { - defaults: { - model: { primary: "kimi-coding/k2p5" }, - models: { "kimi-coding/k2p5": { alias: "Kimi K2.5" } }, - }, - }, -} -``` - -Notes: - -- Set `KIMI_API_KEY` in the environment or use `openclaw onboard --auth-choice kimi-code-api-key`. -- Model ref: `kimi-coding/k2p5`. - -### Synthetic (Anthropic-compatible) - -Use Synthetic's Anthropic-compatible endpoint: - -```json5 -{ - env: { SYNTHETIC_API_KEY: "sk-..." }, - agents: { - defaults: { - model: { primary: "synthetic/hf:MiniMaxAI/MiniMax-M2.1" }, - models: { "synthetic/hf:MiniMaxAI/MiniMax-M2.1": { alias: "MiniMax M2.1" } }, - }, - }, - models: { - mode: "merge", - providers: { - synthetic: { - baseUrl: "https://api.synthetic.new/anthropic", - apiKey: "${SYNTHETIC_API_KEY}", - api: "anthropic-messages", - models: [ - { - id: "hf:MiniMaxAI/MiniMax-M2.1", - name: "MiniMax M2.1", - reasoning: false, - input: ["text"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: 192000, - maxTokens: 65536, - }, - ], - }, - }, - }, -} -``` - -Notes: - -- Set `SYNTHETIC_API_KEY` or use `openclaw onboard --auth-choice synthetic-api-key`. -- Model ref: `synthetic/hf:MiniMaxAI/MiniMax-M2.1`. -- Base URL should omit `/v1` because the Anthropic client appends it. - -### Local models (LM Studio) — recommended setup - -See [/gateway/local-models](/gateway/local-models) for the current local guidance. TL;DR: run MiniMax M2.1 via LM Studio Responses API on serious hardware; keep hosted models merged for fallback. - -### MiniMax M2.1 - -Use MiniMax M2.1 directly without LM Studio: - -```json5 -{ - agent: { - model: { primary: "minimax/MiniMax-M2.1" }, - models: { - "anthropic/claude-opus-4-6": { alias: "Opus" }, - "minimax/MiniMax-M2.1": { alias: "Minimax" }, - }, - }, - models: { - mode: "merge", - providers: { - minimax: { - baseUrl: "https://api.minimax.io/anthropic", - apiKey: "${MINIMAX_API_KEY}", - api: "anthropic-messages", - models: [ - { - id: "MiniMax-M2.1", - name: "MiniMax M2.1", - reasoning: false, - input: ["text"], - // Pricing: update in models.json if you need exact cost tracking. - cost: { input: 15, output: 60, cacheRead: 2, cacheWrite: 10 }, - contextWindow: 200000, - maxTokens: 8192, - }, - ], - }, - }, - }, -} -``` - -Notes: - -- Set `MINIMAX_API_KEY` environment variable or use `openclaw onboard --auth-choice minimax-api`. -- Available model: `MiniMax-M2.1` (default). -- Update pricing in `models.json` if you need exact cost tracking. - -### Cerebras (GLM 4.6 / 4.7) - -Use Cerebras via their OpenAI-compatible endpoint: - -```json5 -{ - env: { CEREBRAS_API_KEY: "sk-..." }, - agents: { - defaults: { - model: { - primary: "cerebras/zai-glm-4.7", - fallbacks: ["cerebras/zai-glm-4.6"], - }, - models: { - "cerebras/zai-glm-4.7": { alias: "GLM 4.7 (Cerebras)" }, - "cerebras/zai-glm-4.6": { alias: "GLM 4.6 (Cerebras)" }, - }, - }, - }, - models: { - mode: "merge", - providers: { - cerebras: { - baseUrl: "https://api.cerebras.ai/v1", - apiKey: "${CEREBRAS_API_KEY}", - api: "openai-completions", - models: [ - { id: "zai-glm-4.7", name: "GLM 4.7 (Cerebras)" }, - { id: "zai-glm-4.6", name: "GLM 4.6 (Cerebras)" }, - ], - }, - }, - }, -} -``` - -Notes: - -- Use `cerebras/zai-glm-4.7` for Cerebras; use `zai/glm-4.7` for Z.AI direct. -- Set `CEREBRAS_API_KEY` in the environment or config. - -Notes: - -- Supported APIs: `openai-completions`, `openai-responses`, `anthropic-messages`, - `google-generative-ai` -- Use `authHeader: true` + `headers` for custom auth needs. -- Override the agent config root with `OPENCLAW_AGENT_DIR` (or `PI_CODING_AGENT_DIR`) - if you want `models.json` stored elsewhere (default: `~/.openclaw/agents/main/agent`). - -### `session` - -Controls session scoping, reset policy, reset triggers, and where the session store is written. - -```json5 -{ - session: { - scope: "per-sender", - dmScope: "main", - identityLinks: { - alice: ["telegram:123456789", "discord:987654321012345678"], - }, - reset: { - mode: "daily", - atHour: 4, - idleMinutes: 60, - }, - resetByType: { - thread: { mode: "daily", atHour: 4 }, - direct: { mode: "idle", idleMinutes: 240 }, - group: { mode: "idle", idleMinutes: 120 }, - }, - resetTriggers: ["/new", "/reset"], - // Default is already per-agent under ~/.openclaw/agents//sessions/sessions.json - // You can override with {agentId} templating: - store: "~/.openclaw/agents/{agentId}/sessions/sessions.json", - maintenance: { - mode: "warn", - pruneAfter: "30d", - maxEntries: 500, - rotateBytes: "10mb", - }, - // Direct chats collapse to agent:: (default: "main"). - mainKey: "main", - agentToAgent: { - // Max ping-pong reply turns between requester/target (0–5). - maxPingPongTurns: 5, - }, - sendPolicy: { - rules: [{ action: "deny", match: { channel: "discord", chatType: "group" } }], - default: "allow", - }, - }, -} -``` - -Fields: - -- `mainKey`: direct-chat bucket key (default: `"main"`). Useful when you want to “rename” the primary DM thread without changing `agentId`. - - Sandbox note: `agents.defaults.sandbox.mode: "non-main"` uses this key to detect the main session. Any session key that does not match `mainKey` (groups/channels) is sandboxed. -- `dmScope`: how DM sessions are grouped (default: `"main"`). - - `main`: all DMs share the main session for continuity. - - `per-peer`: isolate DMs by sender id across channels. - - `per-channel-peer`: isolate DMs per channel + sender (recommended for multi-user inboxes). - - `per-account-channel-peer`: isolate DMs per account + channel + sender (recommended for multi-account inboxes). - - Secure DM mode (recommended): set `session.dmScope: "per-channel-peer"` when multiple people can DM the bot (shared inboxes, multi-person allowlists, or `dmPolicy: "open"`). -- `identityLinks`: map canonical ids to provider-prefixed peers so the same person shares a DM session across channels when using `per-peer`, `per-channel-peer`, or `per-account-channel-peer`. - - Example: `alice: ["telegram:123456789", "discord:987654321012345678"]`. -- `reset`: primary reset policy. Defaults to daily resets at 4:00 AM local time on the gateway host. - - `mode`: `daily` or `idle` (default: `daily` when `reset` is present). - - `atHour`: local hour (0-23) for the daily reset boundary. - - `idleMinutes`: sliding idle window in minutes. When daily + idle are both configured, whichever expires first wins. -- `resetByType`: per-session overrides for `direct`, `group`, and `thread`. Legacy `dm` key is accepted as an alias for `direct`. - - If you only set legacy `session.idleMinutes` without any `reset`/`resetByType`, OpenClaw stays in idle-only mode for backward compatibility. -- `heartbeatIdleMinutes`: optional idle override for heartbeat checks (daily reset still applies when enabled). -- `agentToAgent.maxPingPongTurns`: max reply-back turns between requester/target (0–5, default 5). -- `sendPolicy.default`: `allow` or `deny` fallback when no rule matches. -- `sendPolicy.rules[]`: match by `channel`, `chatType` (`direct|group|room`), or `keyPrefix` (e.g. `cron:`). First deny wins; otherwise allow. -- `maintenance`: session store maintenance settings for pruning, capping, and rotation. - - `mode`: `"warn"` (default) warns the active session (best-effort delivery) when it would be evicted without enforcing maintenance. `"enforce"` applies pruning and rotation. - - `pruneAfter`: remove entries older than this duration (for example `"30m"`, `"1h"`, `"30d"`). Default "30d". - - `maxEntries`: cap the number of session entries kept (default 500). - - `rotateBytes`: rotate `sessions.json` when it exceeds this size (for example `"10kb"`, `"1mb"`, `"10mb"`). Default "10mb". - -### `skills` (skills config) - -Controls bundled allowlist, install preferences, extra skill folders, and per-skill -overrides. Applies to **bundled** skills and `~/.openclaw/skills` (workspace skills -still win on name conflicts). - -Fields: - -- `allowBundled`: optional allowlist for **bundled** skills only. If set, only those - bundled skills are eligible (managed/workspace skills unaffected). -- `load.extraDirs`: additional skill directories to scan (lowest precedence). -- `install.preferBrew`: prefer brew installers when available (default: true). -- `install.nodeManager`: node installer preference (`npm` | `pnpm` | `yarn`, default: npm). -- `entries.`: per-skill config overrides. - -Per-skill fields: - -- `enabled`: set `false` to disable a skill even if it’s bundled/installed. -- `env`: environment variables injected for the agent run (only if not already set). -- `apiKey`: optional convenience for skills that declare a primary env var (e.g. `nano-banana-pro` → `GEMINI_API_KEY`). - -Example: - -```json5 -{ - skills: { - allowBundled: ["gemini", "peekaboo"], - load: { - extraDirs: ["~/Projects/agent-scripts/skills", "~/Projects/oss/some-skill-pack/skills"], - }, - install: { - preferBrew: true, - nodeManager: "npm", - }, - entries: { - "nano-banana-pro": { - apiKey: "GEMINI_KEY_HERE", - env: { - GEMINI_API_KEY: "GEMINI_KEY_HERE", - }, - }, - peekaboo: { enabled: true }, - sag: { enabled: false }, - }, - }, -} -``` - -### `plugins` (extensions) - -Controls plugin discovery, allow/deny, and per-plugin config. Plugins are loaded -from `~/.openclaw/extensions`, `/.openclaw/extensions`, plus any -`plugins.load.paths` entries. **Config changes require a gateway restart.** -See [/plugin](/tools/plugin) for full usage. - -Fields: - -- `enabled`: master toggle for plugin loading (default: true). -- `allow`: optional allowlist of plugin ids; when set, only listed plugins load. -- `deny`: optional denylist of plugin ids (deny wins). -- `load.paths`: extra plugin files or directories to load (absolute or `~`). -- `entries.`: per-plugin overrides. - - `enabled`: set `false` to disable. - - `config`: plugin-specific config object (validated by the plugin if provided). - -Example: - -```json5 -{ - plugins: { - enabled: true, - allow: ["voice-call"], - load: { - paths: ["~/Projects/oss/voice-call-extension"], - }, - entries: { - "voice-call": { - enabled: true, - config: { - provider: "twilio", - }, - }, - }, - }, -} -``` - -### `browser` (openclaw-managed browser) - -OpenClaw can start a **dedicated, isolated** Chrome/Brave/Edge/Chromium instance for openclaw and expose a small loopback control service. -Profiles can point at a **remote** Chromium-based browser via `profiles..cdpUrl`. Remote -profiles are attach-only (start/stop/reset are disabled). - -`browser.cdpUrl` remains for legacy single-profile configs and as the base -scheme/host for profiles that only set `cdpPort`. - -Defaults: - -- enabled: `true` -- evaluateEnabled: `true` (set `false` to disable `act:evaluate` and `wait --fn`) -- control service: loopback only (port derived from `gateway.port`, default `18791`) -- CDP URL: `http://127.0.0.1:18792` (control service + 1, legacy single-profile) -- profile color: `#FF4500` (lobster-orange) -- Note: the control server is started by the running gateway (OpenClaw.app menubar, or `openclaw gateway`). -- Auto-detect order: default browser if Chromium-based; otherwise Chrome → Brave → Edge → Chromium → Chrome Canary. - -```json5 -{ - browser: { - enabled: true, - evaluateEnabled: true, - // cdpUrl: "http://127.0.0.1:18792", // legacy single-profile override - defaultProfile: "chrome", - profiles: { - openclaw: { cdpPort: 18800, color: "#FF4500" }, - work: { cdpPort: 18801, color: "#0066CC" }, - remote: { cdpUrl: "http://10.0.0.42:9222", color: "#00AA00" }, - }, - color: "#FF4500", - // Advanced: - // headless: false, - // noSandbox: false, - // executablePath: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser", - // attachOnly: false, // set true when tunneling a remote CDP to localhost - }, -} -``` - -### `ui` (Appearance) - -Optional accent color used by the native apps for UI chrome (e.g. Talk Mode bubble tint). - -If unset, clients fall back to a muted light-blue. - -```json5 -{ - ui: { - seamColor: "#FF4500", // hex (RRGGBB or #RRGGBB) - // Optional: Control UI assistant identity override. - // If unset, the Control UI uses the active agent identity (config or IDENTITY.md). - assistant: { - name: "OpenClaw", - avatar: "CB", // emoji, short text, or image URL/data URI - }, - }, -} -``` - -### `gateway` (Gateway server mode + bind) - -Use `gateway.mode` to explicitly declare whether this machine should run the Gateway. - -Defaults: - -- mode: **unset** (treated as “do not auto-start”) -- bind: `loopback` -- port: `18789` (single port for WS + HTTP) - -```json5 -{ - gateway: { - mode: "local", // or "remote" - port: 18789, // WS + HTTP multiplex - bind: "loopback", - // controlUi: { enabled: true, basePath: "/openclaw" } - // auth: { mode: "token", token: "your-token" } // token gates WS + Control UI access - // tailscale: { mode: "off" | "serve" | "funnel" } - }, -} -``` - -Control UI base path: - -- `gateway.controlUi.basePath` sets the URL prefix where the Control UI is served. -- Examples: `"/ui"`, `"/openclaw"`, `"/apps/openclaw"`. -- Default: root (`/`) (unchanged). -- `gateway.controlUi.root` sets the filesystem root for Control UI assets (default: `dist/control-ui`). -- `gateway.controlUi.allowInsecureAuth` allows token-only auth for the Control UI when - device identity is omitted (typically over HTTP). Default: `false`. Prefer HTTPS - (Tailscale Serve) or `127.0.0.1`. -- `gateway.controlUi.dangerouslyDisableDeviceAuth` disables device identity checks for the - Control UI (token/password only). Default: `false`. Break-glass only. - -Related docs: - -- [Control UI](/web/control-ui) -- [Web overview](/web) -- [Tailscale](/gateway/tailscale) -- [Remote access](/gateway/remote) - -Trusted proxies: - -- `gateway.trustedProxies`: list of reverse proxy IPs that terminate TLS in front of the Gateway. -- When a connection comes from one of these IPs, OpenClaw uses `x-forwarded-for` (or `x-real-ip`) to determine the client IP for local pairing checks and HTTP auth/local checks. -- Only list proxies you fully control, and ensure they **overwrite** incoming `x-forwarded-for`. - -Notes: - -- `openclaw gateway` refuses to start unless `gateway.mode` is set to `local` (or you pass the override flag). -- `gateway.port` controls the single multiplexed port used for WebSocket + HTTP (control UI, hooks, A2UI). -- OpenAI Chat Completions endpoint: **disabled by default**; enable with `gateway.http.endpoints.chatCompletions.enabled: true`. -- Precedence: `--port` > `OPENCLAW_GATEWAY_PORT` > `gateway.port` > default `18789`. -- Gateway auth is required by default (token/password or Tailscale Serve identity). Non-loopback binds require a shared token/password. -- The onboarding wizard generates a gateway token by default (even on loopback). -- `gateway.remote.token` is **only** for remote CLI calls; it does not enable local gateway auth. `gateway.token` is ignored. - -Auth and Tailscale: - -- `gateway.auth.mode` sets the handshake requirements (`token` or `password`). When unset, token auth is assumed. -- `gateway.auth.token` stores the shared token for token auth (used by the CLI on the same machine). -- When `gateway.auth.mode` is set, only that method is accepted (plus optional Tailscale headers). -- `gateway.auth.password` can be set here, or via `OPENCLAW_GATEWAY_PASSWORD` (recommended). -- `gateway.auth.allowTailscale` allows Tailscale Serve identity headers - (`tailscale-user-login`) to satisfy auth when the request arrives on loopback - with `x-forwarded-for`, `x-forwarded-proto`, and `x-forwarded-host`. OpenClaw - verifies the identity by resolving the `x-forwarded-for` address via - `tailscale whois` before accepting it. When `true`, Serve requests do not need - a token/password; set `false` to require explicit credentials. Defaults to - `true` when `tailscale.mode = "serve"` and auth mode is not `password`. -- `gateway.tailscale.mode: "serve"` uses Tailscale Serve (tailnet only, loopback bind). -- `gateway.tailscale.mode: "funnel"` exposes the dashboard publicly; requires auth. -- `gateway.tailscale.resetOnExit` resets Serve/Funnel config on shutdown. - -Remote client defaults (CLI): - -- `gateway.remote.url` sets the default Gateway WebSocket URL for CLI calls when `gateway.mode = "remote"`. -- `gateway.remote.transport` selects the macOS remote transport (`ssh` default, `direct` for ws/wss). When `direct`, `gateway.remote.url` must be `ws://` or `wss://`. `ws://host` defaults to port `18789`. -- `gateway.remote.token` supplies the token for remote calls (leave unset for no auth). -- `gateway.remote.password` supplies the password for remote calls (leave unset for no auth). - -macOS app behavior: - -- OpenClaw.app watches `~/.openclaw/openclaw.json` and switches modes live when `gateway.mode` or `gateway.remote.url` changes. -- If `gateway.mode` is unset but `gateway.remote.url` is set, the macOS app treats it as remote mode. -- When you change connection mode in the macOS app, it writes `gateway.mode` (and `gateway.remote.url` + `gateway.remote.transport` in remote mode) back to the config file. - -```json5 -{ - gateway: { - mode: "remote", - remote: { - url: "ws://gateway.tailnet:18789", - token: "your-token", - password: "your-password", - }, - }, -} -``` - -Direct transport example (macOS app): - -```json5 -{ - gateway: { - mode: "remote", - remote: { - transport: "direct", - url: "wss://gateway.example.ts.net", - token: "your-token", - }, - }, -} -``` - -### `gateway.reload` (Config hot reload) - -The Gateway watches `~/.openclaw/openclaw.json` (or `OPENCLAW_CONFIG_PATH`) and applies changes automatically. - -Modes: - -- `hybrid` (default): hot-apply safe changes; restart the Gateway for critical changes. -- `hot`: only apply hot-safe changes; log when a restart is required. -- `restart`: restart the Gateway on any config change. -- `off`: disable hot reload. - -```json5 -{ - gateway: { - reload: { - mode: "hybrid", - debounceMs: 300, - }, - }, -} -``` - -#### Hot reload matrix (files + impact) - -Files watched: - -- `~/.openclaw/openclaw.json` (or `OPENCLAW_CONFIG_PATH`) - -Hot-applied (no full gateway restart): - -- `hooks` (webhook auth/path/mappings) + `hooks.gmail` (Gmail watcher restarted) -- `browser` (browser control server restart) -- `cron` (cron service restart + concurrency update) -- `agents.defaults.heartbeat` (heartbeat runner restart) -- `web` (WhatsApp web channel restart) -- `telegram`, `discord`, `signal`, `imessage` (channel restarts) -- `agent`, `models`, `routing`, `messages`, `session`, `whatsapp`, `logging`, `skills`, `ui`, `talk`, `identity`, `wizard` (dynamic reads) - -Requires full Gateway restart: - -- `gateway` (port/bind/auth/control UI/tailscale) -- `bridge` (legacy) -- `discovery` -- `canvasHost` -- `plugins` -- Any unknown/unsupported config path (defaults to restart for safety) - -### Multi-instance isolation - -To run multiple gateways on one host (for redundancy or a rescue bot), isolate per-instance state + config and use unique ports: - -- `OPENCLAW_CONFIG_PATH` (per-instance config) -- `OPENCLAW_STATE_DIR` (sessions/creds) -- `agents.defaults.workspace` (memories) -- `gateway.port` (unique per instance) - -Convenience flags (CLI): - -- `openclaw --dev …` → uses `~/.openclaw-dev` + shifts ports from base `19001` -- `openclaw --profile …` → uses `~/.openclaw-` (port via config/env/flags) - -See [Gateway runbook](/gateway) for the derived port mapping (gateway/browser/canvas). -See [Multiple gateways](/gateway/multiple-gateways) for browser/CDP port isolation details. - -Example: - -```bash -OPENCLAW_CONFIG_PATH=~/.openclaw/a.json \ -OPENCLAW_STATE_DIR=~/.openclaw-a \ -openclaw gateway --port 19001 -``` - -### `hooks` (Gateway webhooks) - -Enable a simple HTTP webhook endpoint on the Gateway HTTP server. - -Defaults: - -- enabled: `false` -- path: `/hooks` -- maxBodyBytes: `262144` (256 KB) - -```json5 -{ - hooks: { - enabled: true, - token: "shared-secret", - path: "/hooks", - // Optional: restrict explicit `agentId` routing. - // Omit or include "*" to allow any agent. - // Set [] to deny all explicit `agentId` routing. - allowedAgentIds: ["hooks", "main"], - presets: ["gmail"], - transformsDir: "~/.openclaw/hooks", - mappings: [ - { - match: { path: "gmail" }, - action: "agent", - agentId: "hooks", - wakeMode: "now", - name: "Gmail", - sessionKey: "hook:gmail:{{messages[0].id}}", - messageTemplate: "From: {{messages[0].from}}\nSubject: {{messages[0].subject}}\n{{messages[0].snippet}}", - deliver: true, - channel: "last", - model: "openai/gpt-5.2-mini", - }, - ], - }, -} -``` - -Requests must include the hook token: - -- `Authorization: Bearer ` **or** -- `x-openclaw-token: ` - -Endpoints: - -- `POST /hooks/wake` → `{ text, mode?: "now"|"next-heartbeat" }` -- `POST /hooks/agent` → `{ message, name?, agentId?, sessionKey?, wakeMode?, deliver?, channel?, to?, model?, thinking?, timeoutSeconds? }` -- `POST /hooks/` → resolved via `hooks.mappings` - -`/hooks/agent` always posts a summary into the main session (and can optionally trigger an immediate heartbeat via `wakeMode: "now"`). - -Mapping notes: - -- `match.path` matches the sub-path after `/hooks` (e.g. `/hooks/gmail` → `gmail`). -- `match.source` matches a payload field (e.g. `{ source: "gmail" }`) so you can use a generic `/hooks/ingest` path. -- Templates like `{{messages[0].subject}}` read from the payload. -- `transform` can point to a JS/TS module that returns a hook action. -- `agentId` can route to a specific agent; unknown IDs fall back to the default agent. -- `hooks.allowedAgentIds` restricts explicit `agentId` routing (`*` or omitted means allow all, `[]` denies all explicit routing). -- `deliver: true` sends the final reply to a channel; `channel` defaults to `last` (falls back to WhatsApp). -- If there is no prior delivery route, set `channel` + `to` explicitly (required for Telegram/Discord/Google Chat/Slack/Signal/iMessage/MS Teams). -- `model` overrides the LLM for this hook run (`provider/model` or alias; must be allowed if `agents.defaults.models` is set). - -Gmail helper config (used by `openclaw webhooks gmail setup` / `run`): - -```json5 -{ - hooks: { - gmail: { - account: "openclaw@gmail.com", - topic: "projects//topics/gog-gmail-watch", - subscription: "gog-gmail-watch-push", - pushToken: "shared-push-token", - hookUrl: "http://127.0.0.1:18789/hooks/gmail", - includeBody: true, - maxBytes: 20000, - renewEveryMinutes: 720, - serve: { bind: "127.0.0.1", port: 8788, path: "/" }, - tailscale: { mode: "funnel", path: "/gmail-pubsub" }, - - // Optional: use a cheaper model for Gmail hook processing - // Falls back to agents.defaults.model.fallbacks, then primary, on auth/rate-limit/timeout - model: "openrouter/meta-llama/llama-3.3-70b-instruct:free", - // Optional: default thinking level for Gmail hooks - thinking: "off", - }, - }, -} -``` - -Model override for Gmail hooks: - -- `hooks.gmail.model` specifies a model to use for Gmail hook processing (defaults to session primary). -- Accepts `provider/model` refs or aliases from `agents.defaults.models`. -- Falls back to `agents.defaults.model.fallbacks`, then `agents.defaults.model.primary`, on auth/rate-limit/timeouts. -- If `agents.defaults.models` is set, include the hooks model in the allowlist. -- At startup, warns if the configured model is not in the model catalog or allowlist. -- `hooks.gmail.thinking` sets the default thinking level for Gmail hooks and is overridden by per-hook `thinking`. - -Gateway auto-start: - -- If `hooks.enabled=true` and `hooks.gmail.account` is set, the Gateway starts - `gog gmail watch serve` on boot and auto-renews the watch. -- Set `OPENCLAW_SKIP_GMAIL_WATCHER=1` to disable the auto-start (for manual runs). -- Avoid running a separate `gog gmail watch serve` alongside the Gateway; it will - fail with `listen tcp 127.0.0.1:8788: bind: address already in use`. - -Note: when `tailscale.mode` is on, OpenClaw defaults `serve.path` to `/` so -Tailscale can proxy `/gmail-pubsub` correctly (it strips the set-path prefix). -If you need the backend to receive the prefixed path, set -`hooks.gmail.tailscale.target` to a full URL (and align `serve.path`). - -### `canvasHost` (LAN/tailnet Canvas file server + live reload) - -The Gateway serves a directory of HTML/CSS/JS over HTTP so iOS/Android nodes can simply `canvas.navigate` to it. - -Default root: `~/.openclaw/workspace/canvas` -Default port: `18793` (chosen to avoid the openclaw browser CDP port `18792`) -The server listens on the **gateway bind host** (LAN or Tailnet) so nodes can reach it. - -The server: - -- serves files under `canvasHost.root` -- injects a tiny live-reload client into served HTML -- watches the directory and broadcasts reloads over a WebSocket endpoint at `/__openclaw__/ws` -- auto-creates a starter `index.html` when the directory is empty (so you see something immediately) -- also serves A2UI at `/__openclaw__/a2ui/` and is advertised to nodes as `canvasHostUrl` - (always used by nodes for Canvas/A2UI) - -Disable live reload (and file watching) if the directory is large or you hit `EMFILE`: - -- config: `canvasHost: { liveReload: false }` - -```json5 -{ - canvasHost: { - root: "~/.openclaw/workspace/canvas", - port: 18793, - liveReload: true, - }, -} -``` - -Changes to `canvasHost.*` require a gateway restart (config reload will restart). - -Disable with: - -- config: `canvasHost: { enabled: false }` -- env: `OPENCLAW_SKIP_CANVAS_HOST=1` - -### `bridge` (legacy TCP bridge, removed) - -Current builds no longer include the TCP bridge listener; `bridge.*` config keys are ignored. -Nodes connect over the Gateway WebSocket. This section is kept for historical reference. - -Legacy behavior: - -- The Gateway could expose a simple TCP bridge for nodes (iOS/Android), typically on port `18790`. - -Defaults: - -- enabled: `true` -- port: `18790` -- bind: `lan` (binds to `0.0.0.0`) - -Bind modes: - -- `lan`: `0.0.0.0` (reachable on any interface, including LAN/Wi‑Fi and Tailscale) -- `tailnet`: bind only to the machine’s Tailscale IP (recommended for Vienna ⇄ London) -- `loopback`: `127.0.0.1` (local only) -- `auto`: prefer tailnet IP if present, else `lan` - -TLS: - -- `bridge.tls.enabled`: enable TLS for bridge connections (TLS-only when enabled). -- `bridge.tls.autoGenerate`: generate a self-signed cert when no cert/key are present (default: true). -- `bridge.tls.certPath` / `bridge.tls.keyPath`: PEM paths for the bridge certificate + private key. -- `bridge.tls.caPath`: optional PEM CA bundle (custom roots or future mTLS). - -When TLS is enabled, the Gateway advertises `bridgeTls=1` and `bridgeTlsSha256` in discovery TXT -records so nodes can pin the certificate. Manual connections use trust-on-first-use if no -fingerprint is stored yet. -Auto-generated certs require `openssl` on PATH; if generation fails, the bridge will not start. - -```json5 -{ - bridge: { - enabled: true, - port: 18790, - bind: "tailnet", - tls: { - enabled: true, - // Uses ~/.openclaw/bridge/tls/bridge-{cert,key}.pem when omitted. - // certPath: "~/.openclaw/bridge/tls/bridge-cert.pem", - // keyPath: "~/.openclaw/bridge/tls/bridge-key.pem" - }, - }, -} -``` - -### `discovery.mdns` (Bonjour / mDNS broadcast mode) - -Controls LAN mDNS discovery broadcasts (`_openclaw-gw._tcp`). - -- `minimal` (default): omit `cliPath` + `sshPort` from TXT records -- `full`: include `cliPath` + `sshPort` in TXT records -- `off`: disable mDNS broadcasts entirely -- Hostname: defaults to `openclaw` (advertises `openclaw.local`). Override with `OPENCLAW_MDNS_HOSTNAME`. - -```json5 -{ - discovery: { mdns: { mode: "minimal" } }, -} -``` - -### `discovery.wideArea` (Wide-Area Bonjour / unicast DNS‑SD) - -When enabled, the Gateway writes a unicast DNS-SD zone for `_openclaw-gw._tcp` under `~/.openclaw/dns/` using the configured discovery domain (example: `openclaw.internal.`). - -To make iOS/Android discover across networks (Vienna ⇄ London), pair this with: - -- a DNS server on the gateway host serving your chosen domain (CoreDNS is recommended) -- Tailscale **split DNS** so clients resolve that domain via the gateway DNS server - -One-time setup helper (gateway host): - -```bash -openclaw dns setup --apply -``` + + Reference env vars in any config string value with `${VAR_NAME}`: ```json5 { - discovery: { wideArea: { enabled: true } }, + gateway: { auth: { token: "${OPENCLAW_GATEWAY_TOKEN}" } }, + models: { providers: { custom: { apiKey: "${CUSTOM_API_KEY}" } } }, } ``` -## Media model template variables - -Template placeholders are expanded in `tools.media.*.models[].args` and `tools.media.models[].args` (and any future templated argument fields). +Rules: -| Variable | Description | -| ------------------ | ------------------------------------------------------------------------------- | -------- | ------- | ---------- | ----- | ------ | -------- | ------- | ------- | --- | -| `{{Body}}` | Full inbound message body | -| `{{RawBody}}` | Raw inbound message body (no history/sender wrappers; best for command parsing) | -| `{{BodyStripped}}` | Body with group mentions stripped (best default for agents) | -| `{{From}}` | Sender identifier (E.164 for WhatsApp; may differ per channel) | -| `{{To}}` | Destination identifier | -| `{{MessageSid}}` | Channel message id (when available) | -| `{{SessionId}}` | Current session UUID | -| `{{IsNewSession}}` | `"true"` when a new session was created | -| `{{MediaUrl}}` | Inbound media pseudo-URL (if present) | -| `{{MediaPath}}` | Local media path (if downloaded) | -| `{{MediaType}}` | Media type (image/audio/document/…) | -| `{{Transcript}}` | Audio transcript (when enabled) | -| `{{Prompt}}` | Resolved media prompt for CLI entries | -| `{{MaxChars}}` | Resolved max output chars for CLI entries | -| `{{ChatType}}` | `"direct"` or `"group"` | -| `{{GroupSubject}}` | Group subject (best effort) | -| `{{GroupMembers}}` | Group members preview (best effort) | -| `{{SenderName}}` | Sender display name (best effort) | -| `{{SenderE164}}` | Sender phone number (best effort) | -| `{{Provider}}` | Provider hint (whatsapp | telegram | discord | googlechat | slack | signal | imessage | msteams | webchat | …) | +- Only uppercase names matched: `[A-Z_][A-Z0-9_]*` +- Missing/empty vars throw an error at load time +- Escape with `$${VAR}` for literal output +- Works inside `$include` files +- Inline substitution: `"${BASE}/v1"` → `"https://api.example.com/v1"` -## Cron (Gateway scheduler) + -Cron is a Gateway-owned scheduler for wakeups and scheduled jobs. See [Cron jobs](/automation/cron-jobs) for the feature overview and CLI examples. - -```json5 -{ - cron: { - enabled: true, - maxConcurrentRuns: 2, - sessionRetention: "24h", - }, -} -``` +See [Environment](/help/environment) for full precedence and sources. -Fields: +## Full reference -- `sessionRetention`: how long to keep completed cron run sessions before pruning. Accepts a duration string like `"24h"` or `"7d"`. Use `false` to disable pruning. Default is 24h. +For the complete field-by-field reference, see **[Configuration Reference](/gateway/configuration-reference)**. --- -_Next: [Agent Runtime](/concepts/agent)_ 🦞 +_Related: [Configuration Examples](/gateway/configuration-examples) · [Configuration Reference](/gateway/configuration-reference) · [Doctor](/gateway/doctor)_ diff --git a/docs/gateway/index.md b/docs/gateway/index.md index 64697f1f461..c1e06d63457 100644 --- a/docs/gateway/index.md +++ b/docs/gateway/index.md @@ -5,120 +5,173 @@ read_when: title: "Gateway Runbook" --- -# Gateway service runbook +# Gateway runbook -Last updated: 2025-12-09 +Use this page for day-1 startup and day-2 operations of the Gateway service. -## What it is + + + Symptom-first diagnostics with exact command ladders and log signatures. + + + Task-oriented setup guide + full configuration reference. + + -- The always-on process that owns the single Baileys/Telegram connection and the control/event plane. -- Replaces the legacy `gateway` command. CLI entry point: `openclaw gateway`. -- Runs until stopped; exits non-zero on fatal errors so the supervisor restarts it. +## 5-minute local startup -## How to run (local) + + ```bash openclaw gateway --port 18789 -# for full debug/trace logs in stdio: +# debug/trace mirrored to stdio openclaw gateway --port 18789 --verbose -# if the port is busy, terminate listeners then start: +# force-kill listener on selected port, then start openclaw gateway --force -# dev loop (auto-reload on TS changes): -pnpm gateway:watch ``` -- Config hot reload watches `~/.openclaw/openclaw.json` (or `OPENCLAW_CONFIG_PATH`). - - Default mode: `gateway.reload.mode="hybrid"` (hot-apply safe changes, restart on critical). - - Hot reload uses in-process restart via **SIGUSR1** when needed. - - Disable with `gateway.reload.mode="off"`. -- Binds WebSocket control plane to `127.0.0.1:` (default 18789). -- The same port also serves HTTP (control UI, hooks, A2UI). Single-port multiplex. - - OpenAI Chat Completions (HTTP): [`/v1/chat/completions`](/gateway/openai-http-api). - - OpenResponses (HTTP): [`/v1/responses`](/gateway/openresponses-http-api). - - Tools Invoke (HTTP): [`/tools/invoke`](/gateway/tools-invoke-http-api). -- Starts a Canvas file server by default on `canvasHost.port` (default `18793`), serving `http://:18793/__openclaw__/canvas/` from `~/.openclaw/workspace/canvas`. Disable with `canvasHost.enabled=false` or `OPENCLAW_SKIP_CANVAS_HOST=1`. -- Logs to stdout; use launchd/systemd to keep it alive and rotate logs. -- Pass `--verbose` to mirror debug logging (handshakes, req/res, events) from the log file into stdio when troubleshooting. -- `--force` uses `lsof` to find listeners on the chosen port, sends SIGTERM, logs what it killed, then starts the gateway (fails fast if `lsof` is missing). -- If you run under a supervisor (launchd/systemd/mac app child-process mode), a stop/restart typically sends **SIGTERM**; older builds may surface this as `pnpm` `ELIFECYCLE` exit code **143** (SIGTERM), which is a normal shutdown, not a crash. -- **SIGUSR1** triggers an in-process restart when authorized (gateway tool/config apply/update, or enable `commands.restart` for manual restarts). -- Gateway auth is required by default: set `gateway.auth.token` (or `OPENCLAW_GATEWAY_TOKEN`) or `gateway.auth.password`. Clients must send `connect.params.auth.token/password` unless using Tailscale Serve identity. -- The wizard now generates a token by default, even on loopback. -- Port precedence: `--port` > `OPENCLAW_GATEWAY_PORT` > `gateway.port` > default `18789`. + + + + +```bash +openclaw gateway status +openclaw status +openclaw logs --follow +``` + +Healthy baseline: `Runtime: running` and `RPC probe: ok`. + + + + + +```bash +openclaw channels status --probe +``` + + + + + +Gateway config reload watches the active config file path (resolved from profile/state defaults, or `OPENCLAW_CONFIG_PATH` when set). +Default mode is `gateway.reload.mode="hybrid"`. + + +## Runtime model + +- One always-on process for routing, control plane, and channel connections. +- Single multiplexed port for: + - WebSocket control/RPC + - HTTP APIs (OpenAI-compatible, Responses, tools invoke) + - Control UI and hooks +- Default bind mode: `loopback`. +- Auth is required by default (`gateway.auth.token` / `gateway.auth.password`, or `OPENCLAW_GATEWAY_TOKEN` / `OPENCLAW_GATEWAY_PASSWORD`). + +### Port and bind precedence + +| Setting | Resolution order | +| ------------ | ------------------------------------------------------------- | +| Gateway port | `--port` → `OPENCLAW_GATEWAY_PORT` → `gateway.port` → `18789` | +| Bind mode | CLI/override → `gateway.bind` → `loopback` | + +### 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 command set + +```bash +openclaw gateway status +openclaw gateway status --deep +openclaw gateway status --json +openclaw gateway install +openclaw gateway restart +openclaw gateway stop +openclaw logs --follow +openclaw doctor +``` ## Remote access -- Tailscale/VPN preferred; otherwise SSH tunnel: - - ```bash - ssh -N -L 18789:127.0.0.1:18789 user@host - ``` - -- Clients then connect to `ws://127.0.0.1:18789` through the tunnel. -- If a token is configured, clients must include it in `connect.params.auth.token` even over the tunnel. - -## Multiple gateways (same host) - -Usually unnecessary: one Gateway can serve multiple messaging channels and agents. Use multiple Gateways only for redundancy or strict isolation (ex: rescue bot). - -Supported if you isolate state + config and use unique ports. Full guide: [Multiple gateways](/gateway/multiple-gateways). - -Service names are profile-aware: - -- macOS: `bot.molt.` (legacy `com.openclaw.*` may still exist) -- Linux: `openclaw-gateway-.service` -- Windows: `OpenClaw Gateway ()` - -Install metadata is embedded in the service config: - -- `OPENCLAW_SERVICE_MARKER=openclaw` -- `OPENCLAW_SERVICE_KIND=gateway` -- `OPENCLAW_SERVICE_VERSION=` - -Rescue-Bot Pattern: keep a second Gateway isolated with its own profile, state dir, workspace, and base port spacing. Full guide: [Rescue-bot guide](/gateway/multiple-gateways#rescue-bot-guide). - -### Dev profile (`--dev`) - -Fast path: run a fully-isolated dev instance (config/state/workspace) without touching your primary setup. +Preferred: Tailscale/VPN. +Fallback: SSH tunnel. ```bash -openclaw --dev setup -openclaw --dev gateway --allow-unconfigured -# then target the dev instance: -openclaw --dev status -openclaw --dev health +ssh -N -L 18789:127.0.0.1:18789 user@host ``` -Defaults (can be overridden via env/flags/config): +Then connect clients to `ws://127.0.0.1:18789` locally. -- `OPENCLAW_STATE_DIR=~/.openclaw-dev` -- `OPENCLAW_CONFIG_PATH=~/.openclaw-dev/openclaw.json` -- `OPENCLAW_GATEWAY_PORT=19001` (Gateway WS + HTTP) -- browser control service port = `19003` (derived: `gateway.port+2`, loopback only) -- `canvasHost.port=19005` (derived: `gateway.port+4`) -- `agents.defaults.workspace` default becomes `~/.openclaw/workspace-dev` when you run `setup`/`onboard` under `--dev`. + +If gateway auth is configured, clients still must send auth (`token`/`password`) even over SSH tunnels. + -Derived ports (rules of thumb): +See: [Remote Gateway](/gateway/remote), [Authentication](/gateway/authentication), [Tailscale](/gateway/tailscale). -- Base port = `gateway.port` (or `OPENCLAW_GATEWAY_PORT` / `--port`) -- browser control service port = base + 2 (loopback only) -- `canvasHost.port = base + 4` (or `OPENCLAW_CANVAS_HOST_PORT` / config override) -- Browser profile CDP ports auto-allocate from `browser.controlPort + 9 .. + 108` (persisted per profile). +## Supervision and service lifecycle + +Use supervised runs for production-like reliability. + + + + +```bash +openclaw gateway install +openclaw gateway status +openclaw gateway restart +openclaw gateway stop +``` + +LaunchAgent labels are `ai.openclaw.gateway` (default) or `ai.openclaw.` (named profile). `openclaw doctor` audits and repairs service config drift. + + + + + +```bash +openclaw gateway install +systemctl --user enable --now openclaw-gateway[-].service +openclaw gateway status +``` + +For persistence after logout, enable lingering: + +```bash +sudo loginctl enable-linger +``` + + + + + +Use a system unit for multi-user/always-on hosts. + +```bash +sudo systemctl daemon-reload +sudo systemctl enable --now openclaw-gateway[-].service +``` + + + + +## Multiple gateways on one host + +Most setups should run **one** Gateway. +Use multiple only for strict isolation/redundancy (for example a rescue profile). Checklist per instance: -- unique `gateway.port` -- unique `OPENCLAW_CONFIG_PATH` -- unique `OPENCLAW_STATE_DIR` -- unique `agents.defaults.workspace` -- separate WhatsApp numbers (if using WA) - -Service install per profile: - -```bash -openclaw --profile main gateway install -openclaw --profile rescue gateway install -``` +- Unique `gateway.port` +- Unique `OPENCLAW_CONFIG_PATH` +- Unique `OPENCLAW_STATE_DIR` +- Unique `agents.defaults.workspace` Example: @@ -127,204 +180,75 @@ OPENCLAW_CONFIG_PATH=~/.openclaw/a.json OPENCLAW_STATE_DIR=~/.openclaw-a opencla OPENCLAW_CONFIG_PATH=~/.openclaw/b.json OPENCLAW_STATE_DIR=~/.openclaw-b openclaw gateway --port 19002 ``` -## Protocol (operator view) +See: [Multiple gateways](/gateway/multiple-gateways). -- Full docs: [Gateway protocol](/gateway/protocol) and [Bridge protocol (legacy)](/gateway/bridge-protocol). -- Mandatory first frame from client: `req {type:"req", id, method:"connect", params:{minProtocol,maxProtocol,client:{id,displayName?,version,platform,deviceFamily?,modelIdentifier?,mode,instanceId?}, caps, auth?, locale?, userAgent? } }`. -- Gateway replies `res {type:"res", id, ok:true, payload:hello-ok }` (or `ok:false` with an error, then closes). -- After handshake: - - Requests: `{type:"req", id, method, params}` → `{type:"res", id, ok, payload|error}` - - Events: `{type:"event", event, payload, seq?, stateVersion?}` -- Structured presence entries: `{host, ip, version, platform?, deviceFamily?, modelIdentifier?, mode, lastInputSeconds?, ts, reason?, tags?[], instanceId? }` (for WS clients, `instanceId` comes from `connect.client.instanceId`). -- `agent` responses are two-stage: first `res` ack `{runId,status:"accepted"}`, then a final `res` `{runId,status:"ok"|"error",summary}` after the run finishes; streamed output arrives as `event:"agent"`. - -## Methods (initial set) - -- `health` — full health snapshot (same shape as `openclaw health --json`). -- `status` — short summary. -- `system-presence` — current presence list. -- `system-event` — post a presence/system note (structured). -- `send` — send a message via the active channel(s). -- `agent` — run an agent turn (streams events back on same connection). -- `node.list` — list paired + currently-connected nodes (includes `caps`, `deviceFamily`, `modelIdentifier`, `paired`, `connected`, and advertised `commands`). -- `node.describe` — describe a node (capabilities + supported `node.invoke` commands; works for paired nodes and for currently-connected unpaired nodes). -- `node.invoke` — invoke a command on a node (e.g. `canvas.*`, `camera.*`). -- `node.pair.*` — pairing lifecycle (`request`, `list`, `approve`, `reject`, `verify`). - -See also: [Presence](/concepts/presence) for how presence is produced/deduped and why a stable `client.instanceId` matters. - -## Events - -- `agent` — streamed tool/output events from the agent run (seq-tagged). -- `presence` — presence updates (deltas with stateVersion) pushed to all connected clients. -- `tick` — periodic keepalive/no-op to confirm liveness. -- `shutdown` — Gateway is exiting; payload includes `reason` and optional `restartExpectedMs`. Clients should reconnect. - -## WebChat integration - -- WebChat is a native SwiftUI UI that talks directly to the Gateway WebSocket for history, sends, abort, and events. -- Remote use goes through the same SSH/Tailscale tunnel; if a gateway token is configured, the client includes it during `connect`. -- macOS app connects via a single WS (shared connection); it hydrates presence from the initial snapshot and listens for `presence` events to update the UI. - -## Typing and validation - -- Server validates every inbound frame with AJV against JSON Schema emitted from the protocol definitions. -- Clients (TS/Swift) consume generated types (TS directly; Swift via the repo’s generator). -- Protocol definitions are the source of truth; regenerate schema/models with: - - `pnpm protocol:gen` - - `pnpm protocol:gen:swift` - -## Connection snapshot - -- `hello-ok` includes a `snapshot` with `presence`, `health`, `stateVersion`, and `uptimeMs` plus `policy {maxPayload,maxBufferedBytes,tickIntervalMs}` so clients can render immediately without extra requests. -- `health`/`system-presence` remain available for manual refresh, but are not required at connect time. - -## Error codes (res.error shape) - -- Errors use `{ code, message, details?, retryable?, retryAfterMs? }`. -- Standard codes: - - `NOT_LINKED` — WhatsApp not authenticated. - - `AGENT_TIMEOUT` — agent did not respond within the configured deadline. - - `INVALID_REQUEST` — schema/param validation failed. - - `UNAVAILABLE` — Gateway is shutting down or a dependency is unavailable. - -## Keepalive behavior - -- `tick` events (or WS ping/pong) are emitted periodically so clients know the Gateway is alive even when no traffic occurs. -- Send/agent acknowledgements remain separate responses; do not overload ticks for sends. - -## Replay / gaps - -- Events are not replayed. Clients detect seq gaps and should refresh (`health` + `system-presence`) before continuing. WebChat and macOS clients now auto-refresh on gap. - -## Supervision (macOS example) - -- Use launchd to keep the service alive: - - Program: path to `openclaw` - - Arguments: `gateway` - - KeepAlive: true - - StandardOut/Err: file paths or `syslog` -- On failure, launchd restarts; fatal misconfig should keep exiting so the operator notices. -- LaunchAgents are per-user and require a logged-in session; for headless setups use a custom LaunchDaemon (not shipped). - - `openclaw gateway install` writes `~/Library/LaunchAgents/bot.molt.gateway.plist` - (or `bot.molt..plist`; legacy `com.openclaw.*` is cleaned up). - - `openclaw doctor` audits the LaunchAgent config and can update it to current defaults. - -## Gateway service management (CLI) - -Use the Gateway CLI for install/start/stop/restart/status: +### Dev profile quick path ```bash -openclaw gateway status -openclaw gateway install -openclaw gateway stop -openclaw gateway restart -openclaw logs --follow +openclaw --dev setup +openclaw --dev gateway --allow-unconfigured +openclaw --dev status ``` -Notes: +Defaults include isolated state/config and base gateway port `19001`. -- `gateway status` probes the Gateway RPC by default using the service’s resolved port/config (override with `--url`). -- `gateway status --deep` adds system-level scans (LaunchDaemons/system units). -- `gateway status --no-probe` skips the RPC probe (useful when networking is down). -- `gateway status --json` is stable for scripts. -- `gateway status` reports **supervisor runtime** (launchd/systemd running) separately from **RPC reachability** (WS connect + status RPC). -- `gateway status` prints config path + probe target to avoid “localhost vs LAN bind” confusion and profile mismatches. -- `gateway status` includes the last gateway error line when the service looks running but the port is closed. -- `logs` tails the Gateway file log via RPC (no manual `tail`/`grep` needed). -- If other gateway-like services are detected, the CLI warns unless they are OpenClaw profile services. - We still recommend **one gateway per machine** for most setups; use isolated profiles/ports for redundancy or a rescue bot. See [Multiple gateways](/gateway/multiple-gateways). - - Cleanup: `openclaw gateway uninstall` (current service) and `openclaw doctor` (legacy migrations). -- `gateway install` is a no-op when already installed; use `openclaw gateway install --force` to reinstall (profile/env/path changes). +## Protocol quick reference (operator view) -Bundled mac app: +- First client frame must be `connect`. +- Gateway returns `hello-ok` snapshot (`presence`, `health`, `stateVersion`, `uptimeMs`, limits/policy). +- Requests: `req(method, params)` → `res(ok/payload|error)`. +- Common events: `connect.challenge`, `agent`, `chat`, `presence`, `tick`, `health`, `heartbeat`, `shutdown`. -- OpenClaw.app can bundle a Node-based gateway relay and install a per-user LaunchAgent labeled - `bot.molt.gateway` (or `bot.molt.`; legacy `com.openclaw.*` labels still unload cleanly). -- To stop it cleanly, use `openclaw gateway stop` (or `launchctl bootout gui/$UID/bot.molt.gateway`). -- To restart, use `openclaw gateway restart` (or `launchctl kickstart -k gui/$UID/bot.molt.gateway`). - - `launchctl` only works if the LaunchAgent is installed; otherwise use `openclaw gateway install` first. - - Replace the label with `bot.molt.` when running a named profile. +Agent runs are two-stage: -## Supervision (systemd user unit) +1. Immediate accepted ack (`status:"accepted"`) +2. Final completion response (`status:"ok"|"error"`), with streamed `agent` events in between. -OpenClaw installs a **systemd user service** by default on Linux/WSL2. We -recommend user services for single-user machines (simpler env, per-user config). -Use a **system service** for multi-user or always-on servers (no lingering -required, shared supervision). - -`openclaw gateway install` writes the user unit. `openclaw doctor` audits the -unit and can update it to match the current recommended defaults. - -Create `~/.config/systemd/user/openclaw-gateway[-].service`: - -``` -[Unit] -Description=OpenClaw Gateway (profile: , v) -After=network-online.target -Wants=network-online.target - -[Service] -ExecStart=/usr/local/bin/openclaw gateway --port 18789 -Restart=always -RestartSec=5 -Environment=OPENCLAW_GATEWAY_TOKEN= -WorkingDirectory=/home/youruser - -[Install] -WantedBy=default.target -``` - -Enable lingering (required so the user service survives logout/idle): - -``` -sudo loginctl enable-linger youruser -``` - -Onboarding runs this on Linux/WSL2 (may prompt for sudo; writes `/var/lib/systemd/linger`). -Then enable the service: - -``` -systemctl --user enable --now openclaw-gateway[-].service -``` - -**Alternative (system service)** - for always-on or multi-user servers, you can -install a systemd **system** unit instead of a user unit (no lingering needed). -Create `/etc/systemd/system/openclaw-gateway[-].service` (copy the unit above, -switch `WantedBy=multi-user.target`, set `User=` + `WorkingDirectory=`), then: - -``` -sudo systemctl daemon-reload -sudo systemctl enable --now openclaw-gateway[-].service -``` - -## Windows (WSL2) - -Windows installs should use **WSL2** and follow the Linux systemd section above. +See full protocol docs: [Gateway Protocol](/gateway/protocol). ## Operational checks -- Liveness: open WS and send `req:connect` → expect `res` with `payload.type="hello-ok"` (with snapshot). -- Readiness: call `health` → expect `ok: true` and a linked channel in `linkChannel` (when applicable). -- Debug: subscribe to `tick` and `presence` events; ensure `status` shows linked/auth age; presence entries show Gateway host and connected clients. +### Liveness + +- Open WS and send `connect`. +- Expect `hello-ok` response with snapshot. + +### Readiness + +```bash +openclaw gateway status +openclaw channels status --probe +openclaw health +``` + +### Gap recovery + +Events are not replayed. On sequence gaps, refresh state (`health`, `system-presence`) before continuing. + +## Common failure signatures + +| Signature | Likely issue | +| -------------------------------------------------------------- | ---------------------------------------- | +| `refusing to bind gateway ... without auth` | Non-loopback bind without token/password | +| `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 between client and gateway | + +For full diagnosis ladders, use [Gateway Troubleshooting](/gateway/troubleshooting). ## Safety guarantees -- Assume one Gateway per host by default; if you run multiple profiles, isolate ports/state and target the right instance. -- No fallback to direct Baileys connections; if the Gateway is down, sends fail fast. -- Non-connect first frames or malformed JSON are rejected and the socket is closed. -- Graceful shutdown: emit `shutdown` event before closing; clients must handle close + reconnect. +- Gateway protocol clients fail fast when Gateway is unavailable (no implicit direct-channel fallback). +- Invalid/non-connect first frames are rejected and closed. +- Graceful shutdown emits `shutdown` event before socket close. -## CLI helpers +--- -- `openclaw gateway health|status` — request health/status over the Gateway WS. -- `openclaw message send --target --message "hi" [--media ...]` — send via Gateway (idempotent for WhatsApp). -- `openclaw agent --message "hi" --to ` — run an agent turn (waits for final by default). -- `openclaw gateway call --params '{"k":"v"}'` — raw method invoker for debugging. -- `openclaw gateway stop|restart` — stop/restart the supervised gateway service (launchd/systemd). -- Gateway helper subcommands assume a running gateway on `--url`; they no longer auto-spawn one. +Related: -## Migration guidance - -- Retire uses of `openclaw gateway` and the legacy TCP control port. -- Update clients to speak the WS protocol with mandatory connect and structured presence. +- [Troubleshooting](/gateway/troubleshooting) +- [Background Process](/gateway/background-process) +- [Configuration](/gateway/configuration) +- [Health](/gateway/health) +- [Doctor](/gateway/doctor) +- [Authentication](/gateway/authentication) diff --git a/docs/start/getting-started.md b/docs/start/getting-started.md index 7d852be828e..c4bed93d33f 100644 --- a/docs/start/getting-started.md +++ b/docs/start/getting-started.md @@ -34,6 +34,11 @@ Check your Node version with `node --version` if you are unsure. ```bash curl -fsSL https://openclaw.ai/install.sh | bash ``` + Install Script Process ```powershell