6 custom skills (assign-task, dispatch-webhook, daily-briefing, task-capture, qmd-brain, tts-voice) with technical documentation. Compatible with Claude Code, OpenClaw, Codex CLI, and OpenCode.
806 lines
171 KiB
JSON
Executable File
806 lines
171 KiB
JSON
Executable File
{
|
||
"title": "Configuration 🔧",
|
||
"content": "OpenClaw reads an optional **JSON5** config from `~/.openclaw/openclaw.json` (comments + trailing commas allowed).\n\nIf 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:\n\n* restrict who can trigger the bot (`channels.whatsapp.allowFrom`, `channels.telegram.allowFrom`, etc.)\n* control group allowlists + mention behavior (`channels.whatsapp.groups`, `channels.telegram.groups`, `channels.discord.guilds`, `agents.list[].groupChat`)\n* customize message prefixes (`messages`)\n* set the agent's workspace (`agents.defaults.workspace` or `agents.list[].workspace`)\n* tune the embedded agent defaults (`agents.defaults`) and session behavior (`session`)\n* set per-agent identity (`agents.list[].identity`)\n\n> **New to configuration?** Check out the [Configuration Examples](/gateway/configuration-examples) guide for complete examples with detailed explanations!\n\n## Strict config validation\n\nOpenClaw only accepts configurations that fully match the schema.\nUnknown keys, malformed types, or invalid values cause the Gateway to **refuse to start** for safety.\n\nWhen validation fails:\n\n* The Gateway does not boot.\n* Only diagnostic commands are allowed (for example: `openclaw doctor`, `openclaw logs`, `openclaw health`, `openclaw status`, `openclaw service`, `openclaw help`).\n* Run `openclaw doctor` to see the exact issues.\n* Run `openclaw doctor --fix` (or `--yes`) to apply migrations/repairs.\n\nDoctor never writes changes unless you explicitly opt into `--fix`/`--yes`.\n\nThe Gateway exposes a JSON Schema representation of the config via `config.schema` for UI editors.\nThe Control UI renders a form from this schema, with a **Raw JSON** editor as an escape hatch.\n\nChannel plugins and extensions can register schema + UI hints for their config, so channel settings\nstay schema-driven across apps without hard-coded forms.\n\nHints (labels, grouping, sensitive fields) ship alongside the schema so clients can render\nbetter forms without hard-coding config knowledge.\n\n## Apply + restart (RPC)\n\nUse `config.apply` to validate + write the full config and restart the Gateway in one step.\nIt writes a restart sentinel and pings the last active session after the Gateway comes back.\n\nWarning: `config.apply` replaces the **entire config**. If you want to change only a few keys,\nuse `config.patch` or `openclaw config set`. Keep a backup of `~/.openclaw/openclaw.json`.\n\n* `raw` (string) — JSON5 payload for the entire config\n* `baseHash` (optional) — config hash from `config.get` (required when a config already exists)\n* `sessionKey` (optional) — last active session key for the wake-up ping\n* `note` (optional) — note to include in the restart sentinel\n* `restartDelayMs` (optional) — delay before restart (default 2000)\n\nExample (via `gateway call`):\n\n## Partial updates (RPC)\n\nUse `config.patch` to merge a partial update into the existing config without clobbering\nunrelated keys. It applies JSON merge patch semantics:\n\n* objects merge recursively\n* `null` deletes a key\n* arrays replace\n Like `config.apply`, it validates, writes the config, stores a restart sentinel, and schedules\n the Gateway restart (with an optional wake when `sessionKey` is provided).\n\n* `raw` (string) — JSON5 payload containing just the keys to change\n* `baseHash` (required) — config hash from `config.get`\n* `sessionKey` (optional) — last active session key for the wake-up ping\n* `note` (optional) — note to include in the restart sentinel\n* `restartDelayMs` (optional) — delay before restart (default 2000)\n\n## Minimal config (recommended starting point)\n\nBuild the default image once with:\n\n## Self-chat mode (recommended for group control)\n\nTo prevent the bot from responding to WhatsApp @-mentions in groups (only respond to specific text triggers):\n\n## Config Includes (`$include`)\n\nSplit your config into multiple files using the `$include` directive. This is useful for:\n\n* Organizing large configs (e.g., per-client agent definitions)\n* Sharing common settings across environments\n* Keeping sensitive configs separate\n\n* **Single file**: Replaces the object containing `$include`\n* **Array of files**: Deep-merges files in order (later files override earlier ones)\n* **With sibling keys**: Sibling keys are merged after includes (override included values)\n* **Sibling keys + arrays/primitives**: Not supported (included content must be an object)\n\nIncluded files can themselves contain `$include` directives (up to 10 levels deep):\n\n* **Relative paths**: Resolved relative to the including file\n* **Absolute paths**: Used as-is\n* **Parent directories**: `../` references work as expected\n\n* **Missing file**: Clear error with resolved path\n* **Parse error**: Shows which included file failed\n* **Circular includes**: Detected and reported with include chain\n\n### Example: Multi-client legal setup\n\n### Env vars + `.env`\n\nOpenClaw reads env vars from the parent process (shell, launchd/systemd, CI, etc.).\n\nAdditionally, it loads:\n\n* `.env` from the current working directory (if present)\n* a global fallback `.env` from `~/.openclaw/.env` (aka `$OPENCLAW_STATE_DIR/.env`)\n\nNeither `.env` file overrides existing env vars.\n\nYou can also provide inline env vars in config. These are only applied if the\nprocess env is missing the key (same non-overriding rule):\n\nSee [/environment](/environment) for full precedence and sources.\n\n### `env.shellEnv` (optional)\n\nOpt-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).\nThis effectively sources your shell profile.\n\n* `OPENCLAW_LOAD_SHELL_ENV=1`\n* `OPENCLAW_SHELL_ENV_TIMEOUT_MS=15000`\n\n### Env var substitution in config\n\nYou can reference environment variables directly in any config string value using\n`${VAR_NAME}` syntax. Variables are substituted at config load time, before validation.\n\n* Only uppercase env var names are matched: `[A-Z_][A-Z0-9_]*`\n* Missing or empty env vars throw an error at config load\n* Escape with `$${VAR}` to output a literal `${VAR}`\n* Works with `$include` (included files also get substitution)\n\n**Inline substitution:**\n\n### Auth storage (OAuth + API keys)\n\nOpenClaw stores **per-agent** auth profiles (OAuth + API keys) in:\n\n* `<agentDir>/auth-profiles.json` (default: `~/.openclaw/agents/<agentId>/agent/auth-profiles.json`)\n\nSee also: [/concepts/oauth](/concepts/oauth)\n\nLegacy OAuth imports:\n\n* `~/.openclaw/credentials/oauth.json` (or `$OPENCLAW_STATE_DIR/credentials/oauth.json`)\n\nThe embedded Pi agent maintains a runtime cache at:\n\n* `<agentDir>/auth.json` (managed automatically; don’t edit manually)\n\nLegacy agent dir (pre multi-agent):\n\n* `~/.openclaw/agent/*` (migrated by `openclaw doctor` into `~/.openclaw/agents/<defaultAgentId>/agent/*`)\n\n* OAuth dir (legacy import only): `OPENCLAW_OAUTH_DIR`\n* Agent dir (default agent root override): `OPENCLAW_AGENT_DIR` (preferred), `PI_CODING_AGENT_DIR` (legacy)\n\nOn first use, OpenClaw imports `oauth.json` entries into `auth-profiles.json`.\n\nOptional metadata for auth profiles. This does **not** store secrets; it maps\nprofile IDs to a provider + mode (and optional email) and defines the provider\nrotation order used for failover.\n\n### `agents.list[].identity`\n\nOptional per-agent identity used for defaults and UX. This is written by the macOS onboarding assistant.\n\nIf set, OpenClaw derives defaults (only when you haven’t set them explicitly):\n\n* `messages.ackReaction` from the **active agent**’s `identity.emoji` (falls back to 👀)\n* `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)\n* `identity.avatar` accepts a workspace-relative image path or a remote URL/data URL. Local files must live inside the agent workspace.\n\n`identity.avatar` accepts:\n\n* Workspace-relative path (must stay within the agent workspace)\n* `http(s)` URL\n* `data:` URI\n\nMetadata written by CLI wizards (`onboard`, `configure`, `doctor`).\n\n* Default log file: `/tmp/openclaw/openclaw-YYYY-MM-DD.log`\n* If you want a stable path, set `logging.file` to `/tmp/openclaw/openclaw.log`.\n* Console output can be tuned separately via:\n * `logging.consoleLevel` (defaults to `info`, bumps to `debug` when `--verbose`)\n * `logging.consoleStyle` (`pretty` | `compact` | `json`)\n* Tool summaries can be redacted to avoid leaking secrets:\n * `logging.redactSensitive` (`off` | `tools`, default: `tools`)\n * `logging.redactPatterns` (array of regex strings; overrides defaults)\n\n### `channels.whatsapp.dmPolicy`\n\nControls how WhatsApp direct chats (DMs) are handled:\n\n* `\"pairing\"` (default): unknown senders get a pairing code; owner must approve\n* `\"allowlist\"`: only allow senders in `channels.whatsapp.allowFrom` (or paired allow store)\n* `\"open\"`: allow all inbound DMs (**requires** `channels.whatsapp.allowFrom` to include `\"*\"`)\n* `\"disabled\"`: ignore all inbound DMs\n\nPairing 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.\n\n* `openclaw pairing list whatsapp`\n* `openclaw pairing approve whatsapp <code>`\n\n### `channels.whatsapp.allowFrom`\n\nAllowlist of E.164 phone numbers that may trigger WhatsApp auto-replies (**DMs only**).\nIf empty and `channels.whatsapp.dmPolicy=\"pairing\"`, unknown senders will receive a pairing code.\nFor groups, use `channels.whatsapp.groupPolicy` + `channels.whatsapp.groupAllowFrom`.\n\n### `channels.whatsapp.sendReadReceipts`\n\nControls whether inbound WhatsApp messages are marked as read (blue ticks). Default: `true`.\n\nSelf-chat mode always skips read receipts, even when enabled.\n\nPer-account override: `channels.whatsapp.accounts.<id>.sendReadReceipts`.\n\n### `channels.whatsapp.accounts` (multi-account)\n\nRun multiple WhatsApp accounts in one gateway:\n\n* Outbound commands default to account `default` if present; otherwise the first configured account id (sorted).\n* The legacy single-account Baileys auth dir is migrated by `openclaw doctor` into `whatsapp/default`.\n\n### `channels.telegram.accounts` / `channels.discord.accounts` / `channels.googlechat.accounts` / `channels.slack.accounts` / `channels.mattermost.accounts` / `channels.signal.accounts` / `channels.imessage.accounts`\n\nRun multiple accounts per channel (each account has its own `accountId` and optional `name`):\n\n* `default` is used when `accountId` is omitted (CLI + routing).\n* Env tokens only apply to the **default** account.\n* Base channel settings (group policy, mention gating, etc.) apply to all accounts unless overridden per account.\n* Use `bindings[].match.accountId` to route each account to a different agents.defaults.\n\n### Group chat mention gating (`agents.list[].groupChat` + `messages.groupChat`)\n\nGroup messages default to **require mention** (either metadata mention or regex patterns). Applies to WhatsApp, Telegram, Discord, Google Chat, and iMessage group chats.\n\n* **Metadata mentions**: Native platform @-mentions (e.g., WhatsApp tap-to-mention). Ignored in WhatsApp self-chat mode (see `channels.whatsapp.allowFrom`).\n* **Text patterns**: Regex patterns defined in `agents.list[].groupChat.mentionPatterns`. Always checked regardless of self-chat mode.\n* Mention gating is enforced only when mention detection is possible (native mentions or at least one `mentionPattern`).\n\n`messages.groupChat.historyLimit` sets the global default for group history context. Channels can override with `channels.<channel>.historyLimit` (or `channels.<channel>.accounts.*.historyLimit` for multi-account). Set `0` to disable history wrapping.\n\n#### DM history limits\n\nDM conversations use session-based history managed by the agent. You can limit the number of user turns retained per DM session:\n\n1. Per-DM override: `channels.<provider>.dms[userId].historyLimit`\n2. Provider default: `channels.<provider>.dmHistoryLimit`\n3. No limit (all history retained)\n\nSupported providers: `telegram`, `whatsapp`, `discord`, `slack`, `signal`, `imessage`, `msteams`.\n\nPer-agent override (takes precedence when set, even `[]`):\n\nMention 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.\n\nTo respond **only** to specific text triggers (ignoring native @-mentions):\n\n### Group policy (per channel)\n\nUse `channels.*.groupPolicy` to control whether group/room messages are accepted at all:\n\n* `\"open\"`: groups bypass allowlists; mention-gating still applies.\n* `\"disabled\"`: block all group/room messages.\n* `\"allowlist\"`: only allow groups/rooms that match the configured allowlist.\n* `channels.defaults.groupPolicy` sets the default when a provider’s `groupPolicy` is unset.\n* WhatsApp/Telegram/Signal/iMessage/Microsoft Teams use `groupAllowFrom` (fallback: explicit `allowFrom`).\n* Discord/Slack use channel allowlists (`channels.discord.guilds.*.channels`, `channels.slack.channels`).\n* Group DMs (Discord/Slack) are still controlled by `dm.groupEnabled` + `dm.groupChannels`.\n* Default is `groupPolicy: \"allowlist\"` (unless overridden by `channels.defaults.groupPolicy`); if no allowlist is configured, group messages are blocked.\n\n### Multi-agent routing (`agents.list` + `bindings`)\n\nRun multiple isolated agents (separate workspace, `agentDir`, sessions) inside one Gateway.\nInbound messages are routed to an agent via bindings.\n\n* `agents.list[]`: per-agent overrides.\n * `id`: stable agent id (required).\n * `default`: optional; when multiple are set, the first wins and a warning is logged.\n If none are set, the **first entry** in the list is the default agent.\n * `name`: display name for the agent.\n * `workspace`: default `~/.openclaw/workspace-<agentId>` (for `main`, falls back to `agents.defaults.workspace`).\n * `agentDir`: default `~/.openclaw/agents/<agentId>/agent`.\n * `model`: per-agent default model, overrides `agents.defaults.model` for that agent.\n * string form: `\"provider/model\"`, overrides only `agents.defaults.model.primary`\n * object form: `{ primary, fallbacks }` (fallbacks override `agents.defaults.model.fallbacks`; `[]` disables global fallbacks for that agent)\n * `identity`: per-agent name/theme/emoji (used for mention patterns + ack reactions).\n * `groupChat`: per-agent mention-gating (`mentionPatterns`).\n * `sandbox`: per-agent sandbox config (overrides `agents.defaults.sandbox`).\n * `mode`: `\"off\"` | `\"non-main\"` | `\"all\"`\n * `workspaceAccess`: `\"none\"` | `\"ro\"` | `\"rw\"`\n * `scope`: `\"session\"` | `\"agent\"` | `\"shared\"`\n * `workspaceRoot`: custom sandbox workspace root\n * `docker`: per-agent docker overrides (e.g. `image`, `network`, `env`, `setupCommand`, limits; ignored when `scope: \"shared\"`)\n * `browser`: per-agent sandboxed browser overrides (ignored when `scope: \"shared\"`)\n * `prune`: per-agent sandbox pruning overrides (ignored when `scope: \"shared\"`)\n * `subagents`: per-agent sub-agent defaults.\n * `allowAgents`: allowlist of agent ids for `sessions_spawn` from this agent (`[\"*\"]` = allow any; default: only same agent)\n * `tools`: per-agent tool restrictions (applied before sandbox tool policy).\n * `profile`: base tool profile (applied before allow/deny)\n * `allow`: array of allowed tool names\n * `deny`: array of denied tool names (deny wins)\n* `agents.defaults`: shared agent defaults (model, workspace, sandbox, etc.).\n* `bindings[]`: routes inbound messages to an `agentId`.\n * `match.channel` (required)\n * `match.accountId` (optional; `*` = any account; omitted = default account)\n * `match.peer` (optional; `{ kind: dm|group|channel, id }`)\n * `match.guildId` / `match.teamId` (optional; channel-specific)\n\nDeterministic match order:\n\n1. `match.peer`\n2. `match.guildId`\n3. `match.teamId`\n4. `match.accountId` (exact, no peer/guild/team)\n5. `match.accountId: \"*\"` (channel-wide, no peer/guild/team)\n6. default agent (`agents.list[].default`, else first list entry, else `\"main\"`)\n\nWithin each match tier, the first matching entry in `bindings` wins.\n\n#### Per-agent access profiles (multi-agent)\n\nEach agent can carry its own sandbox + tool policy. Use this to mix access\nlevels in one gateway:\n\n* **Full access** (personal agent)\n* **Read-only** tools + workspace\n* **No filesystem access** (messaging/session tools only)\n\nSee [Multi-Agent Sandbox & Tools](/multi-agent-sandbox-tools) for precedence and\nadditional examples.\n\nFull access (no sandbox):\n\nRead-only tools + read-only workspace:\n\nNo filesystem access (messaging/session tools enabled):\n\nExample: two WhatsApp accounts → two agents:\n\n### `tools.agentToAgent` (optional)\n\nAgent-to-agent messaging is opt-in:\n\nControls how inbound messages behave when an agent run is already active.\n\n### `messages.inbound`\n\nDebounce rapid inbound messages from the **same sender** so multiple back-to-back\nmessages become a single agent turn. Debouncing is scoped per channel + conversation\nand uses the most recent message for reply threading/IDs.\n\n* Debounce batches **text-only** messages; media/attachments flush immediately.\n* Control commands (e.g. `/queue`, `/new`) bypass debouncing so they stay standalone.\n\n### `commands` (chat command handling)\n\nControls how chat commands are enabled across connectors.\n\n* Text commands must be sent as a **standalone** message and use the leading `/` (no plain-text aliases).\n* `commands.text: false` disables parsing chat messages for commands.\n* `commands.native: \"auto\"` (default) turns on native commands for Discord/Telegram and leaves Slack off; unsupported channels stay text-only.\n* 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.\n* `channels.telegram.customCommands` adds extra Telegram bot menu entries. Names are normalized; conflicts with native commands are ignored.\n* `commands.bash: true` enables `! <cmd>` to run host shell commands (`/bash <cmd>` also works as an alias). Requires `tools.elevated.enabled` and allowlisting the sender in `tools.elevated.allowFrom.<channel>`.\n* `commands.bashForegroundMs` controls how long bash waits before backgrounding. While a bash job is running, new `! <cmd>` requests are rejected (one at a time).\n* `commands.config: true` enables `/config` (reads/writes `openclaw.json`).\n* `channels.<provider>.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).\n* `commands.debug: true` enables `/debug` (runtime-only overrides).\n* `commands.restart: true` enables `/restart` and the gateway tool restart action.\n* `commands.useAccessGroups: false` allows commands to bypass access-group allowlists/policies.\n* Slash commands and directives are only honored for **authorized senders**. Authorization is derived from\n channel allowlists/pairing plus `commands.useAccessGroups`.\n\n### `web` (WhatsApp web channel runtime)\n\nWhatsApp runs through the gateway’s web channel (Baileys Web). It starts automatically when a linked session exists.\nSet `web.enabled: false` to keep it off by default.\n\n### `channels.telegram` (bot transport)\n\nOpenClaw 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.\nSet `channels.telegram.enabled: false` to disable automatic startup.\nMulti-account support lives under `channels.telegram.accounts` (see the multi-account section above). Env tokens only apply to the default account.\nSet `channels.telegram.configWrites: false` to block Telegram-initiated config writes (including supergroup ID migrations and `/config set|unset`).\n\nDraft streaming notes:\n\n* Uses Telegram `sendMessageDraft` (draft bubble, not a real message).\n* Requires **private chat topics** (message\\_thread\\_id in DMs; bot has topics enabled).\n* `/reasoning stream` streams reasoning into the draft, then sends the final answer.\n Retry policy defaults and behavior are documented in [Retry policy](/concepts/retry).\n\n### `channels.discord` (bot transport)\n\nConfigure the Discord bot by setting the bot token and optional gating:\nMulti-account support lives under `channels.discord.accounts` (see the multi-account section above). Env tokens only apply to the default account.\n\nOpenClaw 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:<id>` (DM) or `channel:<id>` (guild channel) when specifying delivery targets for cron/CLI commands; bare numeric IDs are ambiguous and rejected.\nGuild 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.\nBot-authored messages are ignored by default. Enable with `channels.discord.allowBots` (own messages are still filtered to prevent self-reply loops).\nReaction notification modes:\n\n* `off`: no reaction events.\n* `own`: reactions on the bot's own messages (default).\n* `all`: all reactions on all messages.\n* `allowlist`: reactions from `guilds.<id>.users` on all messages (empty list disables).\n 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.\n Retry policy defaults and behavior are documented in [Retry policy](/concepts/retry).\n\n### `channels.googlechat` (Chat API webhook)\n\nGoogle Chat runs over HTTP webhooks with app-level auth (service account).\nMulti-account support lives under `channels.googlechat.accounts` (see the multi-account section above). Env vars only apply to the default account.\n\n* Service account JSON can be inline (`serviceAccount`) or file-based (`serviceAccountFile`).\n* Env fallbacks for the default account: `GOOGLE_CHAT_SERVICE_ACCOUNT` or `GOOGLE_CHAT_SERVICE_ACCOUNT_FILE`.\n* `audienceType` + `audience` must match the Chat app’s webhook auth config.\n* Use `spaces/<spaceId>` or `users/<userId|email>` when setting delivery targets.\n\n### `channels.slack` (socket mode)\n\nSlack runs in Socket Mode and requires both a bot token and app token:\n\nMulti-account support lives under `channels.slack.accounts` (see the multi-account section above). Env tokens only apply to the default account.\n\nOpenClaw starts Slack when the provider is enabled and both tokens are set (via config or `SLACK_BOT_TOKEN` + `SLACK_APP_TOKEN`). Use `user:<id>` (DM) or `channel:<id>` when specifying delivery targets for cron/CLI commands.\nSet `channels.slack.configWrites: false` to block Slack-initiated config writes (including channel ID migrations and `/config set|unset`).\n\nBot-authored messages are ignored by default. Enable with `channels.slack.allowBots` or `channels.slack.channels.<id>.allowBots`.\n\nReaction notification modes:\n\n* `off`: no reaction events.\n* `own`: reactions on the bot's own messages (default).\n* `all`: all reactions on all messages.\n* `allowlist`: reactions from `channels.slack.reactionAllowlist` on all messages (empty list disables).\n\nThread session isolation:\n\n* `channels.slack.thread.historyScope` controls whether thread history is per-thread (`thread`, default) or shared across the channel (`channel`).\n* `channels.slack.thread.inheritParent` controls whether new thread sessions inherit the parent channel transcript (default: false).\n\nSlack action groups (gate `slack` tool actions):\n\n| Action group | Default | Notes |\n| ------------ | ------- | ---------------------- |\n| reactions | enabled | React + list reactions |\n| messages | enabled | Read/send/edit/delete |\n| pins | enabled | Pin/unpin/list |\n| memberInfo | enabled | Member info |\n| emojiList | enabled | Custom emoji list |\n\n### `channels.mattermost` (bot token)\n\nMattermost ships as a plugin and is not bundled with the core install.\nInstall it first: `openclaw plugins install @openclaw/mattermost` (or `./extensions/mattermost` from a git checkout).\n\nMattermost requires a bot token plus the base URL for your server:\n\nOpenClaw 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`).\n\n* `oncall` (default): respond to channel messages only when @mentioned.\n* `onmessage`: respond to every channel message.\n* `onchar`: respond when a message starts with a trigger prefix (`channels.mattermost.oncharPrefixes`, default `[\">\", \"!\"]`).\n\n* Default DMs: `channels.mattermost.dmPolicy=\"pairing\"` (unknown senders get a pairing code).\n* Public DMs: `channels.mattermost.dmPolicy=\"open\"` plus `channels.mattermost.allowFrom=[\"*\"]`.\n* Groups: `channels.mattermost.groupPolicy=\"allowlist\"` by default (mention-gated). Use `channels.mattermost.groupAllowFrom` to restrict senders.\n\nMulti-account support lives under `channels.mattermost.accounts` (see the multi-account section above). Env vars only apply to the default account.\nUse `channel:<id>` or `user:<id>` (or `@username`) when specifying delivery targets; bare ids are treated as channel ids.\n\n### `channels.signal` (signal-cli)\n\nSignal reactions can emit system events (shared reaction tooling):\n\nReaction notification modes:\n\n* `off`: no reaction events.\n* `own`: reactions on the bot's own messages (default).\n* `all`: all reactions on all messages.\n* `allowlist`: reactions from `channels.signal.reactionAllowlist` on all messages (empty list disables).\n\n### `channels.imessage` (imsg CLI)\n\nOpenClaw spawns `imsg rpc` (JSON-RPC over stdio). No daemon or port required.\n\nMulti-account support lives under `channels.imessage.accounts` (see the multi-account section above).\n\n* Requires Full Disk Access to the Messages DB.\n* The first send will prompt for Messages automation permission.\n* Prefer `chat_id:<id>` targets. Use `imsg chats --limit 20` to list chats.\n* `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.\n* For remote SSH wrappers, set `channels.imessage.remoteHost` to fetch attachments via SCP when `includeAttachments` is enabled.\n\n### `agents.defaults.workspace`\n\nSets the **single global workspace directory** used by the agent for file operations.\n\nDefault: `~/.openclaw/workspace`.\n\nIf `agents.defaults.sandbox` is enabled, non-main sessions can override this with their\nown per-scope workspaces under `agents.defaults.sandbox.workspaceRoot`.\n\n### `agents.defaults.repoRoot`\n\nOptional repository root to show in the system prompt’s Runtime line. If unset, OpenClaw\ntries to detect a `.git` directory by walking upward from the workspace (and current\nworking directory). The path must exist to be used.\n\n### `agents.defaults.skipBootstrap`\n\nDisables automatic creation of the workspace bootstrap files (`AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, and `BOOTSTRAP.md`).\n\nUse this for pre-seeded deployments where your workspace files come from a repo.\n\n### `agents.defaults.bootstrapMaxChars`\n\nMax characters of each workspace bootstrap file injected into the system prompt\nbefore truncation. Default: `20000`.\n\nWhen a file exceeds this limit, OpenClaw logs a warning and injects a truncated\nhead/tail with a marker.\n\n### `agents.defaults.userTimezone`\n\nSets the user’s timezone for **system prompt context** (not for timestamps in\nmessage envelopes). If unset, OpenClaw uses the host timezone at runtime.\n\n### `agents.defaults.timeFormat`\n\nControls the **time format** shown in the system prompt’s Current Date & Time section.\nDefault: `auto` (OS preference).\n\nControls inbound/outbound prefixes and optional ack reactions.\nSee [Messages](/concepts/messages) for queueing, sessions, and streaming context.\n\n`responsePrefix` is applied to **all outbound replies** (tool summaries, block\nstreaming, final replies) across channels unless already present.\n\nOverrides can be configured per channel and per account:\n\n* `channels.<channel>.responsePrefix`\n* `channels.<channel>.accounts.<id>.responsePrefix`\n\nResolution order (most specific wins):\n\n1. `channels.<channel>.accounts.<id>.responsePrefix`\n2. `channels.<channel>.responsePrefix`\n3. `messages.responsePrefix`\n\n* `undefined` falls through to the next level.\n* `\"\"` explicitly disables the prefix and stops the cascade.\n* `\"auto\"` derives `[{identity.name}]` for the routed agent.\n\nOverrides apply to all channels, including extensions, and to every outbound reply kind.\n\nIf `messages.responsePrefix` is unset, no prefix is applied by default. WhatsApp self-chat\nreplies are the exception: they default to `[{identity.name}]` when set, otherwise\n`[openclaw]`, so same-phone conversations stay legible.\nSet it to `\"auto\"` to derive `[{identity.name}]` for the routed agent (when set).\n\n#### Template variables\n\nThe `responsePrefix` string can include template variables that resolve dynamically:\n\n| Variable | Description | Example |\n| ----------------- | ---------------------- | --------------------------- |\n| `{model}` | Short model name | `claude-opus-4-5`, `gpt-4o` |\n| `{modelFull}` | Full model identifier | `anthropic/claude-opus-4-5` |\n| `{provider}` | Provider name | `anthropic`, `openai` |\n| `{thinkingLevel}` | Current thinking level | `high`, `low`, `off` |\n| `{identity.name}` | Agent identity name | (same as `\"auto\"` mode) |\n\nVariables are case-insensitive (`{MODEL}` = `{model}`). `{think}` is an alias for `{thinkingLevel}`.\nUnresolved variables remain as literal text.\n\nExample output: `[claude-opus-4-5 | think:high] Here's my response...`\n\nWhatsApp inbound prefix is configured via `channels.whatsapp.messagePrefix` (deprecated:\n`messages.messagePrefix`). Default stays **unchanged**: `\"[openclaw]\"` when\n`channels.whatsapp.allowFrom` is empty, otherwise `\"\"` (no prefix). When using\n`\"[openclaw]\"`, OpenClaw will instead use `[{identity.name}]` when the routed\nagent has `identity.name` set.\n\n`ackReaction` sends a best-effort emoji reaction to acknowledge inbound messages\non channels that support reactions (Slack/Discord/Telegram/Google Chat). Defaults to the\nactive agent’s `identity.emoji` when set, otherwise `\"👀\"`. Set it to `\"\"` to disable.\n\n`ackReactionScope` controls when reactions fire:\n\n* `group-mentions` (default): only when a group/room requires mentions **and** the bot was mentioned\n* `group-all`: all group/room messages\n* `direct`: direct messages only\n* `all`: all messages\n\n`removeAckAfterReply` removes the bot’s ack reaction after a reply is sent\n(Slack/Discord/Telegram/Google Chat only). Default: `false`.\n\nEnable text-to-speech for outbound replies. When on, OpenClaw generates audio\nusing ElevenLabs or OpenAI and attaches it to responses. Telegram uses Opus\nvoice notes; other channels send MP3 audio.\n\n* `messages.tts.auto` controls auto‑TTS (`off`, `always`, `inbound`, `tagged`).\n* `/tts off|always|inbound|tagged` sets the per‑session auto mode (overrides config).\n* `messages.tts.enabled` is legacy; doctor migrates it to `messages.tts.auto`.\n* `prefsPath` stores local overrides (provider/limit/summarize).\n* `maxTextLength` is a hard cap for TTS input; summaries are truncated to fit.\n* `summaryModel` overrides `agents.defaults.model.primary` for auto-summary.\n * Accepts `provider/model` or an alias from `agents.defaults.models`.\n* `modelOverrides` enables model-driven overrides like `[[tts:...]]` tags (on by default).\n* `/tts limit` and `/tts summary` control per-user summarization settings.\n* `apiKey` values fall back to `ELEVENLABS_API_KEY`/`XI_API_KEY` and `OPENAI_API_KEY`.\n* `elevenlabs.baseUrl` overrides the ElevenLabs API base URL.\n* `elevenlabs.voiceSettings` supports `stability`/`similarityBoost`/`style` (0..1),\n `useSpeakerBoost`, and `speed` (0.5..2.0).\n\nDefaults for Talk mode (macOS/iOS/Android). Voice IDs fall back to `ELEVENLABS_VOICE_ID` or `SAG_VOICE_ID` when unset.\n`apiKey` falls back to `ELEVENLABS_API_KEY` (or the gateway’s shell profile) when unset.\n`voiceAliases` lets Talk directives use friendly names (e.g. `\"voice\":\"Clawd\"`).\n\n### `agents.defaults`\n\nControls the embedded agent runtime (model/thinking/verbose/timeouts).\n`agents.defaults.models` defines the configured model catalog (and acts as the allowlist for `/model`).\n`agents.defaults.model.primary` sets the default model; `agents.defaults.model.fallbacks` are global failovers.\n`agents.defaults.imageModel` is optional and is **only used if the primary model lacks image input**.\nEach `agents.defaults.models` entry can include:\n\n* `alias` (optional model shortcut, e.g. `/opus`).\n* `params` (optional provider-specific API params passed through to the model request).\n\n`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.\n\nZ.AI GLM-4.x models automatically enable thinking mode unless you:\n\n* set `--thinking off`, or\n* define `agents.defaults.models[\"zai/<model>\"].params.thinking` yourself.\n\nOpenClaw also ships a few built-in alias shorthands. Defaults only apply when the model\nis already present in `agents.defaults.models`:\n\n* `opus` -> `anthropic/claude-opus-4-5`\n* `sonnet` -> `anthropic/claude-sonnet-4-5`\n* `gpt` -> `openai/gpt-5.2`\n* `gpt-mini` -> `openai/gpt-5-mini`\n* `gemini` -> `google/gemini-3-pro-preview`\n* `gemini-flash` -> `google/gemini-3-flash-preview`\n\nIf you configure the same alias name (case-insensitive) yourself, your value wins (defaults never override).\n\nExample: Opus 4.5 primary with MiniMax M2.1 fallback (hosted MiniMax):\n\nMiniMax auth: set `MINIMAX_API_KEY` (env) or configure `models.providers.minimax`.\n\n#### `agents.defaults.cliBackends` (CLI fallback)\n\nOptional CLI backends for text-only fallback runs (no tool calls). These are useful as a\nbackup path when API providers fail. Image pass-through is supported when you configure\nan `imageArg` that accepts file paths.\n\n* CLI backends are **text-first**; tools are always disabled.\n* Sessions are supported when `sessionArg` is set; session ids are persisted per backend.\n* For `claude-cli`, defaults are wired in. Override the command path if PATH is minimal\n (launchd/systemd).\n\n#### `agents.defaults.contextPruning` (tool-result pruning)\n\n`agents.defaults.contextPruning` prunes **old tool results** from the in-memory context right before a request is sent to the LLM.\nIt does **not** modify the session history on disk (`*.jsonl` remains complete).\n\nThis is intended to reduce token usage for chatty agents that accumulate large tool outputs over time.\n\n* Never touches user/assistant messages.\n* Protects the last `keepLastAssistants` assistant messages (no tool results after that point are pruned).\n* Protects the bootstrap prefix (nothing before the first user message is pruned).\n* Modes:\n * `adaptive`: soft-trims oversized tool results (keep head/tail) when the estimated context ratio crosses `softTrimRatio`.\n Then hard-clears the oldest eligible tool results when the estimated context ratio crosses `hardClearRatio` **and**\n there’s enough prunable tool-result bulk (`minPrunableToolChars`).\n * `aggressive`: always replaces eligible tool results before the cutoff with the `hardClear.placeholder` (no ratio checks).\n\nSoft vs hard pruning (what changes in the context sent to the LLM):\n\n* **Soft-trim**: only for *oversized* tool results. Keeps the beginning + end and inserts `...` in the middle.\n * Before: `toolResult(\"…very long output…\")`\n * After: `toolResult(\"HEAD…\\n...\\n…TAIL\\n\\n[Tool result trimmed: …]\")`\n* **Hard-clear**: replaces the entire tool result with the placeholder.\n * Before: `toolResult(\"…very long output…\")`\n * After: `toolResult(\"[Old tool result content cleared]\")`\n\nNotes / current limitations:\n\n* Tool results containing **image blocks are skipped** (never trimmed/cleared) right now.\n* The estimated “context ratio” is based on **characters** (approximate), not exact tokens.\n* If the session doesn’t contain at least `keepLastAssistants` assistant messages yet, pruning is skipped.\n* In `aggressive` mode, `hardClear.enabled` is ignored (eligible tool results are always replaced with `hardClear.placeholder`).\n\nDefaults (when `mode` is `\"adaptive\"` or `\"aggressive\"`):\n\n* `keepLastAssistants`: `3`\n* `softTrimRatio`: `0.3` (adaptive only)\n* `hardClearRatio`: `0.5` (adaptive only)\n* `minPrunableToolChars`: `50000` (adaptive only)\n* `softTrim`: `{ maxChars: 4000, headChars: 1500, tailChars: 1500 }` (adaptive only)\n* `hardClear`: `{ enabled: true, placeholder: \"[Old tool result content cleared]\" }`\n\nExample (aggressive, minimal):\n\nExample (adaptive tuned):\n\nSee [/concepts/session-pruning](/concepts/session-pruning) for behavior details.\n\n#### `agents.defaults.compaction` (reserve headroom + memory flush)\n\n`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).\n\n`agents.defaults.compaction.reserveTokensFloor` enforces a minimum `reserveTokens`\nvalue for Pi compaction (default: `20000`). Set it to `0` to disable the floor.\n\n`agents.defaults.compaction.memoryFlush` runs a **silent** agentic turn before\nauto-compaction, instructing the model to store durable memories on disk (e.g.\n`memory/YYYY-MM-DD.md`). It triggers when the session token estimate crosses a\nsoft threshold below the compaction limit.\n\n* `memoryFlush.enabled`: `true`\n* `memoryFlush.softThresholdTokens`: `4000`\n* `memoryFlush.prompt` / `memoryFlush.systemPrompt`: built-in defaults with `NO_REPLY`\n* Note: memory flush is skipped when the session workspace is read-only\n (`agents.defaults.sandbox.workspaceAccess: \"ro\"` or `\"none\"`).\n\n* `agents.defaults.blockStreamingDefault`: `\"on\"`/`\"off\"` (default off).\n* Channel overrides: `*.blockStreaming` (and per-account variants) to force block streaming on/off.\n Non-Telegram channels require an explicit `*.blockStreaming: true` to enable block replies.\n* `agents.defaults.blockStreamingBreak`: `\"text_end\"` or `\"message_end\"` (default: text\\_end).\n* `agents.defaults.blockStreamingChunk`: soft chunking for streamed blocks. Defaults to\n 800–1200 chars, prefers paragraph breaks (`\\n\\n`), then newlines, then sentences.\n Example:\n \n* `agents.defaults.blockStreamingCoalesce`: merge streamed blocks before sending.\n Defaults to `{ idleMs: 1000 }` and inherits `minChars` from `blockStreamingChunk`\n with `maxChars` capped to the channel text limit. Signal/Slack/Discord/Google Chat default\n to `minChars: 1500` unless overridden.\n Channel overrides: `channels.whatsapp.blockStreamingCoalesce`, `channels.telegram.blockStreamingCoalesce`,\n `channels.discord.blockStreamingCoalesce`, `channels.slack.blockStreamingCoalesce`, `channels.mattermost.blockStreamingCoalesce`,\n `channels.signal.blockStreamingCoalesce`, `channels.imessage.blockStreamingCoalesce`, `channels.msteams.blockStreamingCoalesce`,\n `channels.googlechat.blockStreamingCoalesce`\n (and per-account variants).\n* `agents.defaults.humanDelay`: randomized pause between **block replies** after the first.\n Modes: `off` (default), `natural` (800–2500ms), `custom` (use `minMs`/`maxMs`).\n Per-agent override: `agents.list[].humanDelay`.\n Example:\n \n See [/concepts/streaming](/concepts/streaming) for behavior + chunking details.\n\n* `agents.defaults.typingMode`: `\"never\" | \"instant\" | \"thinking\" | \"message\"`. Defaults to\n `instant` for direct chats / mentions and `message` for unmentioned group chats.\n* `session.typingMode`: per-session override for the mode.\n* `agents.defaults.typingIntervalSeconds`: how often the typing signal is refreshed (default: 6s).\n* `session.typingIntervalSeconds`: per-session override for the refresh interval.\n See [/concepts/typing-indicators](/concepts/typing-indicators) for behavior details.\n\n`agents.defaults.model.primary` should be set as `provider/model` (e.g. `anthropic/claude-opus-4-5`).\nAliases come from `agents.defaults.models.*.alias` (e.g. `Opus`).\nIf you omit the provider, OpenClaw currently assumes `anthropic` as a temporary\ndeprecation fallback.\nZ.AI models are available as `zai/<model>` (e.g. `zai/glm-4.7`) and require\n`ZAI_API_KEY` (or legacy `Z_AI_API_KEY`) in the environment.\n\n`agents.defaults.heartbeat` configures periodic heartbeat runs:\n\n* `every`: duration string (`ms`, `s`, `m`, `h`); default unit minutes. Default:\n `30m`. Set `0m` to disable.\n* `model`: optional override model for heartbeat runs (`provider/model`).\n* `includeReasoning`: when `true`, heartbeats will also deliver the separate `Reasoning:` message when available (same shape as `/reasoning on`). Default: `false`.\n* `session`: optional session key to control which session the heartbeat runs in. Default: `main`.\n* `to`: optional recipient override (channel-specific id, e.g. E.164 for WhatsApp, chat id for Telegram).\n* `target`: optional delivery channel (`last`, `whatsapp`, `telegram`, `discord`, `slack`, `msteams`, `signal`, `imessage`, `none`). Default: `last`.\n* `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.\n* `ackMaxChars`: max chars allowed after `HEARTBEAT_OK` before delivery (default: 300).\n\nPer-agent heartbeats:\n\n* Set `agents.list[].heartbeat` to enable or override heartbeat settings for a specific agent.\n* If any agent entry defines `heartbeat`, **only those agents** run heartbeats; defaults\n become the shared baseline for those agents.\n\nHeartbeats run full agent turns. Shorter intervals burn more tokens; be mindful\nof `every`, keep `HEARTBEAT.md` tiny, and/or choose a cheaper `model`.\n\n`tools.exec` configures background exec defaults:\n\n* `backgroundMs`: time before auto-background (ms, default 10000)\n* `timeoutSec`: auto-kill after this runtime (seconds, default 1800)\n* `cleanupMs`: how long to keep finished sessions in memory (ms, default 1800000)\n* `notifyOnExit`: enqueue a system event + request heartbeat when backgrounded exec exits (default true)\n* `applyPatch.enabled`: enable experimental `apply_patch` (OpenAI/OpenAI Codex only; default false)\n* `applyPatch.allowModels`: optional allowlist of model ids (e.g. `gpt-5.2` or `openai/gpt-5.2`)\n Note: `applyPatch` is only under `tools.exec`.\n\n`tools.web` configures web search + fetch tools:\n\n* `tools.web.search.enabled` (default: true when key is present)\n* `tools.web.search.apiKey` (recommended: set via `openclaw configure --section web`, or use `BRAVE_API_KEY` env var)\n* `tools.web.search.maxResults` (1–10, default 5)\n* `tools.web.search.timeoutSeconds` (default 30)\n* `tools.web.search.cacheTtlMinutes` (default 15)\n* `tools.web.fetch.enabled` (default true)\n* `tools.web.fetch.maxChars` (default 50000)\n* `tools.web.fetch.maxCharsCap` (default 50000; clamps maxChars from config/tool calls)\n* `tools.web.fetch.timeoutSeconds` (default 30)\n* `tools.web.fetch.cacheTtlMinutes` (default 15)\n* `tools.web.fetch.userAgent` (optional override)\n* `tools.web.fetch.readability` (default true; disable to use basic HTML cleanup only)\n* `tools.web.fetch.firecrawl.enabled` (default true when an API key is set)\n* `tools.web.fetch.firecrawl.apiKey` (optional; defaults to `FIRECRAWL_API_KEY`)\n* `tools.web.fetch.firecrawl.baseUrl` (default [https://api.firecrawl.dev](https://api.firecrawl.dev))\n* `tools.web.fetch.firecrawl.onlyMainContent` (default true)\n* `tools.web.fetch.firecrawl.maxAgeMs` (optional)\n* `tools.web.fetch.firecrawl.timeoutSeconds` (optional)\n\n`tools.media` configures inbound media understanding (image/audio/video):\n\n* `tools.media.models`: shared model list (capability-tagged; used after per-cap lists).\n* `tools.media.concurrency`: max concurrent capability runs (default 2).\n* `tools.media.image` / `tools.media.audio` / `tools.media.video`:\n * `enabled`: opt-out switch (default true when models are configured).\n * `prompt`: optional prompt override (image/video append a `maxChars` hint automatically).\n * `maxChars`: max output characters (default 500 for image/video; unset for audio).\n * `maxBytes`: max media size to send (defaults: image 10MB, audio 20MB, video 50MB).\n * `timeoutSeconds`: request timeout (defaults: image 60s, audio 60s, video 120s).\n * `language`: optional audio hint.\n * `attachments`: attachment policy (`mode`, `maxAttachments`, `prefer`).\n * `scope`: optional gating (first match wins) with `match.channel`, `match.chatType`, or `match.keyPrefix`.\n * `models`: ordered list of model entries; failures or oversize media fall back to the next entry.\n* Each `models[]` entry:\n * Provider entry (`type: \"provider\"` or omitted):\n * `provider`: API provider id (`openai`, `anthropic`, `google`/`gemini`, `groq`, etc).\n * `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).\n * `profile` / `preferredProfile`: auth profile selection.\n * CLI entry (`type: \"cli\"`):\n * `command`: executable to run.\n * `args`: templated args (supports `{{MediaPath}}`, `{{Prompt}}`, `{{MaxChars}}`, etc).\n * `capabilities`: optional list (`image`, `audio`, `video`) to gate a shared entry. Defaults when omitted: `openai`/`anthropic`/`minimax` → image, `google` → image+audio+video, `groq` → audio.\n * `prompt`, `maxChars`, `maxBytes`, `timeoutSeconds`, `language` can be overridden per entry.\n\nIf no models are configured (or `enabled: false`), understanding is skipped; the model still receives the original attachments.\n\nProvider 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`).\n\n`agents.defaults.subagents` configures sub-agent defaults:\n\n* `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.\n* `maxConcurrent`: max concurrent sub-agent runs (default 1)\n* `archiveAfterMinutes`: auto-archive sub-agent sessions after N minutes (default 60; set `0` to disable)\n* Per-subagent tool policy: `tools.subagents.tools.allow` / `tools.subagents.tools.deny` (deny wins)\n\n`tools.profile` sets a **base tool allowlist** before `tools.allow`/`tools.deny`:\n\n* `minimal`: `session_status` only\n* `coding`: `group:fs`, `group:runtime`, `group:sessions`, `group:memory`, `image`\n* `messaging`: `group:messaging`, `sessions_list`, `sessions_history`, `sessions_send`, `session_status`\n* `full`: no restriction (same as unset)\n\nPer-agent override: `agents.list[].tools.profile`.\n\nExample (messaging-only by default, allow Slack + Discord tools too):\n\nExample (coding profile, but deny exec/process everywhere):\n\n`tools.byProvider` lets you **further restrict** tools for specific providers (or a single `provider/model`).\nPer-agent override: `agents.list[].tools.byProvider`.\n\nOrder: base profile → provider profile → allow/deny policies.\nProvider keys accept either `provider` (e.g. `google-antigravity`) or `provider/model`\n(e.g. `openai/gpt-5.2`).\n\nExample (keep global coding profile, but minimal tools for Google Antigravity):\n\nExample (provider/model-specific allowlist):\n\n`tools.allow` / `tools.deny` configure a global tool allow/deny policy (deny wins).\nMatching is case-insensitive and supports `*` wildcards (`\"*\"` means all tools).\nThis is applied even when the Docker sandbox is **off**.\n\nExample (disable browser/canvas everywhere):\n\nTool groups (shorthands) work in **global** and **per-agent** tool policies:\n\n* `group:runtime`: `exec`, `bash`, `process`\n* `group:fs`: `read`, `write`, `edit`, `apply_patch`\n* `group:sessions`: `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn`, `session_status`\n* `group:memory`: `memory_search`, `memory_get`\n* `group:web`: `web_search`, `web_fetch`\n* `group:ui`: `browser`, `canvas`\n* `group:automation`: `cron`, `gateway`\n* `group:messaging`: `message`\n* `group:nodes`: `nodes`\n* `group:openclaw`: all built-in OpenClaw tools (excludes provider plugins)\n\n`tools.elevated` controls elevated (host) exec access:\n\n* `enabled`: allow elevated mode (default true)\n* `allowFrom`: per-channel allowlists (empty = disabled)\n * `whatsapp`: E.164 numbers\n * `telegram`: chat ids or usernames\n * `discord`: user ids or usernames (falls back to `channels.discord.dm.allowFrom` if omitted)\n * `signal`: E.164 numbers\n * `imessage`: handles/chat ids\n * `webchat`: session ids or usernames\n\nPer-agent override (further restrict):\n\n* `tools.elevated` is the global baseline. `agents.list[].tools.elevated` can only further restrict (both must allow).\n* `/elevated on|off|ask|full` stores state per session key; inline directives apply to a single message.\n* Elevated `exec` runs on the host and bypasses sandboxing.\n* Tool policy still applies; if `exec` is denied, elevated cannot be used.\n\n`agents.defaults.maxConcurrent` sets the maximum number of embedded agent runs that can\nexecute in parallel across sessions. Each session is still serialized (one run\nper session key at a time). Default: 1.\n\n### `agents.defaults.sandbox`\n\nOptional **Docker sandboxing** for the embedded agent. Intended for non-main\nsessions so they cannot access your host system.\n\nDetails: [Sandboxing](/gateway/sandboxing)\n\nDefaults (if enabled):\n\n* scope: `\"agent\"` (one container + workspace per agent)\n* Debian bookworm-slim based image\n* agent workspace access: `workspaceAccess: \"none\"` (default)\n * `\"none\"`: use a per-scope sandbox workspace under `~/.openclaw/sandboxes`\n* `\"ro\"`: keep the sandbox workspace at `/workspace`, and mount the agent workspace read-only at `/agent` (disables `write`/`edit`/`apply_patch`)\n * `\"rw\"`: mount the agent workspace read/write at `/workspace`\n* auto-prune: idle > 24h OR age > 7d\n* tool policy: allow only `exec`, `process`, `read`, `write`, `edit`, `apply_patch`, `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn`, `session_status` (deny wins)\n * configure via `tools.sandbox.tools`, override per-agent via `agents.list[].tools.sandbox.tools`\n * 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))\n* optional sandboxed browser (Chromium + CDP, noVNC observer)\n* hardening knobs: `network`, `user`, `pidsLimit`, `memory`, `cpus`, `ulimits`, `seccompProfile`, `apparmorProfile`\n\nWarning: `scope: \"shared\"` means a shared container and shared workspace. No\ncross-session isolation. Use `scope: \"session\"` for per-session isolation.\n\nLegacy: `perSession` is still supported (`true` → `scope: \"session\"`,\n`false` → `scope: \"shared\"`).\n\n`setupCommand` runs **once** after the container is created (inside the container via `sh -lc`).\nFor package installs, ensure network egress, a writable root FS, and a root user.\n\nBuild the default sandbox image once with:\n\nNote: sandbox containers default to `network: \"none\"`; set `agents.defaults.sandbox.docker.network`\nto `\"bridge\"` (or your custom network) if the agent needs outbound access.\n\nNote: inbound attachments are staged into the active workspace at `media/inbound/*`. With `workspaceAccess: \"rw\"`, that means files are written into the agent workspace.\n\nNote: `docker.binds` mounts additional host directories; global and per-agent binds are merged.\n\nBuild the optional browser image with:\n\nWhen `agents.defaults.sandbox.browser.enabled=true`, the browser tool uses a sandboxed\nChromium instance (CDP). If noVNC is enabled (default when headless=false),\nthe noVNC URL is injected into the system prompt so the agent can reference it.\nThis does not require `browser.enabled` in the main config; the sandbox control\nURL is injected per session.\n\n`agents.defaults.sandbox.browser.allowHostControl` (default: false) allows\nsandboxed sessions to explicitly target the **host** browser control server\nvia the browser tool (`target: \"host\"`). Leave this off if you want strict\nsandbox isolation.\n\nAllowlists for remote control:\n\n* `allowedControlUrls`: exact control URLs permitted for `target: \"custom\"`.\n* `allowedControlHosts`: hostnames permitted (hostname only, no port).\n* `allowedControlPorts`: ports permitted (defaults: http=80, https=443).\n Defaults: all allowlists are unset (no restriction). `allowHostControl` defaults to false.\n\n### `models` (custom providers + base URLs)\n\nOpenClaw uses the **pi-coding-agent** model catalog. You can add custom providers\n(LiteLLM, local OpenAI-compatible servers, Anthropic proxies, etc.) by writing\n`~/.openclaw/agents/<agentId>/agent/models.json` or by defining the same schema inside your\nOpenClaw config under `models.providers`.\nProvider-by-provider overview + examples: [/concepts/model-providers](/concepts/model-providers).\n\nWhen `models.providers` is present, OpenClaw writes/merges a `models.json` into\n`~/.openclaw/agents/<agentId>/agent/` on startup:\n\n* default behavior: **merge** (keeps existing providers, overrides on name)\n* set `models.mode: \"replace\"` to overwrite the file contents\n\nSelect the model via `agents.defaults.model.primary` (provider/model).\n\n### OpenCode Zen (multi-model proxy)\n\nOpenCode Zen is a multi-model gateway with per-model endpoints. OpenClaw uses\nthe built-in `opencode` provider from pi-ai; set `OPENCODE_API_KEY` (or\n`OPENCODE_ZEN_API_KEY`) from [https://opencode.ai/auth](https://opencode.ai/auth).\n\n* Model refs use `opencode/<modelId>` (example: `opencode/claude-opus-4-5`).\n* If you enable an allowlist via `agents.defaults.models`, add each model you plan to use.\n* Shortcut: `openclaw onboard --auth-choice opencode-zen`.\n\n### Z.AI (GLM-4.7) — provider alias support\n\nZ.AI models are available via the built-in `zai` provider. Set `ZAI_API_KEY`\nin your environment and reference the model by provider/model.\n\nShortcut: `openclaw onboard --auth-choice zai-api-key`.\n\n* `z.ai/*` and `z-ai/*` are accepted aliases and normalize to `zai/*`.\n* If `ZAI_API_KEY` is missing, requests to `zai/*` will fail with an auth error at runtime.\n* Example error: `No API key found for provider \"zai\".`\n* Z.AI’s general API endpoint is `https://api.z.ai/api/paas/v4`. GLM coding\n requests use the dedicated Coding endpoint `https://api.z.ai/api/coding/paas/v4`.\n The built-in `zai` provider uses the Coding endpoint. If you need the general\n endpoint, define a custom provider in `models.providers` with the base URL\n override (see the custom providers section above).\n* Use a fake placeholder in docs/configs; never commit real API keys.\n\n### Moonshot AI (Kimi)\n\nUse Moonshot's OpenAI-compatible endpoint:\n\n* Set `MOONSHOT_API_KEY` in the environment or use `openclaw onboard --auth-choice moonshot-api-key`.\n* Model ref: `moonshot/kimi-k2.5`.\n* For the China endpoint, either:\n * Run `openclaw onboard --auth-choice moonshot-api-key-cn` (wizard will set `https://api.moonshot.cn/v1`), or\n * Manually set `baseUrl: \"https://api.moonshot.cn/v1\"` in `models.providers.moonshot`.\n\nUse Moonshot AI's Kimi Coding endpoint (Anthropic-compatible, built-in provider):\n\n* Set `KIMI_API_KEY` in the environment or use `openclaw onboard --auth-choice kimi-code-api-key`.\n* Model ref: `kimi-coding/k2p5`.\n\n### Synthetic (Anthropic-compatible)\n\nUse Synthetic's Anthropic-compatible endpoint:\n\n* Set `SYNTHETIC_API_KEY` or use `openclaw onboard --auth-choice synthetic-api-key`.\n* Model ref: `synthetic/hf:MiniMaxAI/MiniMax-M2.1`.\n* Base URL should omit `/v1` because the Anthropic client appends it.\n\n### Local models (LM Studio) — recommended setup\n\nSee [/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.\n\nUse MiniMax M2.1 directly without LM Studio:\n\n* Set `MINIMAX_API_KEY` environment variable or use `openclaw onboard --auth-choice minimax-api`.\n* Available model: `MiniMax-M2.1` (default).\n* Update pricing in `models.json` if you need exact cost tracking.\n\n### Cerebras (GLM 4.6 / 4.7)\n\nUse Cerebras via their OpenAI-compatible endpoint:\n\n* Use `cerebras/zai-glm-4.7` for Cerebras; use `zai/glm-4.7` for Z.AI direct.\n* Set `CEREBRAS_API_KEY` in the environment or config.\n\n* Supported APIs: `openai-completions`, `openai-responses`, `anthropic-messages`,\n `google-generative-ai`\n* Use `authHeader: true` + `headers` for custom auth needs.\n* Override the agent config root with `OPENCLAW_AGENT_DIR` (or `PI_CODING_AGENT_DIR`)\n if you want `models.json` stored elsewhere (default: `~/.openclaw/agents/main/agent`).\n\nControls session scoping, reset policy, reset triggers, and where the session store is written.\n\n* `mainKey`: direct-chat bucket key (default: `\"main\"`). Useful when you want to “rename” the primary DM thread without changing `agentId`.\n * 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.\n* `dmScope`: how DM sessions are grouped (default: `\"main\"`).\n * `main`: all DMs share the main session for continuity.\n * `per-peer`: isolate DMs by sender id across channels.\n * `per-channel-peer`: isolate DMs per channel + sender (recommended for multi-user inboxes).\n * `per-account-channel-peer`: isolate DMs per account + channel + sender (recommended for multi-account inboxes).\n * 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\"`).\n* `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`.\n * Example: `alice: [\"telegram:123456789\", \"discord:987654321012345678\"]`.\n* `reset`: primary reset policy. Defaults to daily resets at 4:00 AM local time on the gateway host.\n * `mode`: `daily` or `idle` (default: `daily` when `reset` is present).\n * `atHour`: local hour (0-23) for the daily reset boundary.\n * `idleMinutes`: sliding idle window in minutes. When daily + idle are both configured, whichever expires first wins.\n* `resetByType`: per-session overrides for `dm`, `group`, and `thread`.\n * If you only set legacy `session.idleMinutes` without any `reset`/`resetByType`, OpenClaw stays in idle-only mode for backward compatibility.\n* `heartbeatIdleMinutes`: optional idle override for heartbeat checks (daily reset still applies when enabled).\n* `agentToAgent.maxPingPongTurns`: max reply-back turns between requester/target (0–5, default 5).\n* `sendPolicy.default`: `allow` or `deny` fallback when no rule matches.\n* `sendPolicy.rules[]`: match by `channel`, `chatType` (`direct|group|room`), or `keyPrefix` (e.g. `cron:`). First deny wins; otherwise allow.\n\n### `skills` (skills config)\n\nControls bundled allowlist, install preferences, extra skill folders, and per-skill\noverrides. Applies to **bundled** skills and `~/.openclaw/skills` (workspace skills\nstill win on name conflicts).\n\n* `allowBundled`: optional allowlist for **bundled** skills only. If set, only those\n bundled skills are eligible (managed/workspace skills unaffected).\n* `load.extraDirs`: additional skill directories to scan (lowest precedence).\n* `install.preferBrew`: prefer brew installers when available (default: true).\n* `install.nodeManager`: node installer preference (`npm` | `pnpm` | `yarn`, default: npm).\n* `entries.<skillKey>`: per-skill config overrides.\n\n* `enabled`: set `false` to disable a skill even if it’s bundled/installed.\n* `env`: environment variables injected for the agent run (only if not already set).\n* `apiKey`: optional convenience for skills that declare a primary env var (e.g. `nano-banana-pro` → `GEMINI_API_KEY`).\n\n### `plugins` (extensions)\n\nControls plugin discovery, allow/deny, and per-plugin config. Plugins are loaded\nfrom `~/.openclaw/extensions`, `<workspace>/.openclaw/extensions`, plus any\n`plugins.load.paths` entries. **Config changes require a gateway restart.**\nSee [/plugin](/plugin) for full usage.\n\n* `enabled`: master toggle for plugin loading (default: true).\n* `allow`: optional allowlist of plugin ids; when set, only listed plugins load.\n* `deny`: optional denylist of plugin ids (deny wins).\n* `load.paths`: extra plugin files or directories to load (absolute or `~`).\n* `entries.<pluginId>`: per-plugin overrides.\n * `enabled`: set `false` to disable.\n * `config`: plugin-specific config object (validated by the plugin if provided).\n\n### `browser` (openclaw-managed browser)\n\nOpenClaw can start a **dedicated, isolated** Chrome/Brave/Edge/Chromium instance for openclaw and expose a small loopback control service.\nProfiles can point at a **remote** Chromium-based browser via `profiles.<name>.cdpUrl`. Remote\nprofiles are attach-only (start/stop/reset are disabled).\n\n`browser.cdpUrl` remains for legacy single-profile configs and as the base\nscheme/host for profiles that only set `cdpPort`.\n\n* enabled: `true`\n* evaluateEnabled: `true` (set `false` to disable `act:evaluate` and `wait --fn`)\n* control service: loopback only (port derived from `gateway.port`, default `18791`)\n* CDP URL: `http://127.0.0.1:18792` (control service + 1, legacy single-profile)\n* profile color: `#FF4500` (lobster-orange)\n* Note: the control server is started by the running gateway (OpenClaw\\.app menubar, or `openclaw gateway`).\n* Auto-detect order: default browser if Chromium-based; otherwise Chrome → Brave → Edge → Chromium → Chrome Canary.\n\n### `ui` (Appearance)\n\nOptional accent color used by the native apps for UI chrome (e.g. Talk Mode bubble tint).\n\nIf unset, clients fall back to a muted light-blue.\n\n### `gateway` (Gateway server mode + bind)\n\nUse `gateway.mode` to explicitly declare whether this machine should run the Gateway.\n\n* mode: **unset** (treated as “do not auto-start”)\n* bind: `loopback`\n* port: `18789` (single port for WS + HTTP)\n\nControl UI base path:\n\n* `gateway.controlUi.basePath` sets the URL prefix where the Control UI is served.\n* Examples: `\"/ui\"`, `\"/openclaw\"`, `\"/apps/openclaw\"`.\n* Default: root (`/`) (unchanged).\n* `gateway.controlUi.root` sets the filesystem root for Control UI assets (default: `dist/control-ui`).\n* `gateway.controlUi.allowInsecureAuth` allows token-only auth for the Control UI when\n device identity is omitted (typically over HTTP). Default: `false`. Prefer HTTPS\n (Tailscale Serve) or `127.0.0.1`.\n* `gateway.controlUi.dangerouslyDisableDeviceAuth` disables device identity checks for the\n Control UI (token/password only). Default: `false`. Break-glass only.\n\n* [Control UI](/web/control-ui)\n* [Web overview](/web)\n* [Tailscale](/gateway/tailscale)\n* [Remote access](/gateway/remote)\n\n* `gateway.trustedProxies`: list of reverse proxy IPs that terminate TLS in front of the Gateway.\n* 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.\n* Only list proxies you fully control, and ensure they **overwrite** incoming `x-forwarded-for`.\n\n* `openclaw gateway` refuses to start unless `gateway.mode` is set to `local` (or you pass the override flag).\n* `gateway.port` controls the single multiplexed port used for WebSocket + HTTP (control UI, hooks, A2UI).\n* OpenAI Chat Completions endpoint: **disabled by default**; enable with `gateway.http.endpoints.chatCompletions.enabled: true`.\n* Precedence: `--port` > `OPENCLAW_GATEWAY_PORT` > `gateway.port` > default `18789`.\n* Gateway auth is required by default (token/password or Tailscale Serve identity). Non-loopback binds require a shared token/password.\n* The onboarding wizard generates a gateway token by default (even on loopback).\n* `gateway.remote.token` is **only** for remote CLI calls; it does not enable local gateway auth. `gateway.token` is ignored.\n\n* `gateway.auth.mode` sets the handshake requirements (`token` or `password`). When unset, token auth is assumed.\n* `gateway.auth.token` stores the shared token for token auth (used by the CLI on the same machine).\n* When `gateway.auth.mode` is set, only that method is accepted (plus optional Tailscale headers).\n* `gateway.auth.password` can be set here, or via `OPENCLAW_GATEWAY_PASSWORD` (recommended).\n* `gateway.auth.allowTailscale` allows Tailscale Serve identity headers\n (`tailscale-user-login`) to satisfy auth when the request arrives on loopback\n with `x-forwarded-for`, `x-forwarded-proto`, and `x-forwarded-host`. OpenClaw\n verifies the identity by resolving the `x-forwarded-for` address via\n `tailscale whois` before accepting it. When `true`, Serve requests do not need\n a token/password; set `false` to require explicit credentials. Defaults to\n `true` when `tailscale.mode = \"serve\"` and auth mode is not `password`.\n* `gateway.tailscale.mode: \"serve\"` uses Tailscale Serve (tailnet only, loopback bind).\n* `gateway.tailscale.mode: \"funnel\"` exposes the dashboard publicly; requires auth.\n* `gateway.tailscale.resetOnExit` resets Serve/Funnel config on shutdown.\n\nRemote client defaults (CLI):\n\n* `gateway.remote.url` sets the default Gateway WebSocket URL for CLI calls when `gateway.mode = \"remote\"`.\n* `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`.\n* `gateway.remote.token` supplies the token for remote calls (leave unset for no auth).\n* `gateway.remote.password` supplies the password for remote calls (leave unset for no auth).\n\n* OpenClaw\\.app watches `~/.openclaw/openclaw.json` and switches modes live when `gateway.mode` or `gateway.remote.url` changes.\n* If `gateway.mode` is unset but `gateway.remote.url` is set, the macOS app treats it as remote mode.\n* 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.\n\nDirect transport example (macOS app):\n\n### `gateway.reload` (Config hot reload)\n\nThe Gateway watches `~/.openclaw/openclaw.json` (or `OPENCLAW_CONFIG_PATH`) and applies changes automatically.\n\n* `hybrid` (default): hot-apply safe changes; restart the Gateway for critical changes.\n* `hot`: only apply hot-safe changes; log when a restart is required.\n* `restart`: restart the Gateway on any config change.\n* `off`: disable hot reload.\n\n#### Hot reload matrix (files + impact)\n\n* `~/.openclaw/openclaw.json` (or `OPENCLAW_CONFIG_PATH`)\n\nHot-applied (no full gateway restart):\n\n* `hooks` (webhook auth/path/mappings) + `hooks.gmail` (Gmail watcher restarted)\n* `browser` (browser control server restart)\n* `cron` (cron service restart + concurrency update)\n* `agents.defaults.heartbeat` (heartbeat runner restart)\n* `web` (WhatsApp web channel restart)\n* `telegram`, `discord`, `signal`, `imessage` (channel restarts)\n* `agent`, `models`, `routing`, `messages`, `session`, `whatsapp`, `logging`, `skills`, `ui`, `talk`, `identity`, `wizard` (dynamic reads)\n\nRequires full Gateway restart:\n\n* `gateway` (port/bind/auth/control UI/tailscale)\n* `bridge` (legacy)\n* `discovery`\n* `canvasHost`\n* `plugins`\n* Any unknown/unsupported config path (defaults to restart for safety)\n\n### Multi-instance isolation\n\nTo run multiple gateways on one host (for redundancy or a rescue bot), isolate per-instance state + config and use unique ports:\n\n* `OPENCLAW_CONFIG_PATH` (per-instance config)\n* `OPENCLAW_STATE_DIR` (sessions/creds)\n* `agents.defaults.workspace` (memories)\n* `gateway.port` (unique per instance)\n\nConvenience flags (CLI):\n\n* `openclaw --dev …` → uses `~/.openclaw-dev` + shifts ports from base `19001`\n* `openclaw --profile <name> …` → uses `~/.openclaw-<name>` (port via config/env/flags)\n\nSee [Gateway runbook](/gateway) for the derived port mapping (gateway/browser/canvas).\nSee [Multiple gateways](/gateway/multiple-gateways) for browser/CDP port isolation details.\n\n### `hooks` (Gateway webhooks)\n\nEnable a simple HTTP webhook endpoint on the Gateway HTTP server.\n\n* enabled: `false`\n* path: `/hooks`\n* maxBodyBytes: `262144` (256 KB)\n\nRequests must include the hook token:\n\n* `Authorization: Bearer <token>` **or**\n* `x-openclaw-token: <token>` **or**\n* `?token=<token>`\n\n* `POST /hooks/wake` → `{ text, mode?: \"now\"|\"next-heartbeat\" }`\n* `POST /hooks/agent` → `{ message, name?, sessionKey?, wakeMode?, deliver?, channel?, to?, model?, thinking?, timeoutSeconds? }`\n* `POST /hooks/<name>` → resolved via `hooks.mappings`\n\n`/hooks/agent` always posts a summary into the main session (and can optionally trigger an immediate heartbeat via `wakeMode: \"now\"`).\n\n* `match.path` matches the sub-path after `/hooks` (e.g. `/hooks/gmail` → `gmail`).\n* `match.source` matches a payload field (e.g. `{ source: \"gmail\" }`) so you can use a generic `/hooks/ingest` path.\n* Templates like `{{messages[0].subject}}` read from the payload.\n* `transform` can point to a JS/TS module that returns a hook action.\n* `deliver: true` sends the final reply to a channel; `channel` defaults to `last` (falls back to WhatsApp).\n* If there is no prior delivery route, set `channel` + `to` explicitly (required for Telegram/Discord/Google Chat/Slack/Signal/iMessage/MS Teams).\n* `model` overrides the LLM for this hook run (`provider/model` or alias; must be allowed if `agents.defaults.models` is set).\n\nGmail helper config (used by `openclaw webhooks gmail setup` / `run`):\n\nModel override for Gmail hooks:\n\n* `hooks.gmail.model` specifies a model to use for Gmail hook processing (defaults to session primary).\n* Accepts `provider/model` refs or aliases from `agents.defaults.models`.\n* Falls back to `agents.defaults.model.fallbacks`, then `agents.defaults.model.primary`, on auth/rate-limit/timeouts.\n* If `agents.defaults.models` is set, include the hooks model in the allowlist.\n* At startup, warns if the configured model is not in the model catalog or allowlist.\n* `hooks.gmail.thinking` sets the default thinking level for Gmail hooks and is overridden by per-hook `thinking`.\n\n* If `hooks.enabled=true` and `hooks.gmail.account` is set, the Gateway starts\n `gog gmail watch serve` on boot and auto-renews the watch.\n* Set `OPENCLAW_SKIP_GMAIL_WATCHER=1` to disable the auto-start (for manual runs).\n* Avoid running a separate `gog gmail watch serve` alongside the Gateway; it will\n fail with `listen tcp 127.0.0.1:8788: bind: address already in use`.\n\nNote: when `tailscale.mode` is on, OpenClaw defaults `serve.path` to `/` so\nTailscale can proxy `/gmail-pubsub` correctly (it strips the set-path prefix).\nIf you need the backend to receive the prefixed path, set\n`hooks.gmail.tailscale.target` to a full URL (and align `serve.path`).\n\n### `canvasHost` (LAN/tailnet Canvas file server + live reload)\n\nThe Gateway serves a directory of HTML/CSS/JS over HTTP so iOS/Android nodes can simply `canvas.navigate` to it.\n\nDefault root: `~/.openclaw/workspace/canvas`\\\nDefault port: `18793` (chosen to avoid the openclaw browser CDP port `18792`)\\\nThe server listens on the **gateway bind host** (LAN or Tailnet) so nodes can reach it.\n\n* serves files under `canvasHost.root`\n* injects a tiny live-reload client into served HTML\n* watches the directory and broadcasts reloads over a WebSocket endpoint at `/__openclaw__/ws`\n* auto-creates a starter `index.html` when the directory is empty (so you see something immediately)\n* also serves A2UI at `/__openclaw__/a2ui/` and is advertised to nodes as `canvasHostUrl`\n (always used by nodes for Canvas/A2UI)\n\nDisable live reload (and file watching) if the directory is large or you hit `EMFILE`:\n\n* config: `canvasHost: { liveReload: false }`\n\nChanges to `canvasHost.*` require a gateway restart (config reload will restart).\n\n* config: `canvasHost: { enabled: false }`\n* env: `OPENCLAW_SKIP_CANVAS_HOST=1`\n\n### `bridge` (legacy TCP bridge, removed)\n\nCurrent builds no longer include the TCP bridge listener; `bridge.*` config keys are ignored.\nNodes connect over the Gateway WebSocket. This section is kept for historical reference.\n\n* The Gateway could expose a simple TCP bridge for nodes (iOS/Android), typically on port `18790`.\n\n* enabled: `true`\n* port: `18790`\n* bind: `lan` (binds to `0.0.0.0`)\n\n* `lan`: `0.0.0.0` (reachable on any interface, including LAN/Wi‑Fi and Tailscale)\n* `tailnet`: bind only to the machine’s Tailscale IP (recommended for Vienna ⇄ London)\n* `loopback`: `127.0.0.1` (local only)\n* `auto`: prefer tailnet IP if present, else `lan`\n\n* `bridge.tls.enabled`: enable TLS for bridge connections (TLS-only when enabled).\n* `bridge.tls.autoGenerate`: generate a self-signed cert when no cert/key are present (default: true).\n* `bridge.tls.certPath` / `bridge.tls.keyPath`: PEM paths for the bridge certificate + private key.\n* `bridge.tls.caPath`: optional PEM CA bundle (custom roots or future mTLS).\n\nWhen TLS is enabled, the Gateway advertises `bridgeTls=1` and `bridgeTlsSha256` in discovery TXT\nrecords so nodes can pin the certificate. Manual connections use trust-on-first-use if no\nfingerprint is stored yet.\nAuto-generated certs require `openssl` on PATH; if generation fails, the bridge will not start.\n\n### `discovery.mdns` (Bonjour / mDNS broadcast mode)\n\nControls LAN mDNS discovery broadcasts (`_openclaw-gw._tcp`).\n\n* `minimal` (default): omit `cliPath` + `sshPort` from TXT records\n* `full`: include `cliPath` + `sshPort` in TXT records\n* `off`: disable mDNS broadcasts entirely\n* Hostname: defaults to `openclaw` (advertises `openclaw.local`). Override with `OPENCLAW_MDNS_HOSTNAME`.\n\n### `discovery.wideArea` (Wide-Area Bonjour / unicast DNS‑SD)\n\nWhen enabled, the Gateway writes a unicast DNS-SD zone for `_openclaw-gw._tcp` under `~/.openclaw/dns/` using the configured discovery domain (example: `openclaw.internal.`).\n\nTo make iOS/Android discover across networks (Vienna ⇄ London), pair this with:\n\n* a DNS server on the gateway host serving your chosen domain (CoreDNS is recommended)\n* Tailscale **split DNS** so clients resolve that domain via the gateway DNS server\n\nOne-time setup helper (gateway host):\n\n## Template variables\n\nTemplate placeholders are expanded in `tools.media.*.models[].args` and `tools.media.models[].args` (and any future templated argument fields).\n\n\\| Variable | Description |\n\\| ------------------ | ------------------------------------------------------------------------------- | -------- | ------- | ---------- | ----- | ------ | -------- | ------- | ------- | --- |\n\\| `{{Body}}` | Full inbound message body |\n\\| `{{RawBody}}` | Raw inbound message body (no history/sender wrappers; best for command parsing) |\n\\| `{{BodyStripped}}` | Body with group mentions stripped (best default for agents) |\n\\| `{{From}}` | Sender identifier (E.164 for WhatsApp; may differ per channel) |\n\\| `{{To}}` | Destination identifier |\n\\| `{{MessageSid}}` | Channel message id (when available) |\n\\| `{{SessionId}}` | Current session UUID |\n\\| `{{IsNewSession}}` | `\"true\"` when a new session was created |\n\\| `{{MediaUrl}}` | Inbound media pseudo-URL (if present) |\n\\| `{{MediaPath}}` | Local media path (if downloaded) |\n\\| `{{MediaType}}` | Media type (image/audio/document/…) |\n\\| `{{Transcript}}` | Audio transcript (when enabled) |\n\\| `{{Prompt}}` | Resolved media prompt for CLI entries |\n\\| `{{MaxChars}}` | Resolved max output chars for CLI entries |\n\\| `{{ChatType}}` | `\"direct\"` or `\"group\"` |\n\\| `{{GroupSubject}}` | Group subject (best effort) |\n\\| `{{GroupMembers}}` | Group members preview (best effort) |\n\\| `{{SenderName}}` | Sender display name (best effort) |\n\\| `{{SenderE164}}` | Sender phone number (best effort) |\n\\| `{{Provider}}` | Provider hint (whatsapp | telegram | discord | googlechat | slack | signal | imessage | msteams | webchat | …) |\n\n## Cron (Gateway scheduler)\n\nCron is a Gateway-owned scheduler for wakeups and scheduled jobs. See [Cron jobs](/automation/cron-jobs) for the feature overview and CLI examples.\n\n*Next: [Agent Runtime](/concepts/agent)* 🦞",
|
||
"code_samples": [
|
||
{
|
||
"code": "## Partial updates (RPC)\n\nUse `config.patch` to merge a partial update into the existing config without clobbering\nunrelated keys. It applies JSON merge patch semantics:\n\n* objects merge recursively\n* `null` deletes a key\n* arrays replace\n Like `config.apply`, it validates, writes the config, stores a restart sentinel, and schedules\n the Gateway restart (with an optional wake when `sessionKey` is provided).\n\nParams:\n\n* `raw` (string) — JSON5 payload containing just the keys to change\n* `baseHash` (required) — config hash from `config.get`\n* `sessionKey` (optional) — last active session key for the wake-up ping\n* `note` (optional) — note to include in the restart sentinel\n* `restartDelayMs` (optional) — delay before restart (default 2000)\n\nExample:",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "## Minimal config (recommended starting point)",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Build the default image once with:",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "## Self-chat mode (recommended for group control)\n\nTo prevent the bot from responding to WhatsApp @-mentions in groups (only respond to specific text triggers):",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "## Config Includes (`$include`)\n\nSplit your config into multiple files using the `$include` directive. This is useful for:\n\n* Organizing large configs (e.g., per-client agent definitions)\n* Sharing common settings across environments\n* Keeping sensitive configs separate\n\n### Basic usage",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### Merge behavior\n\n* **Single file**: Replaces the object containing `$include`\n* **Array of files**: Deep-merges files in order (later files override earlier ones)\n* **With sibling keys**: Sibling keys are merged after includes (override included values)\n* **Sibling keys + arrays/primitives**: Not supported (included content must be an object)",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### Nested includes\n\nIncluded files can themselves contain `$include` directives (up to 10 levels deep):",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### Path resolution\n\n* **Relative paths**: Resolved relative to the including file\n* **Absolute paths**: Used as-is\n* **Parent directories**: `../` references work as expected",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### Error handling\n\n* **Missing file**: Clear error with resolved path\n* **Parse error**: Shows which included file failed\n* **Circular includes**: Detected and reported with include chain\n\n### Example: Multi-client legal setup",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "## Common options\n\n### Env vars + `.env`\n\nOpenClaw reads env vars from the parent process (shell, launchd/systemd, CI, etc.).\n\nAdditionally, it loads:\n\n* `.env` from the current working directory (if present)\n* a global fallback `.env` from `~/.openclaw/.env` (aka `$OPENCLAW_STATE_DIR/.env`)\n\nNeither `.env` file overrides existing env vars.\n\nYou can also provide inline env vars in config. These are only applied if the\nprocess env is missing the key (same non-overriding rule):",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "See [/environment](/environment) for full precedence and sources.\n\n### `env.shellEnv` (optional)\n\nOpt-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).\nThis effectively sources your shell profile.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Env var equivalent:\n\n* `OPENCLAW_LOAD_SHELL_ENV=1`\n* `OPENCLAW_SHELL_ENV_TIMEOUT_MS=15000`\n\n### Env var substitution in config\n\nYou can reference environment variables directly in any config string value using\n`${VAR_NAME}` syntax. Variables are substituted at config load time, before validation.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "**Rules:**\n\n* Only uppercase env var names are matched: `[A-Z_][A-Z0-9_]*`\n* Missing or empty env vars throw an error at config load\n* Escape with `$${VAR}` to output a literal `${VAR}`\n* Works with `$include` (included files also get substitution)\n\n**Inline substitution:**",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### Auth storage (OAuth + API keys)\n\nOpenClaw stores **per-agent** auth profiles (OAuth + API keys) in:\n\n* `<agentDir>/auth-profiles.json` (default: `~/.openclaw/agents/<agentId>/agent/auth-profiles.json`)\n\nSee also: [/concepts/oauth](/concepts/oauth)\n\nLegacy OAuth imports:\n\n* `~/.openclaw/credentials/oauth.json` (or `$OPENCLAW_STATE_DIR/credentials/oauth.json`)\n\nThe embedded Pi agent maintains a runtime cache at:\n\n* `<agentDir>/auth.json` (managed automatically; don’t edit manually)\n\nLegacy agent dir (pre multi-agent):\n\n* `~/.openclaw/agent/*` (migrated by `openclaw doctor` into `~/.openclaw/agents/<defaultAgentId>/agent/*`)\n\nOverrides:\n\n* OAuth dir (legacy import only): `OPENCLAW_OAUTH_DIR`\n* Agent dir (default agent root override): `OPENCLAW_AGENT_DIR` (preferred), `PI_CODING_AGENT_DIR` (legacy)\n\nOn first use, OpenClaw imports `oauth.json` entries into `auth-profiles.json`.\n\n### `auth`\n\nOptional metadata for auth profiles. This does **not** store secrets; it maps\nprofile IDs to a provider + mode (and optional email) and defines the provider\nrotation order used for failover.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### `agents.list[].identity`\n\nOptional per-agent identity used for defaults and UX. This is written by the macOS onboarding assistant.\n\nIf set, OpenClaw derives defaults (only when you haven’t set them explicitly):\n\n* `messages.ackReaction` from the **active agent**’s `identity.emoji` (falls back to 👀)\n* `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)\n* `identity.avatar` accepts a workspace-relative image path or a remote URL/data URL. Local files must live inside the agent workspace.\n\n`identity.avatar` accepts:\n\n* Workspace-relative path (must stay within the agent workspace)\n* `http(s)` URL\n* `data:` URI",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### `wizard`\n\nMetadata written by CLI wizards (`onboard`, `configure`, `doctor`).",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### `logging`\n\n* Default log file: `/tmp/openclaw/openclaw-YYYY-MM-DD.log`\n* If you want a stable path, set `logging.file` to `/tmp/openclaw/openclaw.log`.\n* Console output can be tuned separately via:\n * `logging.consoleLevel` (defaults to `info`, bumps to `debug` when `--verbose`)\n * `logging.consoleStyle` (`pretty` | `compact` | `json`)\n* Tool summaries can be redacted to avoid leaking secrets:\n * `logging.redactSensitive` (`off` | `tools`, default: `tools`)\n * `logging.redactPatterns` (array of regex strings; overrides defaults)",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### `channels.whatsapp.dmPolicy`\n\nControls how WhatsApp direct chats (DMs) are handled:\n\n* `\"pairing\"` (default): unknown senders get a pairing code; owner must approve\n* `\"allowlist\"`: only allow senders in `channels.whatsapp.allowFrom` (or paired allow store)\n* `\"open\"`: allow all inbound DMs (**requires** `channels.whatsapp.allowFrom` to include `\"*\"`)\n* `\"disabled\"`: ignore all inbound DMs\n\nPairing 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.\n\nPairing approvals:\n\n* `openclaw pairing list whatsapp`\n* `openclaw pairing approve whatsapp <code>`\n\n### `channels.whatsapp.allowFrom`\n\nAllowlist of E.164 phone numbers that may trigger WhatsApp auto-replies (**DMs only**).\nIf empty and `channels.whatsapp.dmPolicy=\"pairing\"`, unknown senders will receive a pairing code.\nFor groups, use `channels.whatsapp.groupPolicy` + `channels.whatsapp.groupAllowFrom`.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### `channels.whatsapp.sendReadReceipts`\n\nControls whether inbound WhatsApp messages are marked as read (blue ticks). Default: `true`.\n\nSelf-chat mode always skips read receipts, even when enabled.\n\nPer-account override: `channels.whatsapp.accounts.<id>.sendReadReceipts`.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### `channels.whatsapp.accounts` (multi-account)\n\nRun multiple WhatsApp accounts in one gateway:",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Notes:\n\n* Outbound commands default to account `default` if present; otherwise the first configured account id (sorted).\n* The legacy single-account Baileys auth dir is migrated by `openclaw doctor` into `whatsapp/default`.\n\n### `channels.telegram.accounts` / `channels.discord.accounts` / `channels.googlechat.accounts` / `channels.slack.accounts` / `channels.mattermost.accounts` / `channels.signal.accounts` / `channels.imessage.accounts`\n\nRun multiple accounts per channel (each account has its own `accountId` and optional `name`):",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Notes:\n\n* `default` is used when `accountId` is omitted (CLI + routing).\n* Env tokens only apply to the **default** account.\n* Base channel settings (group policy, mention gating, etc.) apply to all accounts unless overridden per account.\n* Use `bindings[].match.accountId` to route each account to a different agents.defaults.\n\n### Group chat mention gating (`agents.list[].groupChat` + `messages.groupChat`)\n\nGroup messages default to **require mention** (either metadata mention or regex patterns). Applies to WhatsApp, Telegram, Discord, Google Chat, and iMessage group chats.\n\n**Mention types:**\n\n* **Metadata mentions**: Native platform @-mentions (e.g., WhatsApp tap-to-mention). Ignored in WhatsApp self-chat mode (see `channels.whatsapp.allowFrom`).\n* **Text patterns**: Regex patterns defined in `agents.list[].groupChat.mentionPatterns`. Always checked regardless of self-chat mode.\n* Mention gating is enforced only when mention detection is possible (native mentions or at least one `mentionPattern`).",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "`messages.groupChat.historyLimit` sets the global default for group history context. Channels can override with `channels.<channel>.historyLimit` (or `channels.<channel>.accounts.*.historyLimit` for multi-account). Set `0` to disable history wrapping.\n\n#### DM history limits\n\nDM conversations use session-based history managed by the agent. You can limit the number of user turns retained per DM session:",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Resolution order:\n\n1. Per-DM override: `channels.<provider>.dms[userId].historyLimit`\n2. Provider default: `channels.<provider>.dmHistoryLimit`\n3. No limit (all history retained)\n\nSupported providers: `telegram`, `whatsapp`, `discord`, `slack`, `signal`, `imessage`, `msteams`.\n\nPer-agent override (takes precedence when set, even `[]`):",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "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.\n\nTo respond **only** to specific text triggers (ignoring native @-mentions):",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### Group policy (per channel)\n\nUse `channels.*.groupPolicy` to control whether group/room messages are accepted at all:",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Notes:\n\n* `\"open\"`: groups bypass allowlists; mention-gating still applies.\n* `\"disabled\"`: block all group/room messages.\n* `\"allowlist\"`: only allow groups/rooms that match the configured allowlist.\n* `channels.defaults.groupPolicy` sets the default when a provider’s `groupPolicy` is unset.\n* WhatsApp/Telegram/Signal/iMessage/Microsoft Teams use `groupAllowFrom` (fallback: explicit `allowFrom`).\n* Discord/Slack use channel allowlists (`channels.discord.guilds.*.channels`, `channels.slack.channels`).\n* Group DMs (Discord/Slack) are still controlled by `dm.groupEnabled` + `dm.groupChannels`.\n* Default is `groupPolicy: \"allowlist\"` (unless overridden by `channels.defaults.groupPolicy`); if no allowlist is configured, group messages are blocked.\n\n### Multi-agent routing (`agents.list` + `bindings`)\n\nRun multiple isolated agents (separate workspace, `agentDir`, sessions) inside one Gateway.\nInbound messages are routed to an agent via bindings.\n\n* `agents.list[]`: per-agent overrides.\n * `id`: stable agent id (required).\n * `default`: optional; when multiple are set, the first wins and a warning is logged.\n If none are set, the **first entry** in the list is the default agent.\n * `name`: display name for the agent.\n * `workspace`: default `~/.openclaw/workspace-<agentId>` (for `main`, falls back to `agents.defaults.workspace`).\n * `agentDir`: default `~/.openclaw/agents/<agentId>/agent`.\n * `model`: per-agent default model, overrides `agents.defaults.model` for that agent.\n * string form: `\"provider/model\"`, overrides only `agents.defaults.model.primary`\n * object form: `{ primary, fallbacks }` (fallbacks override `agents.defaults.model.fallbacks`; `[]` disables global fallbacks for that agent)\n * `identity`: per-agent name/theme/emoji (used for mention patterns + ack reactions).\n * `groupChat`: per-agent mention-gating (`mentionPatterns`).\n * `sandbox`: per-agent sandbox config (overrides `agents.defaults.sandbox`).\n * `mode`: `\"off\"` | `\"non-main\"` | `\"all\"`\n * `workspaceAccess`: `\"none\"` | `\"ro\"` | `\"rw\"`\n * `scope`: `\"session\"` | `\"agent\"` | `\"shared\"`\n * `workspaceRoot`: custom sandbox workspace root\n * `docker`: per-agent docker overrides (e.g. `image`, `network`, `env`, `setupCommand`, limits; ignored when `scope: \"shared\"`)\n * `browser`: per-agent sandboxed browser overrides (ignored when `scope: \"shared\"`)\n * `prune`: per-agent sandbox pruning overrides (ignored when `scope: \"shared\"`)\n * `subagents`: per-agent sub-agent defaults.\n * `allowAgents`: allowlist of agent ids for `sessions_spawn` from this agent (`[\"*\"]` = allow any; default: only same agent)\n * `tools`: per-agent tool restrictions (applied before sandbox tool policy).\n * `profile`: base tool profile (applied before allow/deny)\n * `allow`: array of allowed tool names\n * `deny`: array of denied tool names (deny wins)\n* `agents.defaults`: shared agent defaults (model, workspace, sandbox, etc.).\n* `bindings[]`: routes inbound messages to an `agentId`.\n * `match.channel` (required)\n * `match.accountId` (optional; `*` = any account; omitted = default account)\n * `match.peer` (optional; `{ kind: dm|group|channel, id }`)\n * `match.guildId` / `match.teamId` (optional; channel-specific)\n\nDeterministic match order:\n\n1. `match.peer`\n2. `match.guildId`\n3. `match.teamId`\n4. `match.accountId` (exact, no peer/guild/team)\n5. `match.accountId: \"*\"` (channel-wide, no peer/guild/team)\n6. default agent (`agents.list[].default`, else first list entry, else `\"main\"`)\n\nWithin each match tier, the first matching entry in `bindings` wins.\n\n#### Per-agent access profiles (multi-agent)\n\nEach agent can carry its own sandbox + tool policy. Use this to mix access\nlevels in one gateway:\n\n* **Full access** (personal agent)\n* **Read-only** tools + workspace\n* **No filesystem access** (messaging/session tools only)\n\nSee [Multi-Agent Sandbox & Tools](/multi-agent-sandbox-tools) for precedence and\nadditional examples.\n\nFull access (no sandbox):",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Read-only tools + read-only workspace:",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "No filesystem access (messaging/session tools enabled):",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Example: two WhatsApp accounts → two agents:",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### `tools.agentToAgent` (optional)\n\nAgent-to-agent messaging is opt-in:",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### `messages.queue`\n\nControls how inbound messages behave when an agent run is already active.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### `messages.inbound`\n\nDebounce rapid inbound messages from the **same sender** so multiple back-to-back\nmessages become a single agent turn. Debouncing is scoped per channel + conversation\nand uses the most recent message for reply threading/IDs.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Notes:\n\n* Debounce batches **text-only** messages; media/attachments flush immediately.\n* Control commands (e.g. `/queue`, `/new`) bypass debouncing so they stay standalone.\n\n### `commands` (chat command handling)\n\nControls how chat commands are enabled across connectors.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Notes:\n\n* Text commands must be sent as a **standalone** message and use the leading `/` (no plain-text aliases).\n* `commands.text: false` disables parsing chat messages for commands.\n* `commands.native: \"auto\"` (default) turns on native commands for Discord/Telegram and leaves Slack off; unsupported channels stay text-only.\n* 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.\n* `channels.telegram.customCommands` adds extra Telegram bot menu entries. Names are normalized; conflicts with native commands are ignored.\n* `commands.bash: true` enables `! <cmd>` to run host shell commands (`/bash <cmd>` also works as an alias). Requires `tools.elevated.enabled` and allowlisting the sender in `tools.elevated.allowFrom.<channel>`.\n* `commands.bashForegroundMs` controls how long bash waits before backgrounding. While a bash job is running, new `! <cmd>` requests are rejected (one at a time).\n* `commands.config: true` enables `/config` (reads/writes `openclaw.json`).\n* `channels.<provider>.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).\n* `commands.debug: true` enables `/debug` (runtime-only overrides).\n* `commands.restart: true` enables `/restart` and the gateway tool restart action.\n* `commands.useAccessGroups: false` allows commands to bypass access-group allowlists/policies.\n* Slash commands and directives are only honored for **authorized senders**. Authorization is derived from\n channel allowlists/pairing plus `commands.useAccessGroups`.\n\n### `web` (WhatsApp web channel runtime)\n\nWhatsApp runs through the gateway’s web channel (Baileys Web). It starts automatically when a linked session exists.\nSet `web.enabled: false` to keep it off by default.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### `channels.telegram` (bot transport)\n\nOpenClaw 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.\nSet `channels.telegram.enabled: false` to disable automatic startup.\nMulti-account support lives under `channels.telegram.accounts` (see the multi-account section above). Env tokens only apply to the default account.\nSet `channels.telegram.configWrites: false` to block Telegram-initiated config writes (including supergroup ID migrations and `/config set|unset`).",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Draft streaming notes:\n\n* Uses Telegram `sendMessageDraft` (draft bubble, not a real message).\n* Requires **private chat topics** (message\\_thread\\_id in DMs; bot has topics enabled).\n* `/reasoning stream` streams reasoning into the draft, then sends the final answer.\n Retry policy defaults and behavior are documented in [Retry policy](/concepts/retry).\n\n### `channels.discord` (bot transport)\n\nConfigure the Discord bot by setting the bot token and optional gating:\nMulti-account support lives under `channels.discord.accounts` (see the multi-account section above). Env tokens only apply to the default account.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "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:<id>` (DM) or `channel:<id>` (guild channel) when specifying delivery targets for cron/CLI commands; bare numeric IDs are ambiguous and rejected.\nGuild 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.\nBot-authored messages are ignored by default. Enable with `channels.discord.allowBots` (own messages are still filtered to prevent self-reply loops).\nReaction notification modes:\n\n* `off`: no reaction events.\n* `own`: reactions on the bot's own messages (default).\n* `all`: all reactions on all messages.\n* `allowlist`: reactions from `guilds.<id>.users` on all messages (empty list disables).\n 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.\n Retry policy defaults and behavior are documented in [Retry policy](/concepts/retry).\n\n### `channels.googlechat` (Chat API webhook)\n\nGoogle Chat runs over HTTP webhooks with app-level auth (service account).\nMulti-account support lives under `channels.googlechat.accounts` (see the multi-account section above). Env vars only apply to the default account.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Notes:\n\n* Service account JSON can be inline (`serviceAccount`) or file-based (`serviceAccountFile`).\n* Env fallbacks for the default account: `GOOGLE_CHAT_SERVICE_ACCOUNT` or `GOOGLE_CHAT_SERVICE_ACCOUNT_FILE`.\n* `audienceType` + `audience` must match the Chat app’s webhook auth config.\n* Use `spaces/<spaceId>` or `users/<userId|email>` when setting delivery targets.\n\n### `channels.slack` (socket mode)\n\nSlack runs in Socket Mode and requires both a bot token and app token:",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Multi-account support lives under `channels.slack.accounts` (see the multi-account section above). Env tokens only apply to the default account.\n\nOpenClaw starts Slack when the provider is enabled and both tokens are set (via config or `SLACK_BOT_TOKEN` + `SLACK_APP_TOKEN`). Use `user:<id>` (DM) or `channel:<id>` when specifying delivery targets for cron/CLI commands.\nSet `channels.slack.configWrites: false` to block Slack-initiated config writes (including channel ID migrations and `/config set|unset`).\n\nBot-authored messages are ignored by default. Enable with `channels.slack.allowBots` or `channels.slack.channels.<id>.allowBots`.\n\nReaction notification modes:\n\n* `off`: no reaction events.\n* `own`: reactions on the bot's own messages (default).\n* `all`: all reactions on all messages.\n* `allowlist`: reactions from `channels.slack.reactionAllowlist` on all messages (empty list disables).\n\nThread session isolation:\n\n* `channels.slack.thread.historyScope` controls whether thread history is per-thread (`thread`, default) or shared across the channel (`channel`).\n* `channels.slack.thread.inheritParent` controls whether new thread sessions inherit the parent channel transcript (default: false).\n\nSlack action groups (gate `slack` tool actions):\n\n| Action group | Default | Notes |\n| ------------ | ------- | ---------------------- |\n| reactions | enabled | React + list reactions |\n| messages | enabled | Read/send/edit/delete |\n| pins | enabled | Pin/unpin/list |\n| memberInfo | enabled | Member info |\n| emojiList | enabled | Custom emoji list |\n\n### `channels.mattermost` (bot token)\n\nMattermost ships as a plugin and is not bundled with the core install.\nInstall it first: `openclaw plugins install @openclaw/mattermost` (or `./extensions/mattermost` from a git checkout).\n\nMattermost requires a bot token plus the base URL for your server:",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "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`).\n\nChat modes:\n\n* `oncall` (default): respond to channel messages only when @mentioned.\n* `onmessage`: respond to every channel message.\n* `onchar`: respond when a message starts with a trigger prefix (`channels.mattermost.oncharPrefixes`, default `[\">\", \"!\"]`).\n\nAccess control:\n\n* Default DMs: `channels.mattermost.dmPolicy=\"pairing\"` (unknown senders get a pairing code).\n* Public DMs: `channels.mattermost.dmPolicy=\"open\"` plus `channels.mattermost.allowFrom=[\"*\"]`.\n* Groups: `channels.mattermost.groupPolicy=\"allowlist\"` by default (mention-gated). Use `channels.mattermost.groupAllowFrom` to restrict senders.\n\nMulti-account support lives under `channels.mattermost.accounts` (see the multi-account section above). Env vars only apply to the default account.\nUse `channel:<id>` or `user:<id>` (or `@username`) when specifying delivery targets; bare ids are treated as channel ids.\n\n### `channels.signal` (signal-cli)\n\nSignal reactions can emit system events (shared reaction tooling):",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Reaction notification modes:\n\n* `off`: no reaction events.\n* `own`: reactions on the bot's own messages (default).\n* `all`: all reactions on all messages.\n* `allowlist`: reactions from `channels.signal.reactionAllowlist` on all messages (empty list disables).\n\n### `channels.imessage` (imsg CLI)\n\nOpenClaw spawns `imsg rpc` (JSON-RPC over stdio). No daemon or port required.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Multi-account support lives under `channels.imessage.accounts` (see the multi-account section above).\n\nNotes:\n\n* Requires Full Disk Access to the Messages DB.\n* The first send will prompt for Messages automation permission.\n* Prefer `chat_id:<id>` targets. Use `imsg chats --limit 20` to list chats.\n* `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.\n* For remote SSH wrappers, set `channels.imessage.remoteHost` to fetch attachments via SCP when `includeAttachments` is enabled.\n\nExample wrapper:",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### `agents.defaults.workspace`\n\nSets the **single global workspace directory** used by the agent for file operations.\n\nDefault: `~/.openclaw/workspace`.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "If `agents.defaults.sandbox` is enabled, non-main sessions can override this with their\nown per-scope workspaces under `agents.defaults.sandbox.workspaceRoot`.\n\n### `agents.defaults.repoRoot`\n\nOptional repository root to show in the system prompt’s Runtime line. If unset, OpenClaw\ntries to detect a `.git` directory by walking upward from the workspace (and current\nworking directory). The path must exist to be used.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### `agents.defaults.skipBootstrap`\n\nDisables automatic creation of the workspace bootstrap files (`AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, and `BOOTSTRAP.md`).\n\nUse this for pre-seeded deployments where your workspace files come from a repo.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### `agents.defaults.bootstrapMaxChars`\n\nMax characters of each workspace bootstrap file injected into the system prompt\nbefore truncation. Default: `20000`.\n\nWhen a file exceeds this limit, OpenClaw logs a warning and injects a truncated\nhead/tail with a marker.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### `agents.defaults.userTimezone`\n\nSets the user’s timezone for **system prompt context** (not for timestamps in\nmessage envelopes). If unset, OpenClaw uses the host timezone at runtime.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### `agents.defaults.timeFormat`\n\nControls the **time format** shown in the system prompt’s Current Date & Time section.\nDefault: `auto` (OS preference).",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### `messages`\n\nControls inbound/outbound prefixes and optional ack reactions.\nSee [Messages](/concepts/messages) for queueing, sessions, and streaming context.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "`responsePrefix` is applied to **all outbound replies** (tool summaries, block\nstreaming, final replies) across channels unless already present.\n\nOverrides can be configured per channel and per account:\n\n* `channels.<channel>.responsePrefix`\n* `channels.<channel>.accounts.<id>.responsePrefix`\n\nResolution order (most specific wins):\n\n1. `channels.<channel>.accounts.<id>.responsePrefix`\n2. `channels.<channel>.responsePrefix`\n3. `messages.responsePrefix`\n\nSemantics:\n\n* `undefined` falls through to the next level.\n* `\"\"` explicitly disables the prefix and stops the cascade.\n* `\"auto\"` derives `[{identity.name}]` for the routed agent.\n\nOverrides apply to all channels, including extensions, and to every outbound reply kind.\n\nIf `messages.responsePrefix` is unset, no prefix is applied by default. WhatsApp self-chat\nreplies are the exception: they default to `[{identity.name}]` when set, otherwise\n`[openclaw]`, so same-phone conversations stay legible.\nSet it to `\"auto\"` to derive `[{identity.name}]` for the routed agent (when set).\n\n#### Template variables\n\nThe `responsePrefix` string can include template variables that resolve dynamically:\n\n| Variable | Description | Example |\n| ----------------- | ---------------------- | --------------------------- |\n| `{model}` | Short model name | `claude-opus-4-5`, `gpt-4o` |\n| `{modelFull}` | Full model identifier | `anthropic/claude-opus-4-5` |\n| `{provider}` | Provider name | `anthropic`, `openai` |\n| `{thinkingLevel}` | Current thinking level | `high`, `low`, `off` |\n| `{identity.name}` | Agent identity name | (same as `\"auto\"` mode) |\n\nVariables are case-insensitive (`{MODEL}` = `{model}`). `{think}` is an alias for `{thinkingLevel}`.\nUnresolved variables remain as literal text.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Example output: `[claude-opus-4-5 | think:high] Here's my response...`\n\nWhatsApp inbound prefix is configured via `channels.whatsapp.messagePrefix` (deprecated:\n`messages.messagePrefix`). Default stays **unchanged**: `\"[openclaw]\"` when\n`channels.whatsapp.allowFrom` is empty, otherwise `\"\"` (no prefix). When using\n`\"[openclaw]\"`, OpenClaw will instead use `[{identity.name}]` when the routed\nagent has `identity.name` set.\n\n`ackReaction` sends a best-effort emoji reaction to acknowledge inbound messages\non channels that support reactions (Slack/Discord/Telegram/Google Chat). Defaults to the\nactive agent’s `identity.emoji` when set, otherwise `\"👀\"`. Set it to `\"\"` to disable.\n\n`ackReactionScope` controls when reactions fire:\n\n* `group-mentions` (default): only when a group/room requires mentions **and** the bot was mentioned\n* `group-all`: all group/room messages\n* `direct`: direct messages only\n* `all`: all messages\n\n`removeAckAfterReply` removes the bot’s ack reaction after a reply is sent\n(Slack/Discord/Telegram/Google Chat only). Default: `false`.\n\n#### `messages.tts`\n\nEnable text-to-speech for outbound replies. When on, OpenClaw generates audio\nusing ElevenLabs or OpenAI and attaches it to responses. Telegram uses Opus\nvoice notes; other channels send MP3 audio.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Notes:\n\n* `messages.tts.auto` controls auto‑TTS (`off`, `always`, `inbound`, `tagged`).\n* `/tts off|always|inbound|tagged` sets the per‑session auto mode (overrides config).\n* `messages.tts.enabled` is legacy; doctor migrates it to `messages.tts.auto`.\n* `prefsPath` stores local overrides (provider/limit/summarize).\n* `maxTextLength` is a hard cap for TTS input; summaries are truncated to fit.\n* `summaryModel` overrides `agents.defaults.model.primary` for auto-summary.\n * Accepts `provider/model` or an alias from `agents.defaults.models`.\n* `modelOverrides` enables model-driven overrides like `[[tts:...]]` tags (on by default).\n* `/tts limit` and `/tts summary` control per-user summarization settings.\n* `apiKey` values fall back to `ELEVENLABS_API_KEY`/`XI_API_KEY` and `OPENAI_API_KEY`.\n* `elevenlabs.baseUrl` overrides the ElevenLabs API base URL.\n* `elevenlabs.voiceSettings` supports `stability`/`similarityBoost`/`style` (0..1),\n `useSpeakerBoost`, and `speed` (0.5..2.0).\n\n### `talk`\n\nDefaults for Talk mode (macOS/iOS/Android). Voice IDs fall back to `ELEVENLABS_VOICE_ID` or `SAG_VOICE_ID` when unset.\n`apiKey` falls back to `ELEVENLABS_API_KEY` (or the gateway’s shell profile) when unset.\n`voiceAliases` lets Talk directives use friendly names (e.g. `\"voice\":\"Clawd\"`).",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### `agents.defaults`\n\nControls the embedded agent runtime (model/thinking/verbose/timeouts).\n`agents.defaults.models` defines the configured model catalog (and acts as the allowlist for `/model`).\n`agents.defaults.model.primary` sets the default model; `agents.defaults.model.fallbacks` are global failovers.\n`agents.defaults.imageModel` is optional and is **only used if the primary model lacks image input**.\nEach `agents.defaults.models` entry can include:\n\n* `alias` (optional model shortcut, e.g. `/opus`).\n* `params` (optional provider-specific API params passed through to the model request).\n\n`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.\n\nExample:",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Z.AI GLM-4.x models automatically enable thinking mode unless you:\n\n* set `--thinking off`, or\n* define `agents.defaults.models[\"zai/<model>\"].params.thinking` yourself.\n\nOpenClaw also ships a few built-in alias shorthands. Defaults only apply when the model\nis already present in `agents.defaults.models`:\n\n* `opus` -> `anthropic/claude-opus-4-5`\n* `sonnet` -> `anthropic/claude-sonnet-4-5`\n* `gpt` -> `openai/gpt-5.2`\n* `gpt-mini` -> `openai/gpt-5-mini`\n* `gemini` -> `google/gemini-3-pro-preview`\n* `gemini-flash` -> `google/gemini-3-flash-preview`\n\nIf you configure the same alias name (case-insensitive) yourself, your value wins (defaults never override).\n\nExample: Opus 4.5 primary with MiniMax M2.1 fallback (hosted MiniMax):",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "MiniMax auth: set `MINIMAX_API_KEY` (env) or configure `models.providers.minimax`.\n\n#### `agents.defaults.cliBackends` (CLI fallback)\n\nOptional CLI backends for text-only fallback runs (no tool calls). These are useful as a\nbackup path when API providers fail. Image pass-through is supported when you configure\nan `imageArg` that accepts file paths.\n\nNotes:\n\n* CLI backends are **text-first**; tools are always disabled.\n* Sessions are supported when `sessionArg` is set; session ids are persisted per backend.\n* For `claude-cli`, defaults are wired in. Override the command path if PATH is minimal\n (launchd/systemd).\n\nExample:",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "#### `agents.defaults.contextPruning` (tool-result pruning)\n\n`agents.defaults.contextPruning` prunes **old tool results** from the in-memory context right before a request is sent to the LLM.\nIt does **not** modify the session history on disk (`*.jsonl` remains complete).\n\nThis is intended to reduce token usage for chatty agents that accumulate large tool outputs over time.\n\nHigh level:\n\n* Never touches user/assistant messages.\n* Protects the last `keepLastAssistants` assistant messages (no tool results after that point are pruned).\n* Protects the bootstrap prefix (nothing before the first user message is pruned).\n* Modes:\n * `adaptive`: soft-trims oversized tool results (keep head/tail) when the estimated context ratio crosses `softTrimRatio`.\n Then hard-clears the oldest eligible tool results when the estimated context ratio crosses `hardClearRatio` **and**\n there’s enough prunable tool-result bulk (`minPrunableToolChars`).\n * `aggressive`: always replaces eligible tool results before the cutoff with the `hardClear.placeholder` (no ratio checks).\n\nSoft vs hard pruning (what changes in the context sent to the LLM):\n\n* **Soft-trim**: only for *oversized* tool results. Keeps the beginning + end and inserts `...` in the middle.\n * Before: `toolResult(\"…very long output…\")`\n * After: `toolResult(\"HEAD…\\n...\\n…TAIL\\n\\n[Tool result trimmed: …]\")`\n* **Hard-clear**: replaces the entire tool result with the placeholder.\n * Before: `toolResult(\"…very long output…\")`\n * After: `toolResult(\"[Old tool result content cleared]\")`\n\nNotes / current limitations:\n\n* Tool results containing **image blocks are skipped** (never trimmed/cleared) right now.\n* The estimated “context ratio” is based on **characters** (approximate), not exact tokens.\n* If the session doesn’t contain at least `keepLastAssistants` assistant messages yet, pruning is skipped.\n* In `aggressive` mode, `hardClear.enabled` is ignored (eligible tool results are always replaced with `hardClear.placeholder`).\n\nDefault (adaptive):",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "To disable:",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Defaults (when `mode` is `\"adaptive\"` or `\"aggressive\"`):\n\n* `keepLastAssistants`: `3`\n* `softTrimRatio`: `0.3` (adaptive only)\n* `hardClearRatio`: `0.5` (adaptive only)\n* `minPrunableToolChars`: `50000` (adaptive only)\n* `softTrim`: `{ maxChars: 4000, headChars: 1500, tailChars: 1500 }` (adaptive only)\n* `hardClear`: `{ enabled: true, placeholder: \"[Old tool result content cleared]\" }`\n\nExample (aggressive, minimal):",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Example (adaptive tuned):",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "See [/concepts/session-pruning](/concepts/session-pruning) for behavior details.\n\n#### `agents.defaults.compaction` (reserve headroom + memory flush)\n\n`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).\n\n`agents.defaults.compaction.reserveTokensFloor` enforces a minimum `reserveTokens`\nvalue for Pi compaction (default: `20000`). Set it to `0` to disable the floor.\n\n`agents.defaults.compaction.memoryFlush` runs a **silent** agentic turn before\nauto-compaction, instructing the model to store durable memories on disk (e.g.\n`memory/YYYY-MM-DD.md`). It triggers when the session token estimate crosses a\nsoft threshold below the compaction limit.\n\nLegacy defaults:\n\n* `memoryFlush.enabled`: `true`\n* `memoryFlush.softThresholdTokens`: `4000`\n* `memoryFlush.prompt` / `memoryFlush.systemPrompt`: built-in defaults with `NO_REPLY`\n* Note: memory flush is skipped when the session workspace is read-only\n (`agents.defaults.sandbox.workspaceAccess: \"ro\"` or `\"none\"`).\n\nExample (tuned):",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Block streaming:\n\n* `agents.defaults.blockStreamingDefault`: `\"on\"`/`\"off\"` (default off).\n* Channel overrides: `*.blockStreaming` (and per-account variants) to force block streaming on/off.\n Non-Telegram channels require an explicit `*.blockStreaming: true` to enable block replies.\n* `agents.defaults.blockStreamingBreak`: `\"text_end\"` or `\"message_end\"` (default: text\\_end).\n* `agents.defaults.blockStreamingChunk`: soft chunking for streamed blocks. Defaults to\n 800–1200 chars, prefers paragraph breaks (`\\n\\n`), then newlines, then sentences.\n Example:",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "* `agents.defaults.blockStreamingCoalesce`: merge streamed blocks before sending.\n Defaults to `{ idleMs: 1000 }` and inherits `minChars` from `blockStreamingChunk`\n with `maxChars` capped to the channel text limit. Signal/Slack/Discord/Google Chat default\n to `minChars: 1500` unless overridden.\n Channel overrides: `channels.whatsapp.blockStreamingCoalesce`, `channels.telegram.blockStreamingCoalesce`,\n `channels.discord.blockStreamingCoalesce`, `channels.slack.blockStreamingCoalesce`, `channels.mattermost.blockStreamingCoalesce`,\n `channels.signal.blockStreamingCoalesce`, `channels.imessage.blockStreamingCoalesce`, `channels.msteams.blockStreamingCoalesce`,\n `channels.googlechat.blockStreamingCoalesce`\n (and per-account variants).\n* `agents.defaults.humanDelay`: randomized pause between **block replies** after the first.\n Modes: `off` (default), `natural` (800–2500ms), `custom` (use `minMs`/`maxMs`).\n Per-agent override: `agents.list[].humanDelay`.\n Example:",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "See [/concepts/streaming](/concepts/streaming) for behavior + chunking details.\n\nTyping indicators:\n\n* `agents.defaults.typingMode`: `\"never\" | \"instant\" | \"thinking\" | \"message\"`. Defaults to\n `instant` for direct chats / mentions and `message` for unmentioned group chats.\n* `session.typingMode`: per-session override for the mode.\n* `agents.defaults.typingIntervalSeconds`: how often the typing signal is refreshed (default: 6s).\n* `session.typingIntervalSeconds`: per-session override for the refresh interval.\n See [/concepts/typing-indicators](/concepts/typing-indicators) for behavior details.\n\n`agents.defaults.model.primary` should be set as `provider/model` (e.g. `anthropic/claude-opus-4-5`).\nAliases come from `agents.defaults.models.*.alias` (e.g. `Opus`).\nIf you omit the provider, OpenClaw currently assumes `anthropic` as a temporary\ndeprecation fallback.\nZ.AI models are available as `zai/<model>` (e.g. `zai/glm-4.7`) and require\n`ZAI_API_KEY` (or legacy `Z_AI_API_KEY`) in the environment.\n\n`agents.defaults.heartbeat` configures periodic heartbeat runs:\n\n* `every`: duration string (`ms`, `s`, `m`, `h`); default unit minutes. Default:\n `30m`. Set `0m` to disable.\n* `model`: optional override model for heartbeat runs (`provider/model`).\n* `includeReasoning`: when `true`, heartbeats will also deliver the separate `Reasoning:` message when available (same shape as `/reasoning on`). Default: `false`.\n* `session`: optional session key to control which session the heartbeat runs in. Default: `main`.\n* `to`: optional recipient override (channel-specific id, e.g. E.164 for WhatsApp, chat id for Telegram).\n* `target`: optional delivery channel (`last`, `whatsapp`, `telegram`, `discord`, `slack`, `msteams`, `signal`, `imessage`, `none`). Default: `last`.\n* `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.\n* `ackMaxChars`: max chars allowed after `HEARTBEAT_OK` before delivery (default: 300).\n\nPer-agent heartbeats:\n\n* Set `agents.list[].heartbeat` to enable or override heartbeat settings for a specific agent.\n* If any agent entry defines `heartbeat`, **only those agents** run heartbeats; defaults\n become the shared baseline for those agents.\n\nHeartbeats run full agent turns. Shorter intervals burn more tokens; be mindful\nof `every`, keep `HEARTBEAT.md` tiny, and/or choose a cheaper `model`.\n\n`tools.exec` configures background exec defaults:\n\n* `backgroundMs`: time before auto-background (ms, default 10000)\n* `timeoutSec`: auto-kill after this runtime (seconds, default 1800)\n* `cleanupMs`: how long to keep finished sessions in memory (ms, default 1800000)\n* `notifyOnExit`: enqueue a system event + request heartbeat when backgrounded exec exits (default true)\n* `applyPatch.enabled`: enable experimental `apply_patch` (OpenAI/OpenAI Codex only; default false)\n* `applyPatch.allowModels`: optional allowlist of model ids (e.g. `gpt-5.2` or `openai/gpt-5.2`)\n Note: `applyPatch` is only under `tools.exec`.\n\n`tools.web` configures web search + fetch tools:\n\n* `tools.web.search.enabled` (default: true when key is present)\n* `tools.web.search.apiKey` (recommended: set via `openclaw configure --section web`, or use `BRAVE_API_KEY` env var)\n* `tools.web.search.maxResults` (1–10, default 5)\n* `tools.web.search.timeoutSeconds` (default 30)\n* `tools.web.search.cacheTtlMinutes` (default 15)\n* `tools.web.fetch.enabled` (default true)\n* `tools.web.fetch.maxChars` (default 50000)\n* `tools.web.fetch.maxCharsCap` (default 50000; clamps maxChars from config/tool calls)\n* `tools.web.fetch.timeoutSeconds` (default 30)\n* `tools.web.fetch.cacheTtlMinutes` (default 15)\n* `tools.web.fetch.userAgent` (optional override)\n* `tools.web.fetch.readability` (default true; disable to use basic HTML cleanup only)\n* `tools.web.fetch.firecrawl.enabled` (default true when an API key is set)\n* `tools.web.fetch.firecrawl.apiKey` (optional; defaults to `FIRECRAWL_API_KEY`)\n* `tools.web.fetch.firecrawl.baseUrl` (default [https://api.firecrawl.dev](https://api.firecrawl.dev))\n* `tools.web.fetch.firecrawl.onlyMainContent` (default true)\n* `tools.web.fetch.firecrawl.maxAgeMs` (optional)\n* `tools.web.fetch.firecrawl.timeoutSeconds` (optional)\n\n`tools.media` configures inbound media understanding (image/audio/video):\n\n* `tools.media.models`: shared model list (capability-tagged; used after per-cap lists).\n* `tools.media.concurrency`: max concurrent capability runs (default 2).\n* `tools.media.image` / `tools.media.audio` / `tools.media.video`:\n * `enabled`: opt-out switch (default true when models are configured).\n * `prompt`: optional prompt override (image/video append a `maxChars` hint automatically).\n * `maxChars`: max output characters (default 500 for image/video; unset for audio).\n * `maxBytes`: max media size to send (defaults: image 10MB, audio 20MB, video 50MB).\n * `timeoutSeconds`: request timeout (defaults: image 60s, audio 60s, video 120s).\n * `language`: optional audio hint.\n * `attachments`: attachment policy (`mode`, `maxAttachments`, `prefer`).\n * `scope`: optional gating (first match wins) with `match.channel`, `match.chatType`, or `match.keyPrefix`.\n * `models`: ordered list of model entries; failures or oversize media fall back to the next entry.\n* Each `models[]` entry:\n * Provider entry (`type: \"provider\"` or omitted):\n * `provider`: API provider id (`openai`, `anthropic`, `google`/`gemini`, `groq`, etc).\n * `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).\n * `profile` / `preferredProfile`: auth profile selection.\n * CLI entry (`type: \"cli\"`):\n * `command`: executable to run.\n * `args`: templated args (supports `{{MediaPath}}`, `{{Prompt}}`, `{{MaxChars}}`, etc).\n * `capabilities`: optional list (`image`, `audio`, `video`) to gate a shared entry. Defaults when omitted: `openai`/`anthropic`/`minimax` → image, `google` → image+audio+video, `groq` → audio.\n * `prompt`, `maxChars`, `maxBytes`, `timeoutSeconds`, `language` can be overridden per entry.\n\nIf no models are configured (or `enabled: false`), understanding is skipped; the model still receives the original attachments.\n\nProvider 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`).\n\nExample:",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "`agents.defaults.subagents` configures sub-agent defaults:\n\n* `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.\n* `maxConcurrent`: max concurrent sub-agent runs (default 1)\n* `archiveAfterMinutes`: auto-archive sub-agent sessions after N minutes (default 60; set `0` to disable)\n* Per-subagent tool policy: `tools.subagents.tools.allow` / `tools.subagents.tools.deny` (deny wins)\n\n`tools.profile` sets a **base tool allowlist** before `tools.allow`/`tools.deny`:\n\n* `minimal`: `session_status` only\n* `coding`: `group:fs`, `group:runtime`, `group:sessions`, `group:memory`, `image`\n* `messaging`: `group:messaging`, `sessions_list`, `sessions_history`, `sessions_send`, `session_status`\n* `full`: no restriction (same as unset)\n\nPer-agent override: `agents.list[].tools.profile`.\n\nExample (messaging-only by default, allow Slack + Discord tools too):",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Example (coding profile, but deny exec/process everywhere):",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "`tools.byProvider` lets you **further restrict** tools for specific providers (or a single `provider/model`).\nPer-agent override: `agents.list[].tools.byProvider`.\n\nOrder: base profile → provider profile → allow/deny policies.\nProvider keys accept either `provider` (e.g. `google-antigravity`) or `provider/model`\n(e.g. `openai/gpt-5.2`).\n\nExample (keep global coding profile, but minimal tools for Google Antigravity):",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Example (provider/model-specific allowlist):",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "`tools.allow` / `tools.deny` configure a global tool allow/deny policy (deny wins).\nMatching is case-insensitive and supports `*` wildcards (`\"*\"` means all tools).\nThis is applied even when the Docker sandbox is **off**.\n\nExample (disable browser/canvas everywhere):",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Tool groups (shorthands) work in **global** and **per-agent** tool policies:\n\n* `group:runtime`: `exec`, `bash`, `process`\n* `group:fs`: `read`, `write`, `edit`, `apply_patch`\n* `group:sessions`: `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn`, `session_status`\n* `group:memory`: `memory_search`, `memory_get`\n* `group:web`: `web_search`, `web_fetch`\n* `group:ui`: `browser`, `canvas`\n* `group:automation`: `cron`, `gateway`\n* `group:messaging`: `message`\n* `group:nodes`: `nodes`\n* `group:openclaw`: all built-in OpenClaw tools (excludes provider plugins)\n\n`tools.elevated` controls elevated (host) exec access:\n\n* `enabled`: allow elevated mode (default true)\n* `allowFrom`: per-channel allowlists (empty = disabled)\n * `whatsapp`: E.164 numbers\n * `telegram`: chat ids or usernames\n * `discord`: user ids or usernames (falls back to `channels.discord.dm.allowFrom` if omitted)\n * `signal`: E.164 numbers\n * `imessage`: handles/chat ids\n * `webchat`: session ids or usernames\n\nExample:",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Per-agent override (further restrict):",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Notes:\n\n* `tools.elevated` is the global baseline. `agents.list[].tools.elevated` can only further restrict (both must allow).\n* `/elevated on|off|ask|full` stores state per session key; inline directives apply to a single message.\n* Elevated `exec` runs on the host and bypasses sandboxing.\n* Tool policy still applies; if `exec` is denied, elevated cannot be used.\n\n`agents.defaults.maxConcurrent` sets the maximum number of embedded agent runs that can\nexecute in parallel across sessions. Each session is still serialized (one run\nper session key at a time). Default: 1.\n\n### `agents.defaults.sandbox`\n\nOptional **Docker sandboxing** for the embedded agent. Intended for non-main\nsessions so they cannot access your host system.\n\nDetails: [Sandboxing](/gateway/sandboxing)\n\nDefaults (if enabled):\n\n* scope: `\"agent\"` (one container + workspace per agent)\n* Debian bookworm-slim based image\n* agent workspace access: `workspaceAccess: \"none\"` (default)\n * `\"none\"`: use a per-scope sandbox workspace under `~/.openclaw/sandboxes`\n* `\"ro\"`: keep the sandbox workspace at `/workspace`, and mount the agent workspace read-only at `/agent` (disables `write`/`edit`/`apply_patch`)\n * `\"rw\"`: mount the agent workspace read/write at `/workspace`\n* auto-prune: idle > 24h OR age > 7d\n* tool policy: allow only `exec`, `process`, `read`, `write`, `edit`, `apply_patch`, `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn`, `session_status` (deny wins)\n * configure via `tools.sandbox.tools`, override per-agent via `agents.list[].tools.sandbox.tools`\n * 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))\n* optional sandboxed browser (Chromium + CDP, noVNC observer)\n* hardening knobs: `network`, `user`, `pidsLimit`, `memory`, `cpus`, `ulimits`, `seccompProfile`, `apparmorProfile`\n\nWarning: `scope: \"shared\"` means a shared container and shared workspace. No\ncross-session isolation. Use `scope: \"session\"` for per-session isolation.\n\nLegacy: `perSession` is still supported (`true` → `scope: \"session\"`,\n`false` → `scope: \"shared\"`).\n\n`setupCommand` runs **once** after the container is created (inside the container via `sh -lc`).\nFor package installs, ensure network egress, a writable root FS, and a root user.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Build the default sandbox image once with:",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Note: sandbox containers default to `network: \"none\"`; set `agents.defaults.sandbox.docker.network`\nto `\"bridge\"` (or your custom network) if the agent needs outbound access.\n\nNote: inbound attachments are staged into the active workspace at `media/inbound/*`. With `workspaceAccess: \"rw\"`, that means files are written into the agent workspace.\n\nNote: `docker.binds` mounts additional host directories; global and per-agent binds are merged.\n\nBuild the optional browser image with:",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "When `agents.defaults.sandbox.browser.enabled=true`, the browser tool uses a sandboxed\nChromium instance (CDP). If noVNC is enabled (default when headless=false),\nthe noVNC URL is injected into the system prompt so the agent can reference it.\nThis does not require `browser.enabled` in the main config; the sandbox control\nURL is injected per session.\n\n`agents.defaults.sandbox.browser.allowHostControl` (default: false) allows\nsandboxed sessions to explicitly target the **host** browser control server\nvia the browser tool (`target: \"host\"`). Leave this off if you want strict\nsandbox isolation.\n\nAllowlists for remote control:\n\n* `allowedControlUrls`: exact control URLs permitted for `target: \"custom\"`.\n* `allowedControlHosts`: hostnames permitted (hostname only, no port).\n* `allowedControlPorts`: ports permitted (defaults: http=80, https=443).\n Defaults: all allowlists are unset (no restriction). `allowHostControl` defaults to false.\n\n### `models` (custom providers + base URLs)\n\nOpenClaw uses the **pi-coding-agent** model catalog. You can add custom providers\n(LiteLLM, local OpenAI-compatible servers, Anthropic proxies, etc.) by writing\n`~/.openclaw/agents/<agentId>/agent/models.json` or by defining the same schema inside your\nOpenClaw config under `models.providers`.\nProvider-by-provider overview + examples: [/concepts/model-providers](/concepts/model-providers).\n\nWhen `models.providers` is present, OpenClaw writes/merges a `models.json` into\n`~/.openclaw/agents/<agentId>/agent/` on startup:\n\n* default behavior: **merge** (keeps existing providers, overrides on name)\n* set `models.mode: \"replace\"` to overwrite the file contents\n\nSelect the model via `agents.defaults.model.primary` (provider/model).",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### OpenCode Zen (multi-model proxy)\n\nOpenCode Zen is a multi-model gateway with per-model endpoints. OpenClaw uses\nthe built-in `opencode` provider from pi-ai; set `OPENCODE_API_KEY` (or\n`OPENCODE_ZEN_API_KEY`) from [https://opencode.ai/auth](https://opencode.ai/auth).\n\nNotes:\n\n* Model refs use `opencode/<modelId>` (example: `opencode/claude-opus-4-5`).\n* If you enable an allowlist via `agents.defaults.models`, add each model you plan to use.\n* Shortcut: `openclaw onboard --auth-choice opencode-zen`.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### Z.AI (GLM-4.7) — provider alias support\n\nZ.AI models are available via the built-in `zai` provider. Set `ZAI_API_KEY`\nin your environment and reference the model by provider/model.\n\nShortcut: `openclaw onboard --auth-choice zai-api-key`.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Notes:\n\n* `z.ai/*` and `z-ai/*` are accepted aliases and normalize to `zai/*`.\n* If `ZAI_API_KEY` is missing, requests to `zai/*` will fail with an auth error at runtime.\n* Example error: `No API key found for provider \"zai\".`\n* Z.AI’s general API endpoint is `https://api.z.ai/api/paas/v4`. GLM coding\n requests use the dedicated Coding endpoint `https://api.z.ai/api/coding/paas/v4`.\n The built-in `zai` provider uses the Coding endpoint. If you need the general\n endpoint, define a custom provider in `models.providers` with the base URL\n override (see the custom providers section above).\n* Use a fake placeholder in docs/configs; never commit real API keys.\n\n### Moonshot AI (Kimi)\n\nUse Moonshot's OpenAI-compatible endpoint:",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Notes:\n\n* Set `MOONSHOT_API_KEY` in the environment or use `openclaw onboard --auth-choice moonshot-api-key`.\n* Model ref: `moonshot/kimi-k2.5`.\n* For the China endpoint, either:\n * Run `openclaw onboard --auth-choice moonshot-api-key-cn` (wizard will set `https://api.moonshot.cn/v1`), or\n * Manually set `baseUrl: \"https://api.moonshot.cn/v1\"` in `models.providers.moonshot`.\n\n### Kimi Coding\n\nUse Moonshot AI's Kimi Coding endpoint (Anthropic-compatible, built-in provider):",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Notes:\n\n* Set `KIMI_API_KEY` in the environment or use `openclaw onboard --auth-choice kimi-code-api-key`.\n* Model ref: `kimi-coding/k2p5`.\n\n### Synthetic (Anthropic-compatible)\n\nUse Synthetic's Anthropic-compatible endpoint:",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Notes:\n\n* Set `SYNTHETIC_API_KEY` or use `openclaw onboard --auth-choice synthetic-api-key`.\n* Model ref: `synthetic/hf:MiniMaxAI/MiniMax-M2.1`.\n* Base URL should omit `/v1` because the Anthropic client appends it.\n\n### Local models (LM Studio) — recommended setup\n\nSee [/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.\n\n### MiniMax M2.1\n\nUse MiniMax M2.1 directly without LM Studio:",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Notes:\n\n* Set `MINIMAX_API_KEY` environment variable or use `openclaw onboard --auth-choice minimax-api`.\n* Available model: `MiniMax-M2.1` (default).\n* Update pricing in `models.json` if you need exact cost tracking.\n\n### Cerebras (GLM 4.6 / 4.7)\n\nUse Cerebras via their OpenAI-compatible endpoint:",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Notes:\n\n* Use `cerebras/zai-glm-4.7` for Cerebras; use `zai/glm-4.7` for Z.AI direct.\n* Set `CEREBRAS_API_KEY` in the environment or config.\n\nNotes:\n\n* Supported APIs: `openai-completions`, `openai-responses`, `anthropic-messages`,\n `google-generative-ai`\n* Use `authHeader: true` + `headers` for custom auth needs.\n* Override the agent config root with `OPENCLAW_AGENT_DIR` (or `PI_CODING_AGENT_DIR`)\n if you want `models.json` stored elsewhere (default: `~/.openclaw/agents/main/agent`).\n\n### `session`\n\nControls session scoping, reset policy, reset triggers, and where the session store is written.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Fields:\n\n* `mainKey`: direct-chat bucket key (default: `\"main\"`). Useful when you want to “rename” the primary DM thread without changing `agentId`.\n * 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.\n* `dmScope`: how DM sessions are grouped (default: `\"main\"`).\n * `main`: all DMs share the main session for continuity.\n * `per-peer`: isolate DMs by sender id across channels.\n * `per-channel-peer`: isolate DMs per channel + sender (recommended for multi-user inboxes).\n * `per-account-channel-peer`: isolate DMs per account + channel + sender (recommended for multi-account inboxes).\n * 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\"`).\n* `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`.\n * Example: `alice: [\"telegram:123456789\", \"discord:987654321012345678\"]`.\n* `reset`: primary reset policy. Defaults to daily resets at 4:00 AM local time on the gateway host.\n * `mode`: `daily` or `idle` (default: `daily` when `reset` is present).\n * `atHour`: local hour (0-23) for the daily reset boundary.\n * `idleMinutes`: sliding idle window in minutes. When daily + idle are both configured, whichever expires first wins.\n* `resetByType`: per-session overrides for `dm`, `group`, and `thread`.\n * If you only set legacy `session.idleMinutes` without any `reset`/`resetByType`, OpenClaw stays in idle-only mode for backward compatibility.\n* `heartbeatIdleMinutes`: optional idle override for heartbeat checks (daily reset still applies when enabled).\n* `agentToAgent.maxPingPongTurns`: max reply-back turns between requester/target (0–5, default 5).\n* `sendPolicy.default`: `allow` or `deny` fallback when no rule matches.\n* `sendPolicy.rules[]`: match by `channel`, `chatType` (`direct|group|room`), or `keyPrefix` (e.g. `cron:`). First deny wins; otherwise allow.\n\n### `skills` (skills config)\n\nControls bundled allowlist, install preferences, extra skill folders, and per-skill\noverrides. Applies to **bundled** skills and `~/.openclaw/skills` (workspace skills\nstill win on name conflicts).\n\nFields:\n\n* `allowBundled`: optional allowlist for **bundled** skills only. If set, only those\n bundled skills are eligible (managed/workspace skills unaffected).\n* `load.extraDirs`: additional skill directories to scan (lowest precedence).\n* `install.preferBrew`: prefer brew installers when available (default: true).\n* `install.nodeManager`: node installer preference (`npm` | `pnpm` | `yarn`, default: npm).\n* `entries.<skillKey>`: per-skill config overrides.\n\nPer-skill fields:\n\n* `enabled`: set `false` to disable a skill even if it’s bundled/installed.\n* `env`: environment variables injected for the agent run (only if not already set).\n* `apiKey`: optional convenience for skills that declare a primary env var (e.g. `nano-banana-pro` → `GEMINI_API_KEY`).\n\nExample:",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### `plugins` (extensions)\n\nControls plugin discovery, allow/deny, and per-plugin config. Plugins are loaded\nfrom `~/.openclaw/extensions`, `<workspace>/.openclaw/extensions`, plus any\n`plugins.load.paths` entries. **Config changes require a gateway restart.**\nSee [/plugin](/plugin) for full usage.\n\nFields:\n\n* `enabled`: master toggle for plugin loading (default: true).\n* `allow`: optional allowlist of plugin ids; when set, only listed plugins load.\n* `deny`: optional denylist of plugin ids (deny wins).\n* `load.paths`: extra plugin files or directories to load (absolute or `~`).\n* `entries.<pluginId>`: per-plugin overrides.\n * `enabled`: set `false` to disable.\n * `config`: plugin-specific config object (validated by the plugin if provided).\n\nExample:",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### `browser` (openclaw-managed browser)\n\nOpenClaw can start a **dedicated, isolated** Chrome/Brave/Edge/Chromium instance for openclaw and expose a small loopback control service.\nProfiles can point at a **remote** Chromium-based browser via `profiles.<name>.cdpUrl`. Remote\nprofiles are attach-only (start/stop/reset are disabled).\n\n`browser.cdpUrl` remains for legacy single-profile configs and as the base\nscheme/host for profiles that only set `cdpPort`.\n\nDefaults:\n\n* enabled: `true`\n* evaluateEnabled: `true` (set `false` to disable `act:evaluate` and `wait --fn`)\n* control service: loopback only (port derived from `gateway.port`, default `18791`)\n* CDP URL: `http://127.0.0.1:18792` (control service + 1, legacy single-profile)\n* profile color: `#FF4500` (lobster-orange)\n* Note: the control server is started by the running gateway (OpenClaw\\.app menubar, or `openclaw gateway`).\n* Auto-detect order: default browser if Chromium-based; otherwise Chrome → Brave → Edge → Chromium → Chrome Canary.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### `ui` (Appearance)\n\nOptional accent color used by the native apps for UI chrome (e.g. Talk Mode bubble tint).\n\nIf unset, clients fall back to a muted light-blue.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### `gateway` (Gateway server mode + bind)\n\nUse `gateway.mode` to explicitly declare whether this machine should run the Gateway.\n\nDefaults:\n\n* mode: **unset** (treated as “do not auto-start”)\n* bind: `loopback`\n* port: `18789` (single port for WS + HTTP)",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Control UI base path:\n\n* `gateway.controlUi.basePath` sets the URL prefix where the Control UI is served.\n* Examples: `\"/ui\"`, `\"/openclaw\"`, `\"/apps/openclaw\"`.\n* Default: root (`/`) (unchanged).\n* `gateway.controlUi.root` sets the filesystem root for Control UI assets (default: `dist/control-ui`).\n* `gateway.controlUi.allowInsecureAuth` allows token-only auth for the Control UI when\n device identity is omitted (typically over HTTP). Default: `false`. Prefer HTTPS\n (Tailscale Serve) or `127.0.0.1`.\n* `gateway.controlUi.dangerouslyDisableDeviceAuth` disables device identity checks for the\n Control UI (token/password only). Default: `false`. Break-glass only.\n\nRelated docs:\n\n* [Control UI](/web/control-ui)\n* [Web overview](/web)\n* [Tailscale](/gateway/tailscale)\n* [Remote access](/gateway/remote)\n\nTrusted proxies:\n\n* `gateway.trustedProxies`: list of reverse proxy IPs that terminate TLS in front of the Gateway.\n* 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.\n* Only list proxies you fully control, and ensure they **overwrite** incoming `x-forwarded-for`.\n\nNotes:\n\n* `openclaw gateway` refuses to start unless `gateway.mode` is set to `local` (or you pass the override flag).\n* `gateway.port` controls the single multiplexed port used for WebSocket + HTTP (control UI, hooks, A2UI).\n* OpenAI Chat Completions endpoint: **disabled by default**; enable with `gateway.http.endpoints.chatCompletions.enabled: true`.\n* Precedence: `--port` > `OPENCLAW_GATEWAY_PORT` > `gateway.port` > default `18789`.\n* Gateway auth is required by default (token/password or Tailscale Serve identity). Non-loopback binds require a shared token/password.\n* The onboarding wizard generates a gateway token by default (even on loopback).\n* `gateway.remote.token` is **only** for remote CLI calls; it does not enable local gateway auth. `gateway.token` is ignored.\n\nAuth and Tailscale:\n\n* `gateway.auth.mode` sets the handshake requirements (`token` or `password`). When unset, token auth is assumed.\n* `gateway.auth.token` stores the shared token for token auth (used by the CLI on the same machine).\n* When `gateway.auth.mode` is set, only that method is accepted (plus optional Tailscale headers).\n* `gateway.auth.password` can be set here, or via `OPENCLAW_GATEWAY_PASSWORD` (recommended).\n* `gateway.auth.allowTailscale` allows Tailscale Serve identity headers\n (`tailscale-user-login`) to satisfy auth when the request arrives on loopback\n with `x-forwarded-for`, `x-forwarded-proto`, and `x-forwarded-host`. OpenClaw\n verifies the identity by resolving the `x-forwarded-for` address via\n `tailscale whois` before accepting it. When `true`, Serve requests do not need\n a token/password; set `false` to require explicit credentials. Defaults to\n `true` when `tailscale.mode = \"serve\"` and auth mode is not `password`.\n* `gateway.tailscale.mode: \"serve\"` uses Tailscale Serve (tailnet only, loopback bind).\n* `gateway.tailscale.mode: \"funnel\"` exposes the dashboard publicly; requires auth.\n* `gateway.tailscale.resetOnExit` resets Serve/Funnel config on shutdown.\n\nRemote client defaults (CLI):\n\n* `gateway.remote.url` sets the default Gateway WebSocket URL for CLI calls when `gateway.mode = \"remote\"`.\n* `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`.\n* `gateway.remote.token` supplies the token for remote calls (leave unset for no auth).\n* `gateway.remote.password` supplies the password for remote calls (leave unset for no auth).\n\nmacOS app behavior:\n\n* OpenClaw\\.app watches `~/.openclaw/openclaw.json` and switches modes live when `gateway.mode` or `gateway.remote.url` changes.\n* If `gateway.mode` is unset but `gateway.remote.url` is set, the macOS app treats it as remote mode.\n* 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.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Direct transport example (macOS app):",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### `gateway.reload` (Config hot reload)\n\nThe Gateway watches `~/.openclaw/openclaw.json` (or `OPENCLAW_CONFIG_PATH`) and applies changes automatically.\n\nModes:\n\n* `hybrid` (default): hot-apply safe changes; restart the Gateway for critical changes.\n* `hot`: only apply hot-safe changes; log when a restart is required.\n* `restart`: restart the Gateway on any config change.\n* `off`: disable hot reload.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "#### Hot reload matrix (files + impact)\n\nFiles watched:\n\n* `~/.openclaw/openclaw.json` (or `OPENCLAW_CONFIG_PATH`)\n\nHot-applied (no full gateway restart):\n\n* `hooks` (webhook auth/path/mappings) + `hooks.gmail` (Gmail watcher restarted)\n* `browser` (browser control server restart)\n* `cron` (cron service restart + concurrency update)\n* `agents.defaults.heartbeat` (heartbeat runner restart)\n* `web` (WhatsApp web channel restart)\n* `telegram`, `discord`, `signal`, `imessage` (channel restarts)\n* `agent`, `models`, `routing`, `messages`, `session`, `whatsapp`, `logging`, `skills`, `ui`, `talk`, `identity`, `wizard` (dynamic reads)\n\nRequires full Gateway restart:\n\n* `gateway` (port/bind/auth/control UI/tailscale)\n* `bridge` (legacy)\n* `discovery`\n* `canvasHost`\n* `plugins`\n* Any unknown/unsupported config path (defaults to restart for safety)\n\n### Multi-instance isolation\n\nTo run multiple gateways on one host (for redundancy or a rescue bot), isolate per-instance state + config and use unique ports:\n\n* `OPENCLAW_CONFIG_PATH` (per-instance config)\n* `OPENCLAW_STATE_DIR` (sessions/creds)\n* `agents.defaults.workspace` (memories)\n* `gateway.port` (unique per instance)\n\nConvenience flags (CLI):\n\n* `openclaw --dev …` → uses `~/.openclaw-dev` + shifts ports from base `19001`\n* `openclaw --profile <name> …` → uses `~/.openclaw-<name>` (port via config/env/flags)\n\nSee [Gateway runbook](/gateway) for the derived port mapping (gateway/browser/canvas).\nSee [Multiple gateways](/gateway/multiple-gateways) for browser/CDP port isolation details.\n\nExample:",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### `hooks` (Gateway webhooks)\n\nEnable a simple HTTP webhook endpoint on the Gateway HTTP server.\n\nDefaults:\n\n* enabled: `false`\n* path: `/hooks`\n* maxBodyBytes: `262144` (256 KB)",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Requests must include the hook token:\n\n* `Authorization: Bearer <token>` **or**\n* `x-openclaw-token: <token>` **or**\n* `?token=<token>`\n\nEndpoints:\n\n* `POST /hooks/wake` → `{ text, mode?: \"now\"|\"next-heartbeat\" }`\n* `POST /hooks/agent` → `{ message, name?, sessionKey?, wakeMode?, deliver?, channel?, to?, model?, thinking?, timeoutSeconds? }`\n* `POST /hooks/<name>` → resolved via `hooks.mappings`\n\n`/hooks/agent` always posts a summary into the main session (and can optionally trigger an immediate heartbeat via `wakeMode: \"now\"`).\n\nMapping notes:\n\n* `match.path` matches the sub-path after `/hooks` (e.g. `/hooks/gmail` → `gmail`).\n* `match.source` matches a payload field (e.g. `{ source: \"gmail\" }`) so you can use a generic `/hooks/ingest` path.\n* Templates like `{{messages[0].subject}}` read from the payload.\n* `transform` can point to a JS/TS module that returns a hook action.\n* `deliver: true` sends the final reply to a channel; `channel` defaults to `last` (falls back to WhatsApp).\n* If there is no prior delivery route, set `channel` + `to` explicitly (required for Telegram/Discord/Google Chat/Slack/Signal/iMessage/MS Teams).\n* `model` overrides the LLM for this hook run (`provider/model` or alias; must be allowed if `agents.defaults.models` is set).\n\nGmail helper config (used by `openclaw webhooks gmail setup` / `run`):",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Model override for Gmail hooks:\n\n* `hooks.gmail.model` specifies a model to use for Gmail hook processing (defaults to session primary).\n* Accepts `provider/model` refs or aliases from `agents.defaults.models`.\n* Falls back to `agents.defaults.model.fallbacks`, then `agents.defaults.model.primary`, on auth/rate-limit/timeouts.\n* If `agents.defaults.models` is set, include the hooks model in the allowlist.\n* At startup, warns if the configured model is not in the model catalog or allowlist.\n* `hooks.gmail.thinking` sets the default thinking level for Gmail hooks and is overridden by per-hook `thinking`.\n\nGateway auto-start:\n\n* If `hooks.enabled=true` and `hooks.gmail.account` is set, the Gateway starts\n `gog gmail watch serve` on boot and auto-renews the watch.\n* Set `OPENCLAW_SKIP_GMAIL_WATCHER=1` to disable the auto-start (for manual runs).\n* Avoid running a separate `gog gmail watch serve` alongside the Gateway; it will\n fail with `listen tcp 127.0.0.1:8788: bind: address already in use`.\n\nNote: when `tailscale.mode` is on, OpenClaw defaults `serve.path` to `/` so\nTailscale can proxy `/gmail-pubsub` correctly (it strips the set-path prefix).\nIf you need the backend to receive the prefixed path, set\n`hooks.gmail.tailscale.target` to a full URL (and align `serve.path`).\n\n### `canvasHost` (LAN/tailnet Canvas file server + live reload)\n\nThe Gateway serves a directory of HTML/CSS/JS over HTTP so iOS/Android nodes can simply `canvas.navigate` to it.\n\nDefault root: `~/.openclaw/workspace/canvas`\\\nDefault port: `18793` (chosen to avoid the openclaw browser CDP port `18792`)\\\nThe server listens on the **gateway bind host** (LAN or Tailnet) so nodes can reach it.\n\nThe server:\n\n* serves files under `canvasHost.root`\n* injects a tiny live-reload client into served HTML\n* watches the directory and broadcasts reloads over a WebSocket endpoint at `/__openclaw__/ws`\n* auto-creates a starter `index.html` when the directory is empty (so you see something immediately)\n* also serves A2UI at `/__openclaw__/a2ui/` and is advertised to nodes as `canvasHostUrl`\n (always used by nodes for Canvas/A2UI)\n\nDisable live reload (and file watching) if the directory is large or you hit `EMFILE`:\n\n* config: `canvasHost: { liveReload: false }`",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "Changes to `canvasHost.*` require a gateway restart (config reload will restart).\n\nDisable with:\n\n* config: `canvasHost: { enabled: false }`\n* env: `OPENCLAW_SKIP_CANVAS_HOST=1`\n\n### `bridge` (legacy TCP bridge, removed)\n\nCurrent builds no longer include the TCP bridge listener; `bridge.*` config keys are ignored.\nNodes connect over the Gateway WebSocket. This section is kept for historical reference.\n\nLegacy behavior:\n\n* The Gateway could expose a simple TCP bridge for nodes (iOS/Android), typically on port `18790`.\n\nDefaults:\n\n* enabled: `true`\n* port: `18790`\n* bind: `lan` (binds to `0.0.0.0`)\n\nBind modes:\n\n* `lan`: `0.0.0.0` (reachable on any interface, including LAN/Wi‑Fi and Tailscale)\n* `tailnet`: bind only to the machine’s Tailscale IP (recommended for Vienna ⇄ London)\n* `loopback`: `127.0.0.1` (local only)\n* `auto`: prefer tailnet IP if present, else `lan`\n\nTLS:\n\n* `bridge.tls.enabled`: enable TLS for bridge connections (TLS-only when enabled).\n* `bridge.tls.autoGenerate`: generate a self-signed cert when no cert/key are present (default: true).\n* `bridge.tls.certPath` / `bridge.tls.keyPath`: PEM paths for the bridge certificate + private key.\n* `bridge.tls.caPath`: optional PEM CA bundle (custom roots or future mTLS).\n\nWhen TLS is enabled, the Gateway advertises `bridgeTls=1` and `bridgeTlsSha256` in discovery TXT\nrecords so nodes can pin the certificate. Manual connections use trust-on-first-use if no\nfingerprint is stored yet.\nAuto-generated certs require `openssl` on PATH; if generation fails, the bridge will not start.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### `discovery.mdns` (Bonjour / mDNS broadcast mode)\n\nControls LAN mDNS discovery broadcasts (`_openclaw-gw._tcp`).\n\n* `minimal` (default): omit `cliPath` + `sshPort` from TXT records\n* `full`: include `cliPath` + `sshPort` in TXT records\n* `off`: disable mDNS broadcasts entirely\n* Hostname: defaults to `openclaw` (advertises `openclaw.local`). Override with `OPENCLAW_MDNS_HOSTNAME`.",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "### `discovery.wideArea` (Wide-Area Bonjour / unicast DNS‑SD)\n\nWhen enabled, the Gateway writes a unicast DNS-SD zone for `_openclaw-gw._tcp` under `~/.openclaw/dns/` using the configured discovery domain (example: `openclaw.internal.`).\n\nTo make iOS/Android discover across networks (Vienna ⇄ London), pair this with:\n\n* a DNS server on the gateway host serving your chosen domain (CoreDNS is recommended)\n* Tailscale **split DNS** so clients resolve that domain via the gateway DNS server\n\nOne-time setup helper (gateway host):",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "",
|
||
"language": "unknown"
|
||
},
|
||
{
|
||
"code": "## Template variables\n\nTemplate placeholders are expanded in `tools.media.*.models[].args` and `tools.media.models[].args` (and any future templated argument fields).\n\n\\| Variable | Description |\n\\| ------------------ | ------------------------------------------------------------------------------- | -------- | ------- | ---------- | ----- | ------ | -------- | ------- | ------- | --- |\n\\| `{{Body}}` | Full inbound message body |\n\\| `{{RawBody}}` | Raw inbound message body (no history/sender wrappers; best for command parsing) |\n\\| `{{BodyStripped}}` | Body with group mentions stripped (best default for agents) |\n\\| `{{From}}` | Sender identifier (E.164 for WhatsApp; may differ per channel) |\n\\| `{{To}}` | Destination identifier |\n\\| `{{MessageSid}}` | Channel message id (when available) |\n\\| `{{SessionId}}` | Current session UUID |\n\\| `{{IsNewSession}}` | `\"true\"` when a new session was created |\n\\| `{{MediaUrl}}` | Inbound media pseudo-URL (if present) |\n\\| `{{MediaPath}}` | Local media path (if downloaded) |\n\\| `{{MediaType}}` | Media type (image/audio/document/…) |\n\\| `{{Transcript}}` | Audio transcript (when enabled) |\n\\| `{{Prompt}}` | Resolved media prompt for CLI entries |\n\\| `{{MaxChars}}` | Resolved max output chars for CLI entries |\n\\| `{{ChatType}}` | `\"direct\"` or `\"group\"` |\n\\| `{{GroupSubject}}` | Group subject (best effort) |\n\\| `{{GroupMembers}}` | Group members preview (best effort) |\n\\| `{{SenderName}}` | Sender display name (best effort) |\n\\| `{{SenderE164}}` | Sender phone number (best effort) |\n\\| `{{Provider}}` | Provider hint (whatsapp | telegram | discord | googlechat | slack | signal | imessage | msteams | webchat | …) |\n\n## Cron (Gateway scheduler)\n\nCron is a Gateway-owned scheduler for wakeups and scheduled jobs. See [Cron jobs](/automation/cron-jobs) for the feature overview and CLI examples.",
|
||
"language": "unknown"
|
||
}
|
||
],
|
||
"headings": [
|
||
{
|
||
"level": "h2",
|
||
"text": "Strict config validation",
|
||
"id": "strict-config-validation"
|
||
},
|
||
{
|
||
"level": "h2",
|
||
"text": "Schema + UI hints",
|
||
"id": "schema-+-ui-hints"
|
||
},
|
||
{
|
||
"level": "h2",
|
||
"text": "Apply + restart (RPC)",
|
||
"id": "apply-+-restart-(rpc)"
|
||
},
|
||
{
|
||
"level": "h2",
|
||
"text": "Partial updates (RPC)",
|
||
"id": "partial-updates-(rpc)"
|
||
},
|
||
{
|
||
"level": "h2",
|
||
"text": "Minimal config (recommended starting point)",
|
||
"id": "minimal-config-(recommended-starting-point)"
|
||
},
|
||
{
|
||
"level": "h2",
|
||
"text": "Self-chat mode (recommended for group control)",
|
||
"id": "self-chat-mode-(recommended-for-group-control)"
|
||
},
|
||
{
|
||
"level": "h2",
|
||
"text": "Config Includes (`$include`)",
|
||
"id": "config-includes-(`$include`)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "Basic usage",
|
||
"id": "basic-usage"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "Merge behavior",
|
||
"id": "merge-behavior"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "Nested includes",
|
||
"id": "nested-includes"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "Path resolution",
|
||
"id": "path-resolution"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "Error handling",
|
||
"id": "error-handling"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "Example: Multi-client legal setup",
|
||
"id": "example:-multi-client-legal-setup"
|
||
},
|
||
{
|
||
"level": "h2",
|
||
"text": "Common options",
|
||
"id": "common-options"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "Env vars + `.env`",
|
||
"id": "env-vars-+-`.env`"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`env.shellEnv` (optional)",
|
||
"id": "`env.shellenv`-(optional)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "Env var substitution in config",
|
||
"id": "env-var-substitution-in-config"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "Auth storage (OAuth + API keys)",
|
||
"id": "auth-storage-(oauth-+-api-keys)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`auth`",
|
||
"id": "`auth`"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`agents.list[].identity`",
|
||
"id": "`agents.list[].identity`"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`wizard`",
|
||
"id": "`wizard`"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`logging`",
|
||
"id": "`logging`"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`channels.whatsapp.dmPolicy`",
|
||
"id": "`channels.whatsapp.dmpolicy`"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`channels.whatsapp.allowFrom`",
|
||
"id": "`channels.whatsapp.allowfrom`"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`channels.whatsapp.sendReadReceipts`",
|
||
"id": "`channels.whatsapp.sendreadreceipts`"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`channels.whatsapp.accounts` (multi-account)",
|
||
"id": "`channels.whatsapp.accounts`-(multi-account)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`channels.telegram.accounts` / `channels.discord.accounts` / `channels.googlechat.accounts` / `channels.slack.accounts` / `channels.mattermost.accounts` / `channels.signal.accounts` / `channels.imessage.accounts`",
|
||
"id": "`channels.telegram.accounts`-/-`channels.discord.accounts`-/-`channels.googlechat.accounts`-/-`channels.slack.accounts`-/-`channels.mattermost.accounts`-/-`channels.signal.accounts`-/-`channels.imessage.accounts`"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "Group chat mention gating (`agents.list[].groupChat` + `messages.groupChat`)",
|
||
"id": "group-chat-mention-gating-(`agents.list[].groupchat`-+-`messages.groupchat`)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "Group policy (per channel)",
|
||
"id": "group-policy-(per-channel)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "Multi-agent routing (`agents.list` + `bindings`)",
|
||
"id": "multi-agent-routing-(`agents.list`-+-`bindings`)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`tools.agentToAgent` (optional)",
|
||
"id": "`tools.agenttoagent`-(optional)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`messages.queue`",
|
||
"id": "`messages.queue`"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`messages.inbound`",
|
||
"id": "`messages.inbound`"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`commands` (chat command handling)",
|
||
"id": "`commands`-(chat-command-handling)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`web` (WhatsApp web channel runtime)",
|
||
"id": "`web`-(whatsapp-web-channel-runtime)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`channels.telegram` (bot transport)",
|
||
"id": "`channels.telegram`-(bot-transport)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`channels.discord` (bot transport)",
|
||
"id": "`channels.discord`-(bot-transport)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`channels.googlechat` (Chat API webhook)",
|
||
"id": "`channels.googlechat`-(chat-api-webhook)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`channels.slack` (socket mode)",
|
||
"id": "`channels.slack`-(socket-mode)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`channels.mattermost` (bot token)",
|
||
"id": "`channels.mattermost`-(bot-token)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`channels.signal` (signal-cli)",
|
||
"id": "`channels.signal`-(signal-cli)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`channels.imessage` (imsg CLI)",
|
||
"id": "`channels.imessage`-(imsg-cli)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`agents.defaults.workspace`",
|
||
"id": "`agents.defaults.workspace`"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`agents.defaults.repoRoot`",
|
||
"id": "`agents.defaults.reporoot`"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`agents.defaults.skipBootstrap`",
|
||
"id": "`agents.defaults.skipbootstrap`"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`agents.defaults.bootstrapMaxChars`",
|
||
"id": "`agents.defaults.bootstrapmaxchars`"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`agents.defaults.userTimezone`",
|
||
"id": "`agents.defaults.usertimezone`"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`agents.defaults.timeFormat`",
|
||
"id": "`agents.defaults.timeformat`"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`messages`",
|
||
"id": "`messages`"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`talk`",
|
||
"id": "`talk`"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`agents.defaults`",
|
||
"id": "`agents.defaults`"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`agents.defaults.sandbox`",
|
||
"id": "`agents.defaults.sandbox`"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`models` (custom providers + base URLs)",
|
||
"id": "`models`-(custom-providers-+-base-urls)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "OpenCode Zen (multi-model proxy)",
|
||
"id": "opencode-zen-(multi-model-proxy)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "Z.AI (GLM-4.7) — provider alias support",
|
||
"id": "z.ai-(glm-4.7)-—-provider-alias-support"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "Moonshot AI (Kimi)",
|
||
"id": "moonshot-ai-(kimi)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "Kimi Coding",
|
||
"id": "kimi-coding"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "Synthetic (Anthropic-compatible)",
|
||
"id": "synthetic-(anthropic-compatible)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "Local models (LM Studio) — recommended setup",
|
||
"id": "local-models-(lm-studio)-—-recommended-setup"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "MiniMax M2.1",
|
||
"id": "minimax-m2.1"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "Cerebras (GLM 4.6 / 4.7)",
|
||
"id": "cerebras-(glm-4.6-/-4.7)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`session`",
|
||
"id": "`session`"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`skills` (skills config)",
|
||
"id": "`skills`-(skills-config)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`plugins` (extensions)",
|
||
"id": "`plugins`-(extensions)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`browser` (openclaw-managed browser)",
|
||
"id": "`browser`-(openclaw-managed-browser)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`ui` (Appearance)",
|
||
"id": "`ui`-(appearance)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`gateway` (Gateway server mode + bind)",
|
||
"id": "`gateway`-(gateway-server-mode-+-bind)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`gateway.reload` (Config hot reload)",
|
||
"id": "`gateway.reload`-(config-hot-reload)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "Multi-instance isolation",
|
||
"id": "multi-instance-isolation"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`hooks` (Gateway webhooks)",
|
||
"id": "`hooks`-(gateway-webhooks)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`canvasHost` (LAN/tailnet Canvas file server + live reload)",
|
||
"id": "`canvashost`-(lan/tailnet-canvas-file-server-+-live-reload)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`bridge` (legacy TCP bridge, removed)",
|
||
"id": "`bridge`-(legacy-tcp-bridge,-removed)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`discovery.mdns` (Bonjour / mDNS broadcast mode)",
|
||
"id": "`discovery.mdns`-(bonjour-/-mdns-broadcast-mode)"
|
||
},
|
||
{
|
||
"level": "h3",
|
||
"text": "`discovery.wideArea` (Wide-Area Bonjour / unicast DNS‑SD)",
|
||
"id": "`discovery.widearea`-(wide-area-bonjour-/-unicast-dns‑sd)"
|
||
},
|
||
{
|
||
"level": "h2",
|
||
"text": "Template variables",
|
||
"id": "template-variables"
|
||
},
|
||
{
|
||
"level": "h2",
|
||
"text": "Cron (Gateway scheduler)",
|
||
"id": "cron-(gateway-scheduler)"
|
||
}
|
||
],
|
||
"url": "llms-txt#configuration-🔧",
|
||
"links": []
|
||
} |