2026-02-11 10:44:34 -05:00
---
title: "Configuration Reference"
description: "Complete field-by-field reference for ~/.openclaw/openclaw.json"
2026-02-22 20:44:31 +01:00
summary: "Complete reference for every OpenClaw config key, defaults, and channel settings"
read_when:
- You need exact field-level config semantics or defaults
- You are validating channel, model, gateway, or tool config blocks
2026-02-11 10:44:34 -05:00
---
# Configuration Reference
Every field available in `~/.openclaw/openclaw.json` . For a task-oriented overview, see [Configuration ](/gateway/configuration ).
Config format is **JSON5** (comments + trailing commas allowed). All fields are optional — OpenClaw uses safe defaults when omitted.
---
## Channels
Each channel starts automatically when its config section exists (unless `enabled: false` ).
### DM and group access
All channels support DM policies and group policies:
| DM policy | Behavior |
| ------------------- | --------------------------------------------------------------- |
| `pairing` (default) | Unknown senders get a one-time pairing code; owner must approve |
| `allowlist` | Only senders in `allowFrom` (or paired allow store) |
| `open` | Allow all inbound DMs (requires `allowFrom: ["*"]` ) |
| `disabled` | Ignore all inbound DMs |
| Group policy | Behavior |
| --------------------- | ------------------------------------------------------ |
| `allowlist` (default) | Only groups matching the configured allowlist |
| `open` | Bypass group allowlists (mention-gating still applies) |
| `disabled` | Block all group/room messages |
< Note >
`channels.defaults.groupPolicy` sets the default when a provider's `groupPolicy` is unset.
Pairing codes expire after 1 hour. Pending DM pairing requests are capped at **3 per channel** .
2026-02-22 12:35:02 +01:00
If a provider block is missing entirely (`channels.<provider>` absent), runtime group policy falls back to `allowlist` (fail-closed) with a startup warning.
2026-02-11 10:44:34 -05:00
< / Note >
2026-02-20 19:26:25 -06:00
### Channel model overrides
Use `channels.modelByChannel` to pin specific channel IDs to a model. Values accept `provider/model` or configured model aliases. The channel mapping applies when a session does not already have a model override (for example, set via `/model` ).
```json5
{
channels: {
modelByChannel: {
discord: {
"123456789012345678": "anthropic/claude-opus-4-6",
},
slack: {
C1234567890: "openai/gpt-4.1",
},
telegram: {
"-1001234567890": "openai/gpt-4.1-mini",
"-1001234567890:topic:99": "anthropic/claude-sonnet-4-6",
},
},
},
}
```
2026-02-26 22:35:43 -05:00
### Channel defaults and heartbeat
Use `channels.defaults` for shared group-policy and heartbeat behavior across providers:
```json5
{
channels: {
defaults: {
groupPolicy: "allowlist", // open | allowlist | disabled
heartbeat: {
showOk: false,
showAlerts: true,
useIndicator: true,
},
},
},
}
```
- `channels.defaults.groupPolicy` : fallback group policy when a provider-level `groupPolicy` is unset.
- `channels.defaults.heartbeat.showOk` : include healthy channel statuses in heartbeat output.
- `channels.defaults.heartbeat.showAlerts` : include degraded/error statuses in heartbeat output.
- `channels.defaults.heartbeat.useIndicator` : render compact indicator-style heartbeat output.
2026-02-11 10:44:34 -05:00
### WhatsApp
WhatsApp runs through the gateway's web channel (Baileys Web). It starts automatically when a linked session exists.
```json5
{
channels: {
whatsapp: {
dmPolicy: "pairing", // pairing | allowlist | open | disabled
allowFrom: ["+15555550123", "+447700900123"],
textChunkLimit: 4000,
chunkMode: "length", // length | newline
mediaMaxMb: 50,
sendReadReceipts: true, // blue ticks (false in self-chat mode)
groups: {
"*": { requireMention: true },
},
groupPolicy: "allowlist",
groupAllowFrom: ["+15551234567"],
},
},
web: {
enabled: true,
heartbeatSeconds: 60,
reconnect: {
initialMs: 2000,
maxMs: 120000,
factor: 1.4,
jitter: 0.2,
maxAttempts: 0,
},
},
}
```
< Accordion title = "Multi-account WhatsApp" >
```json5
{
channels: {
whatsapp: {
accounts: {
default: {},
personal: {},
biz: {
// authDir: "~/.openclaw/credentials/whatsapp/biz",
},
},
},
},
}
```
- Outbound commands default to account `default` if present; otherwise the first configured account id (sorted).
2026-03-02 04:03:13 +00:00
- Optional `channels.whatsapp.defaultAccount` overrides that fallback default account selection when it matches a configured account id.
2026-02-11 10:44:34 -05:00
- Legacy single-account Baileys auth dir is migrated by `openclaw doctor` into `whatsapp/default` .
2026-02-14 19:49:26 +01:00
- Per-account overrides: `channels.whatsapp.accounts.<id>.sendReadReceipts` , `channels.whatsapp.accounts.<id>.dmPolicy` , `channels.whatsapp.accounts.<id>.allowFrom` .
2026-02-11 10:44:34 -05:00
< / Accordion >
### Telegram
```json5
{
channels: {
telegram: {
enabled: true,
botToken: "your-bot-token",
dmPolicy: "pairing",
allowFrom: ["tg:123456789"],
groups: {
"*": { requireMention: true },
"-1001234567890": {
allowFrom: ["@admin "],
systemPrompt: "Keep answers brief.",
topics: {
"99": {
requireMention: false,
skills: ["search"],
systemPrompt: "Stay on topic.",
},
},
},
},
customCommands: [
{ command: "backup", description: "Git backup" },
{ command: "generate", description: "Create an image" },
],
historyLimit: 50,
replyToMode: "first", // off | first | all
linkPreview: true,
2026-02-21 19:53:23 +01:00
streaming: "partial", // off | partial | block | progress (default: off)
2026-02-11 10:44:34 -05:00
actions: { reactions: true, sendMessage: true },
reactionNotifications: "own", // off | own | all
2026-03-06 10:53:06 -05:00
mediaMaxMb: 100,
2026-02-11 10:44:34 -05:00
retry: {
attempts: 3,
minDelayMs: 400,
maxDelayMs: 30000,
jitter: 0.1,
},
2026-02-22 17:09:06 +01:00
network: {
autoSelectFamily: true,
dnsResultOrder: "ipv4first",
},
2026-02-11 10:44:34 -05:00
proxy: "socks5://localhost:9050",
webhookUrl: "https://example.com/telegram-webhook",
webhookSecret: "secret",
webhookPath: "/telegram-webhook",
},
},
}
```
- Bot token: `channels.telegram.botToken` or `channels.telegram.tokenFile` , with `TELEGRAM_BOT_TOKEN` as fallback for the default account.
2026-03-02 04:03:13 +00:00
- Optional `channels.telegram.defaultAccount` overrides default account selection when it matches a configured account id.
2026-03-03 16:27:19 +08:00
- In multi-account setups (2+ account ids), set an explicit default (`channels.telegram.defaultAccount` or `channels.telegram.accounts.default` ) to avoid fallback routing; `openclaw doctor` warns when this is missing or invalid.
2026-02-11 10:44:34 -05:00
- `configWrites: false` blocks Telegram-initiated config writes (supergroup ID migrations, `/config set|unset` ).
2026-03-05 09:38:12 +01:00
- Top-level `bindings[]` entries with `type: "acp"` configure persistent ACP bindings for forum topics (use canonical `chatId:topic:topicId` in `match.peer.id` ). Field semantics are shared in [ACP Agents ](/tools/acp-agents#channel-specific-settings ).
2026-02-15 20:09:10 +05:30
- Telegram stream previews use `sendMessage` + `editMessageText` (works in direct and group chats).
2026-02-11 10:44:34 -05:00
- Retry policy: see [Retry policy ](/concepts/retry ).
### Discord
```json5
{
channels: {
discord: {
enabled: true,
token: "your-bot-token",
mediaMaxMb: 8,
allowBots: false,
actions: {
reactions: true,
stickers: true,
polls: true,
permissions: true,
messages: true,
threads: true,
pins: true,
search: true,
memberInfo: true,
roleInfo: true,
roles: false,
channelInfo: true,
voiceStatus: true,
events: true,
moderation: false,
},
replyToMode: "off", // off | first | all
2026-02-14 20:54:50 +01:00
dmPolicy: "pairing",
2026-02-24 01:01:51 +00:00
allowFrom: ["1234567890", "123456789012345678"],
2026-02-14 20:54:50 +01:00
dm: { enabled: true, groupEnabled: false, groupChannels: ["openclaw-dm"] },
2026-02-11 10:44:34 -05:00
guilds: {
"123456789012345678": {
slug: "friends-of-openclaw",
requireMention: false,
2026-03-03 11:26:20 -06:00
ignoreOtherMentions: true,
2026-02-11 10:44:34 -05:00
reactionNotifications: "own",
users: ["987654321098765432"],
channels: {
general: { allow: true },
help: {
allow: true,
requireMention: true,
users: ["987654321098765432"],
skills: ["docs"],
systemPrompt: "Short answers only.",
},
},
},
},
historyLimit: 20,
textChunkLimit: 2000,
chunkMode: "length", // length | newline
2026-02-21 19:53:23 +01:00
streaming: "off", // off | partial | block | progress (progress maps to partial on Discord)
2026-02-11 10:44:34 -05:00
maxLinesPerMessage: 17,
2026-02-15 10:24:53 -06:00
ui: {
components: {
accentColor: "#5865F2 ",
},
},
2026-02-21 19:59:50 +01:00
threadBindings: {
enabled: true,
2026-02-27 10:02:39 +01:00
idleHours: 24,
maxAgeHours: 0,
2026-02-21 19:59:50 +01:00
spawnSubagentSessions: false, // opt-in for sessions_spawn({ thread: true })
},
2026-02-20 16:06:07 -06:00
voice: {
enabled: true,
autoJoin: [
{
guildId: "123456789012345678",
channelId: "234567890123456789",
},
],
2026-02-25 00:19:36 +00:00
daveEncryption: true,
decryptionFailureTolerance: 24,
2026-02-20 16:06:07 -06:00
tts: {
provider: "openai",
openai: { voice: "alloy" },
},
},
2026-02-11 10:44:34 -05:00
retry: {
attempts: 3,
minDelayMs: 500,
maxDelayMs: 30000,
jitter: 0.1,
},
},
},
}
```
- Token: `channels.discord.token` , with `DISCORD_BOT_TOKEN` as fallback for the default account.
2026-03-02 04:03:13 +00:00
- Optional `channels.discord.defaultAccount` overrides default account selection when it matches a configured account id.
2026-02-11 10:44:34 -05:00
- Use `user:<id>` (DM) or `channel:<id>` (guild channel) for delivery targets; bare numeric IDs are rejected.
- Guild slugs are lowercase with spaces replaced by `-` ; channel keys use the slugged name (no `#` ). Prefer guild IDs.
2026-03-03 12:47:25 -06:00
- Bot-authored messages are ignored by default. `allowBots: true` enables them; use `allowBots: "mentions"` to only accept bot messages that mention the bot (own messages still filtered).
2026-03-03 11:26:20 -06:00
- `channels.discord.guilds.<id>.ignoreOtherMentions` (and channel overrides) drops messages that mention another user or role but not the bot (excluding @everyone/@here ).
2026-02-11 10:44:34 -05:00
- `maxLinesPerMessage` (default 17) splits tall messages even when under 2000 chars.
2026-02-21 19:59:50 +01:00
- `channels.discord.threadBindings` controls Discord thread-bound routing:
2026-02-27 10:02:39 +01:00
- `enabled` : Discord override for thread-bound session features (`/focus` , `/unfocus` , `/agents` , `/session idle` , `/session max-age` , and bound delivery/routing)
- `idleHours` : Discord override for inactivity auto-unfocus in hours (`0` disables)
- `maxAgeHours` : Discord override for hard max age in hours (`0` disables)
2026-02-21 19:59:50 +01:00
- `spawnSubagentSessions` : opt-in switch for `sessions_spawn({ thread: true })` auto thread creation/binding
2026-03-05 09:38:12 +01:00
- Top-level `bindings[]` entries with `type: "acp"` configure persistent ACP bindings for channels and threads (use channel/thread id in `match.peer.id` ). Field semantics are shared in [ACP Agents ](/tools/acp-agents#channel-specific-settings ).
2026-02-15 10:24:53 -06:00
- `channels.discord.ui.components.accentColor` sets the accent color for Discord components v2 containers.
2026-02-20 16:06:07 -06:00
- `channels.discord.voice` enables Discord voice channel conversations and optional auto-join + TTS overrides.
2026-02-25 00:28:02 +00:00
- `channels.discord.voice.daveEncryption` and `channels.discord.voice.decryptionFailureTolerance` pass through to `@discordjs/voice` DAVE options (`true` and `24` by default).
- OpenClaw additionally attempts voice receive recovery by leaving/rejoining a voice session after repeated decrypt failures.
2026-02-21 19:53:23 +01:00
- `channels.discord.streaming` is the canonical stream mode key. Legacy `streamMode` and boolean `streaming` values are auto-migrated.
2026-03-03 11:20:59 -06:00
- `channels.discord.autoPresence` maps runtime availability to bot presence (healthy => online, degraded => idle, exhausted => dnd) and allows optional status text overrides.
2026-02-24 01:01:51 +00:00
- `channels.discord.dangerouslyAllowNameMatching` re-enables mutable name/tag matching (break-glass compatibility mode).
2026-02-11 10:44:34 -05:00
**Reaction notification modes:** `off` (none), `own` (bot's messages, default), `all` (all messages), `allowlist` (from `guilds.<id>.users` on all messages).
### Google Chat
```json5
{
channels: {
googlechat: {
enabled: true,
serviceAccountFile: "/path/to/service-account.json",
audienceType: "app-url", // app-url | project-number
audience: "https://gateway.example.com/googlechat",
webhookPath: "/googlechat",
botUser: "users/1234567890",
dm: {
enabled: true,
policy: "pairing",
allowFrom: ["users/1234567890"],
},
groupPolicy: "allowlist",
groups: {
"spaces/AAAA": { allow: true, requireMention: true },
},
actions: { reactions: true },
typingIndicator: "message",
mediaMaxMb: 20,
},
},
}
```
- Service account JSON: inline (`serviceAccount` ) or file-based (`serviceAccountFile` ).
2026-02-24 16:26:51 -06:00
- Service account SecretRef is also supported (`serviceAccountRef` ).
2026-02-11 10:44:34 -05:00
- Env fallbacks: `GOOGLE_CHAT_SERVICE_ACCOUNT` or `GOOGLE_CHAT_SERVICE_ACCOUNT_FILE` .
2026-02-24 01:01:51 +00:00
- Use `spaces/<spaceId>` or `users/<userId>` for delivery targets.
- `channels.googlechat.dangerouslyAllowNameMatching` re-enables mutable email principal matching (break-glass compatibility mode).
2026-02-11 10:44:34 -05:00
### Slack
```json5
{
channels: {
slack: {
enabled: true,
botToken: "xoxb-...",
appToken: "xapp-...",
2026-02-14 20:54:50 +01:00
dmPolicy: "pairing",
allowFrom: ["U123", "U456", "*"],
dm: { enabled: true, groupEnabled: false, groupChannels: ["G123"] },
2026-02-11 10:44:34 -05:00
channels: {
C123: { allow: true, requireMention: true, allowBots: false },
"#general ": {
allow: true,
requireMention: true,
allowBots: false,
users: ["U123"],
skills: ["docs"],
systemPrompt: "Short answers only.",
},
},
historyLimit: 50,
allowBots: false,
reactionNotifications: "own",
reactionAllowlist: ["U123"],
replyToMode: "off", // off | first | all
thread: {
historyScope: "thread", // thread | channel
inheritParent: false,
},
actions: {
reactions: true,
messages: true,
pins: true,
memberInfo: true,
emojiList: true,
},
slashCommand: {
enabled: true,
name: "openclaw",
sessionPrefix: "slack:slash",
ephemeral: true,
},
2026-03-05 16:57:40 -05:00
typingReaction: "hourglass_flowing_sand",
2026-02-11 10:44:34 -05:00
textChunkLimit: 4000,
chunkMode: "length",
2026-02-21 19:53:23 +01:00
streaming: "partial", // off | partial | block | progress (preview mode)
nativeStreaming: true, // use Slack native streaming API when streaming=partial
2026-02-11 10:44:34 -05:00
mediaMaxMb: 20,
},
},
}
```
- **Socket mode** requires both `botToken` and `appToken` (`SLACK_BOT_TOKEN` + `SLACK_APP_TOKEN` for default account env fallback).
- **HTTP mode** requires `botToken` plus `signingSecret` (at root or per-account).
- `configWrites: false` blocks Slack-initiated config writes.
2026-03-02 04:03:13 +00:00
- Optional `channels.slack.defaultAccount` overrides default account selection when it matches a configured account id.
2026-02-21 19:53:23 +01:00
- `channels.slack.streaming` is the canonical stream mode key. Legacy `streamMode` and boolean `streaming` values are auto-migrated.
2026-02-11 10:44:34 -05:00
- Use `user:<id>` (DM) or `channel:<id>` for delivery targets.
**Reaction notification modes:** `off` , `own` (default), `all` , `allowlist` (from `reactionAllowlist` ).
**Thread session isolation:** `thread.historyScope` is per-thread (default) or shared across channel. `thread.inheritParent` copies parent channel transcript to new threads.
2026-03-05 16:57:40 -05:00
- `typingReaction` adds a temporary reaction to the inbound Slack message while a reply is running, then removes it on completion. Use a Slack emoji shortcode such as `"hourglass_flowing_sand"` .
2026-02-11 10:44:34 -05:00
| Action group | Default | Notes |
| ------------ | ------- | ---------------------- |
| reactions | enabled | React + list reactions |
| messages | enabled | Read/send/edit/delete |
| pins | enabled | Pin/unpin/list |
| memberInfo | enabled | Member info |
| emojiList | enabled | Custom emoji list |
### Mattermost
Mattermost ships as a plugin: `openclaw plugins install @openclaw/mattermost` .
```json5
{
channels: {
mattermost: {
enabled: true,
botToken: "mm-token",
baseUrl: "https://chat.example.com",
dmPolicy: "pairing",
chatmode: "oncall", // oncall | onmessage | onchar
oncharPrefixes: [">", "!"],
2026-03-03 12:39:18 +05:30
commands: {
native: true, // opt-in
nativeSkills: true,
callbackPath: "/api/channels/mattermost/command",
// Optional explicit URL for reverse-proxy/public deployments
callbackUrl: "https://gateway.example.com/api/channels/mattermost/command",
},
2026-02-11 10:44:34 -05:00
textChunkLimit: 4000,
chunkMode: "length",
},
},
}
```
Chat modes: `oncall` (respond on @-mention, default), `onmessage` (every message), `onchar` (messages starting with trigger prefix).
2026-03-03 12:39:18 +05:30
When Mattermost native commands are enabled:
- `commands.callbackPath` must be a path (for example `/api/channels/mattermost/command` ), not a full URL.
- `commands.callbackUrl` must resolve to the OpenClaw gateway endpoint and be reachable from the Mattermost server.
- For private/tailnet/internal callback hosts, Mattermost may require
`ServiceSettings.AllowedUntrustedInternalConnections` to include the callback host/domain.
Use host/domain values, not full URLs.
2026-02-26 22:35:43 -05:00
- `channels.mattermost.configWrites` : allow or deny Mattermost-initiated config writes.
- `channels.mattermost.requireMention` : require `@mention` before replying in channels.
2026-03-02 04:03:13 +00:00
- Optional `channels.mattermost.defaultAccount` overrides default account selection when it matches a configured account id.
2026-02-26 22:35:43 -05:00
2026-02-11 10:44:34 -05:00
### Signal
```json5
{
channels: {
signal: {
2026-02-26 22:35:43 -05:00
enabled: true,
account: "+15555550123", // optional account binding
dmPolicy: "pairing",
allowFrom: ["+15551234567", "uuid:123e4567-e89b-12d3-a456-426614174000"],
configWrites: true,
2026-02-11 10:44:34 -05:00
reactionNotifications: "own", // off | own | all | allowlist
reactionAllowlist: ["+15551234567", "uuid:123e4567-e89b-12d3-a456-426614174000"],
historyLimit: 50,
},
},
}
```
**Reaction notification modes:** `off` , `own` (default), `all` , `allowlist` (from `reactionAllowlist` ).
2026-02-26 22:35:43 -05:00
- `channels.signal.account` : pin channel startup to a specific Signal account identity.
- `channels.signal.configWrites` : allow or deny Signal-initiated config writes.
2026-03-02 04:03:13 +00:00
- Optional `channels.signal.defaultAccount` overrides default account selection when it matches a configured account id.
2026-02-26 22:35:43 -05:00
### BlueBubbles
BlueBubbles is the recommended iMessage path (plugin-backed, configured under `channels.bluebubbles` ).
```json5
{
channels: {
bluebubbles: {
enabled: true,
dmPolicy: "pairing",
// serverUrl, password, webhookPath, group controls, and advanced actions:
// see /channels/bluebubbles
},
},
}
```
- Core key paths covered here: `channels.bluebubbles` , `channels.bluebubbles.dmPolicy` .
2026-03-02 04:03:13 +00:00
- Optional `channels.bluebubbles.defaultAccount` overrides default account selection when it matches a configured account id.
2026-02-26 22:35:43 -05:00
- Full BlueBubbles channel configuration is documented in [BlueBubbles ](/channels/bluebubbles ).
2026-02-11 10:44:34 -05:00
### iMessage
OpenClaw spawns `imsg rpc` (JSON-RPC over stdio). No daemon or port required.
```json5
{
channels: {
imessage: {
enabled: true,
cliPath: "imsg",
dbPath: "~/Library/Messages/chat.db",
remoteHost: "user@gateway -host",
dmPolicy: "pairing",
allowFrom: ["+15555550123", "user@example .com", "chat_id:123"],
historyLimit: 50,
includeAttachments: false,
2026-02-19 14:15:34 +01:00
attachmentRoots: ["/Users/*/Library/Messages/Attachments"],
remoteAttachmentRoots: ["/Users/*/Library/Messages/Attachments"],
2026-02-11 10:44:34 -05:00
mediaMaxMb: 16,
service: "auto",
region: "US",
},
},
}
```
2026-03-02 04:03:13 +00:00
- Optional `channels.imessage.defaultAccount` overrides default account selection when it matches a configured account id.
2026-02-11 10:44:34 -05:00
- Requires Full Disk Access to the Messages DB.
- Prefer `chat_id:<id>` targets. Use `imsg chats --limit 20` to list chats.
2026-02-19 11:07:56 +01:00
- `cliPath` can point to an SSH wrapper; set `remoteHost` (`host` or `user@host` ) for SCP attachment fetching.
2026-02-19 14:15:34 +01:00
- `attachmentRoots` and `remoteAttachmentRoots` restrict inbound attachment paths (default: `/Users/*/Library/Messages/Attachments` ).
2026-02-19 11:07:56 +01:00
- SCP uses strict host-key checking, so ensure the relay host key already exists in `~/.ssh/known_hosts` .
2026-02-26 22:35:43 -05:00
- `channels.imessage.configWrites` : allow or deny iMessage-initiated config writes.
2026-02-11 10:44:34 -05:00
< Accordion title = "iMessage SSH wrapper example" >
```bash
#!/usr/bin/env bash
exec ssh -T gateway-host imsg "$@"
```
< / Accordion >
2026-02-26 22:35:43 -05:00
### Microsoft Teams
Microsoft Teams is extension-backed and configured under `channels.msteams` .
```json5
{
channels: {
msteams: {
enabled: true,
configWrites: true,
// appId, appPassword, tenantId, webhook, team/channel policies:
// see /channels/msteams
},
},
}
```
- Core key paths covered here: `channels.msteams` , `channels.msteams.configWrites` .
- Full Teams config (credentials, webhook, DM/group policy, per-team/per-channel overrides) is documented in [Microsoft Teams ](/channels/msteams ).
### IRC
IRC is extension-backed and configured under `channels.irc` .
```json5
{
channels: {
irc: {
enabled: true,
dmPolicy: "pairing",
configWrites: true,
nickserv: {
enabled: true,
service: "NickServ",
password: "${IRC_NICKSERV_PASSWORD}",
register: false,
registerEmail: "bot@example .com",
},
},
},
}
```
- Core key paths covered here: `channels.irc` , `channels.irc.dmPolicy` , `channels.irc.configWrites` , `channels.irc.nickserv.*` .
2026-03-02 04:03:13 +00:00
- Optional `channels.irc.defaultAccount` overrides default account selection when it matches a configured account id.
2026-02-26 22:35:43 -05:00
- Full IRC channel configuration (host/port/TLS/channels/allowlists/mention gating) is documented in [IRC ](/channels/irc ).
2026-02-11 10:44:34 -05:00
### Multi-account (all channels)
Run multiple accounts per channel (each with its own `accountId` ):
```json5
{
channels: {
telegram: {
accounts: {
default: {
name: "Primary bot",
botToken: "123456:ABC...",
},
alerts: {
name: "Alerts bot",
botToken: "987654:XYZ...",
},
},
},
},
}
```
- `default` is used when `accountId` is omitted (CLI + routing).
- Env tokens only apply to the **default** account.
- Base channel settings apply to all accounts unless overridden per account.
- Use `bindings[].match.accountId` to route each account to a different agent.
2026-02-26 04:06:03 -05:00
- If you add a non-default account via `openclaw channels add` (or channel onboarding) while still on a single-account top-level channel config, OpenClaw moves account-scoped top-level single-account values into `channels.<channel>.accounts.default` first so the original account keeps working.
- Existing channel-only bindings (no `accountId` ) keep matching the default account; account-scoped bindings remain optional.
- `openclaw doctor --fix` also repairs mixed shapes by moving account-scoped top-level single-account values into `accounts.default` when named accounts exist but `default` is missing.
2026-02-11 10:44:34 -05:00
2026-02-26 22:35:43 -05:00
### Other extension channels
Many extension channels are configured as `channels.<id>` and documented in their dedicated channel pages (for example Feishu, Matrix, LINE, Nostr, Zalo, Nextcloud Talk, Synology Chat, and Twitch).
See the full channel index: [Channels ](/channels ).
2026-02-11 10:44:34 -05:00
### Group chat mention gating
Group messages default to **require mention** (metadata mention or regex patterns). Applies to WhatsApp, Telegram, Discord, Google Chat, and iMessage group chats.
**Mention types:**
- **Metadata mentions**: Native platform @-mentions. Ignored in WhatsApp self-chat mode.
- **Text patterns**: Regex patterns in `agents.list[].groupChat.mentionPatterns` . Always checked.
- Mention gating is enforced only when detection is possible (native mentions or at least one pattern).
```json5
{
messages: {
groupChat: { historyLimit: 50 },
},
agents: {
list: [{ id: "main", groupChat: { mentionPatterns: ["@openclaw ", "openclaw"] } }],
},
}
```
`messages.groupChat.historyLimit` sets the global default. Channels can override with `channels.<channel>.historyLimit` (or per-account). Set `0` to disable.
#### DM history limits
```json5
{
channels: {
telegram: {
dmHistoryLimit: 30,
dms: {
"123456789": { historyLimit: 50 },
},
},
},
}
```
Resolution: per-DM override → provider default → no limit (all retained).
Supported: `telegram` , `whatsapp` , `discord` , `slack` , `signal` , `imessage` , `msteams` .
#### Self-chat mode
Include your own number in `allowFrom` to enable self-chat mode (ignores native @-mentions, only responds to text patterns):
```json5
{
channels: {
whatsapp: {
allowFrom: ["+15555550123"],
groups: { "*": { requireMention: true } },
},
},
agents: {
list: [
{
id: "main",
groupChat: { mentionPatterns: ["reisponde", "@openclaw "] },
},
],
},
}
```
### Commands (chat command handling)
```json5
{
commands: {
native: "auto", // register native commands when supported
text: true, // parse /commands in chat messages
bash: false, // allow ! (alias: /bash)
bashForegroundMs: 2000,
config: false, // allow /config
debug: false, // allow /debug
restart: false, // allow /restart + gateway restart tool
allowFrom: {
"*": ["user1"],
discord: ["user:123"],
},
useAccessGroups: true,
},
}
```
< Accordion title = "Command details" >
- Text commands must be **standalone** messages with leading `/` .
- `native: "auto"` turns on native commands for Discord/Telegram, leaves Slack off.
- Override per channel: `channels.discord.commands.native` (bool or `"auto"` ). `false` clears previously registered commands.
- `channels.telegram.customCommands` adds extra Telegram bot menu entries.
- `bash: true` enables `! <cmd>` for host shell. Requires `tools.elevated.enabled` and sender in `tools.elevated.allowFrom.<channel>` .
2026-03-07 19:38:40 +00:00
- `config: true` enables `/config` (reads/writes `openclaw.json` ). For gateway `chat.send` clients, persistent `/config set|unset` writes also require `operator.admin` ; read-only `/config show` stays available to normal write-scoped operator clients.
2026-02-11 10:44:34 -05:00
- `channels.<provider>.configWrites` gates config mutations per channel (default: true).
- `allowFrom` is per-provider. When set, it is the **only** authorization source (channel allowlists/pairing and `useAccessGroups` are ignored).
- `useAccessGroups: false` allows commands to bypass access-group policies when `allowFrom` is not set.
< / Accordion >
---
## Agent defaults
### `agents.defaults.workspace`
Default: `~/.openclaw/workspace` .
```json5
{
agents: { defaults: { workspace: "~/.openclaw/workspace" } },
}
```
### `agents.defaults.repoRoot`
Optional repository root shown in the system prompt's Runtime line. If unset, OpenClaw auto-detects by walking upward from the workspace.
```json5
{
agents: { defaults: { repoRoot: "~/Projects/openclaw" } },
}
```
### `agents.defaults.skipBootstrap`
Disables automatic creation of workspace bootstrap files (`AGENTS.md` , `SOUL.md` , `TOOLS.md` , `IDENTITY.md` , `USER.md` , `HEARTBEAT.md` , `BOOTSTRAP.md` ).
```json5
{
agents: { defaults: { skipBootstrap: true } },
}
```
### `agents.defaults.bootstrapMaxChars`
Max characters per workspace bootstrap file before truncation. Default: `20000` .
```json5
{
agents: { defaults: { bootstrapMaxChars: 20000 } },
}
```
2026-02-14 18:36:35 -05:00
### `agents.defaults.bootstrapTotalMaxChars`
2026-02-16 12:04:53 -05:00
Max total characters injected across all workspace bootstrap files. Default: `150000` .
2026-02-14 18:36:35 -05:00
```json5
{
2026-02-16 12:04:53 -05:00
agents: { defaults: { bootstrapTotalMaxChars: 150000 } },
2026-02-14 18:36:35 -05:00
}
```
2026-03-03 16:28:38 -05:00
### `agents.defaults.bootstrapPromptTruncationWarning`
Controls agent-visible warning text when bootstrap context is truncated.
Default: `"once"` .
- `"off"` : never inject warning text into the system prompt.
- `"once"` : inject warning once per unique truncation signature (recommended).
- `"always"` : inject warning on every run when truncation exists.
```json5
{
agents: { defaults: { bootstrapPromptTruncationWarning: "once" } }, // off | once | always
}
```
2026-02-18 00:56:57 +01:00
### `agents.defaults.imageMaxDimensionPx`
Max pixel size for the longest image side in transcript/tool image blocks before provider calls.
Default: `1200` .
Lower values usually reduce vision-token usage and request payload size for screenshot-heavy runs.
Higher values preserve more visual detail.
```json5
{
agents: { defaults: { imageMaxDimensionPx: 1200 } },
}
```
2026-02-11 10:44:34 -05:00
### `agents.defaults.userTimezone`
Timezone for system prompt context (not message timestamps). Falls back to host timezone.
```json5
{
agents: { defaults: { userTimezone: "America/Chicago" } },
}
```
### `agents.defaults.timeFormat`
Time format in system prompt. Default: `auto` (OS preference).
```json5
{
agents: { defaults: { timeFormat: "auto" } }, // auto | 12 | 24
}
```
### `agents.defaults.model`
```json5
{
agents: {
defaults: {
models: {
"anthropic/claude-opus-4-6": { alias: "opus" },
2026-03-03 00:02:25 +00:00
"minimax/MiniMax-M2.5": { alias: "minimax" },
2026-02-11 10:44:34 -05:00
},
model: {
primary: "anthropic/claude-opus-4-6",
2026-03-03 00:02:25 +00:00
fallbacks: ["minimax/MiniMax-M2.5"],
2026-02-11 10:44:34 -05:00
},
imageModel: {
primary: "openrouter/qwen/qwen-2.5-vl-72b-instruct:free",
fallbacks: ["openrouter/google/gemini-2.0-flash-vision:free"],
},
feat: add PDF analysis tool with native provider support (#31319)
* feat: add PDF analysis tool with native provider support
New `pdf` tool for analyzing PDF documents with model-powered analysis.
Architecture:
- Native PDF path: sends raw PDF bytes directly to providers that support
inline document input (Anthropic via DocumentBlockParam, Google Gemini
via inlineData with application/pdf MIME type)
- Extraction fallback: for providers without native PDF support, extracts
text via pdfjs-dist and rasterizes pages to images via @napi-rs/canvas,
then sends through the standard vision/text completion path
Key features:
- Single PDF (`pdf` param) or multiple PDFs (`pdfs` array, up to 10)
- Page range selection (`pages` param, e.g. "1-5", "1,3,7-9")
- Model override (`model` param) and file size limits (`maxBytesMb`)
- Auto-detects provider capability and falls back gracefully
- Same security patterns as image tool (SSRF guards, sandbox support,
local path roots, workspace-only policy)
Config (agents.defaults):
- pdfModel: primary/fallbacks (defaults to imageModel, then session model)
- pdfMaxBytesMb: max PDF file size (default: 10)
- pdfMaxPages: max pages to process (default: 20)
Model catalog:
- Extended ModelInputType to include "document" alongside "text"/"image"
- Added modelSupportsDocument() capability check
Files:
- src/agents/tools/pdf-tool.ts - main tool factory
- src/agents/tools/pdf-tool.helpers.ts - helpers (page range, config, etc.)
- src/agents/tools/pdf-native-providers.ts - direct API calls for Anthropic/Google
- src/agents/tools/pdf-tool.test.ts - 43 tests covering all paths
- Modified: model-catalog.ts, openclaw-tools.ts, config schema/types/labels/help
* fix: prepare pdf tool for merge (#31319) (thanks @tyler6204)
2026-03-01 22:39:12 -08:00
pdfModel: {
primary: "anthropic/claude-opus-4-6",
fallbacks: ["openai/gpt-5-mini"],
},
pdfMaxBytesMb: 10,
pdfMaxPages: 20,
2026-02-11 10:44:34 -05:00
thinkingDefault: "low",
verboseDefault: "off",
elevatedDefault: "on",
timeoutSeconds: 600,
mediaMaxMb: 5,
contextTokens: 200000,
maxConcurrent: 3,
},
},
}
```
2026-02-23 16:18:55 +08:00
- `model` : accepts either a string (`"provider/model"` ) or an object (`{ primary, fallbacks }` ).
- String form sets only the primary model.
- Object form sets primary plus ordered failover models.
- `imageModel` : accepts either a string (`"provider/model"` ) or an object (`{ primary, fallbacks }` ).
- Used by the `image` tool path as its vision-model config.
- Also used as fallback routing when the selected/default model cannot accept image input.
feat: add PDF analysis tool with native provider support (#31319)
* feat: add PDF analysis tool with native provider support
New `pdf` tool for analyzing PDF documents with model-powered analysis.
Architecture:
- Native PDF path: sends raw PDF bytes directly to providers that support
inline document input (Anthropic via DocumentBlockParam, Google Gemini
via inlineData with application/pdf MIME type)
- Extraction fallback: for providers without native PDF support, extracts
text via pdfjs-dist and rasterizes pages to images via @napi-rs/canvas,
then sends through the standard vision/text completion path
Key features:
- Single PDF (`pdf` param) or multiple PDFs (`pdfs` array, up to 10)
- Page range selection (`pages` param, e.g. "1-5", "1,3,7-9")
- Model override (`model` param) and file size limits (`maxBytesMb`)
- Auto-detects provider capability and falls back gracefully
- Same security patterns as image tool (SSRF guards, sandbox support,
local path roots, workspace-only policy)
Config (agents.defaults):
- pdfModel: primary/fallbacks (defaults to imageModel, then session model)
- pdfMaxBytesMb: max PDF file size (default: 10)
- pdfMaxPages: max pages to process (default: 20)
Model catalog:
- Extended ModelInputType to include "document" alongside "text"/"image"
- Added modelSupportsDocument() capability check
Files:
- src/agents/tools/pdf-tool.ts - main tool factory
- src/agents/tools/pdf-tool.helpers.ts - helpers (page range, config, etc.)
- src/agents/tools/pdf-native-providers.ts - direct API calls for Anthropic/Google
- src/agents/tools/pdf-tool.test.ts - 43 tests covering all paths
- Modified: model-catalog.ts, openclaw-tools.ts, config schema/types/labels/help
* fix: prepare pdf tool for merge (#31319) (thanks @tyler6204)
2026-03-01 22:39:12 -08:00
- `pdfModel` : accepts either a string (`"provider/model"` ) or an object (`{ primary, fallbacks }` ).
- Used by the `pdf` tool for model routing.
- If omitted, the PDF tool falls back to `imageModel` , then to best-effort provider defaults.
- `pdfMaxBytesMb` : default PDF size limit for the `pdf` tool when `maxBytesMb` is not passed at call time.
- `pdfMaxPages` : default maximum pages considered by extraction fallback mode in the `pdf` tool.
2026-02-11 10:44:34 -05:00
- `model.primary` : format `provider/model` (e.g. `anthropic/claude-opus-4-6` ). If you omit the provider, OpenClaw assumes `anthropic` (deprecated).
2026-02-23 18:45:30 +00:00
- `models` : the configured model catalog and allowlist for `/model` . Each entry can include `alias` (shortcut) and `params` (provider-specific, for example `temperature` , `maxTokens` , `cacheRetention` , `context1m` ).
- `params` merge precedence (config): `agents.defaults.models["provider/model"].params` is the base, then `agents.list[].params` (matching agent id) overrides by key.
2026-02-23 16:18:55 +08:00
- Config writers that mutate these fields (for example `/models set` , `/models set-image` , and fallback add/remove commands) save canonical object form and preserve existing fallback lists when possible.
2026-02-11 10:44:34 -05:00
- `maxConcurrent` : max parallel agent runs across sessions (each session still serialized). Default: 1.
**Built-in alias shorthands** (only apply when the model is in `agents.defaults.models` ):
2026-03-08 05:12:48 +00:00
| Alias | Model |
| ------------------- | -------------------------------------- |
| `opus` | `anthropic/claude-opus-4-6` |
| `sonnet` | `anthropic/claude-sonnet-4-6` |
| `gpt` | `openai/gpt-5.4` |
| `gpt-mini` | `openai/gpt-5-mini` |
| `gemini` | `google/gemini-3.1-pro-preview` |
| `gemini-flash` | `google/gemini-3-flash-preview` |
| `gemini-flash-lite` | `google/gemini-3.1-flash-lite-preview` |
2026-02-11 10:44:34 -05:00
Your configured aliases always win over defaults.
Z.AI GLM-4.x models automatically enable thinking mode unless you set `--thinking off` or define `agents.defaults.models["zai/<model>"].params.thinking` yourself.
2026-02-17 09:22:26 -05:00
Z.AI models enable `tool_stream` by default for tool call streaming. Set `agents.defaults.models["zai/<model>"].params.tool_stream` to `false` to disable it.
2026-03-02 05:16:09 +00:00
Anthropic Claude 4.6 models default to `adaptive` thinking when no explicit thinking level is set.
2026-02-11 10:44:34 -05:00
### `agents.defaults.cliBackends`
Optional CLI backends for text-only fallback runs (no tool calls). Useful as a backup when API providers fail.
```json5
{
agents: {
defaults: {
cliBackends: {
"claude-cli": {
command: "/opt/homebrew/bin/claude",
},
"my-cli": {
command: "my-cli",
args: ["--json"],
output: "json",
modelArg: "--model",
sessionArg: "--session",
sessionMode: "existing",
systemPromptArg: "--system",
systemPromptWhen: "first",
imageArg: "--image",
imageMode: "repeat",
},
},
},
},
}
```
- CLI backends are text-first; tools are always disabled.
- Sessions supported when `sessionArg` is set.
- Image pass-through supported when `imageArg` accepts file paths.
### `agents.defaults.heartbeat`
Periodic heartbeat runs.
```json5
{
agents: {
defaults: {
heartbeat: {
every: "30m", // 0m disables
model: "openai/gpt-5.2-mini",
includeReasoning: false,
2026-03-05 17:05:21 -05:00
lightContext: false, // default: false; true keeps only HEARTBEAT.md from workspace bootstrap files
2026-02-11 10:44:34 -05:00
session: "main",
to: "+15555550123",
2026-02-26 03:56:40 +01:00
directPolicy: "allow", // allow (default) | block
2026-02-25 00:33:32 +00:00
target: "none", // default: none | options: last | whatsapp | telegram | discord | ...
2026-02-11 10:44:34 -05:00
prompt: "Read HEARTBEAT.md if it exists...",
ackMaxChars: 300,
2026-02-16 13:29:24 -06:00
suppressToolErrorWarnings: false,
2026-02-11 10:44:34 -05:00
},
},
},
}
```
- `every` : duration string (ms/s/m/h). Default: `30m` .
2026-02-16 13:29:24 -06:00
- `suppressToolErrorWarnings` : when true, suppresses tool error warning payloads during heartbeat runs.
2026-02-26 03:56:40 +01:00
- `directPolicy` : direct/DM delivery policy. `allow` (default) permits direct-target delivery. `block` suppresses direct-target delivery and emits `reason=dm-blocked` .
2026-03-05 17:05:21 -05:00
- `lightContext` : when true, heartbeat runs use lightweight bootstrap context and keep only `HEARTBEAT.md` from workspace bootstrap files.
2026-02-11 10:44:34 -05:00
- Per-agent: set `agents.list[].heartbeat` . When any agent defines `heartbeat` , **only those agents** run heartbeats.
- Heartbeats run full agent turns — shorter intervals burn more tokens.
### `agents.defaults.compaction`
```json5
{
agents: {
defaults: {
compaction: {
mode: "safeguard", // default | safeguard
reserveTokensFloor: 24000,
2026-02-27 11:14:05 -03:00
identifierPolicy: "strict", // strict | off | custom
identifierInstructions: "Preserve deployment IDs, ticket IDs, and host:port pairs exactly.", // used when identifierPolicy=custom
2026-03-07 01:57:15 +03:00
postCompactionSections: ["Session Startup", "Red Lines"], // [] disables reinjection
2026-02-11 10:44:34 -05:00
memoryFlush: {
enabled: true,
softThresholdTokens: 6000,
systemPrompt: "Session nearing compaction. Store durable memories now.",
prompt: "Write any lasting notes to memory/YYYY-MM-DD.md; reply with NO_REPLY if nothing to store.",
},
},
},
},
}
```
- `mode` : `default` or `safeguard` (chunked summarization for long histories). See [Compaction ](/concepts/compaction ).
2026-02-27 11:14:05 -03:00
- `identifierPolicy` : `strict` (default), `off` , or `custom` . `strict` prepends built-in opaque identifier retention guidance during compaction summarization.
- `identifierInstructions` : optional custom identifier-preservation text used when `identifierPolicy=custom` .
2026-03-07 01:57:15 +03:00
- `postCompactionSections` : optional AGENTS.md H2/H3 section names to re-inject after compaction. Defaults to `["Session Startup", "Red Lines"]` ; set `[]` to disable reinjection. When unset or explicitly set to that default pair, older `Every Session` /`Safety` headings are also accepted as a legacy fallback.
2026-02-11 10:44:34 -05:00
- `memoryFlush` : silent agentic turn before auto-compaction to store durable memories. Skipped when workspace is read-only.
### `agents.defaults.contextPruning`
Prunes **old tool results** from in-memory context before sending to the LLM. Does **not** modify session history on disk.
```json5
{
agents: {
defaults: {
contextPruning: {
mode: "cache-ttl", // off | cache-ttl
ttl: "1h", // duration (ms/s/m/h), default unit: minutes
keepLastAssistants: 3,
softTrimRatio: 0.3,
hardClearRatio: 0.5,
minPrunableToolChars: 50000,
softTrim: { maxChars: 4000, headChars: 1500, tailChars: 1500 },
hardClear: { enabled: true, placeholder: "[Old tool result content cleared]" },
tools: { deny: ["browser", "canvas"] },
},
},
},
}
```
< Accordion title = "cache-ttl mode behavior" >
- `mode: "cache-ttl"` enables pruning passes.
- `ttl` controls how often pruning can run again (after the last cache touch).
- Pruning soft-trims oversized tool results first, then hard-clears older tool results if needed.
**Soft-trim** keeps beginning + end and inserts `...` in the middle.
**Hard-clear** replaces the entire tool result with the placeholder.
Notes:
- Image blocks are never trimmed/cleared.
- Ratios are character-based (approximate), not exact token counts.
- If fewer than `keepLastAssistants` assistant messages exist, pruning is skipped.
< / Accordion >
See [Session Pruning ](/concepts/session-pruning ) for behavior details.
### Block streaming
```json5
{
agents: {
defaults: {
blockStreamingDefault: "off", // on | off
blockStreamingBreak: "text_end", // text_end | message_end
blockStreamingChunk: { minChars: 800, maxChars: 1200 },
blockStreamingCoalesce: { idleMs: 1000 },
humanDelay: { mode: "natural" }, // off | natural | custom (use minMs/maxMs)
},
},
}
```
- Non-Telegram channels require explicit `*.blockStreaming: true` to enable block replies.
- Channel overrides: `channels.<channel>.blockStreamingCoalesce` (and per-account variants). Signal/Slack/Discord/Google Chat default `minChars: 1500` .
- `humanDelay` : randomized pause between block replies. `natural` = 800– 2500ms. Per-agent override: `agents.list[].humanDelay` .
See [Streaming ](/concepts/streaming ) for behavior + chunking details.
### Typing indicators
```json5
{
agents: {
defaults: {
typingMode: "instant", // never | instant | thinking | message
typingIntervalSeconds: 6,
},
},
}
```
- Defaults: `instant` for direct chats/mentions, `message` for unmentioned group chats.
- Per-session overrides: `session.typingMode` , `session.typingIntervalSeconds` .
See [Typing Indicators ](/concepts/typing-indicators ).
### `agents.defaults.sandbox`
Optional **Docker sandboxing** for the embedded agent. See [Sandboxing ](/gateway/sandboxing ) for the full guide.
```json5
{
agents: {
defaults: {
sandbox: {
mode: "non-main", // off | non-main | all
scope: "agent", // session | agent | shared
workspaceAccess: "none", // none | ro | rw
workspaceRoot: "~/.openclaw/sandboxes",
docker: {
image: "openclaw-sandbox:bookworm-slim",
containerPrefix: "openclaw-sbx-",
workdir: "/workspace",
readOnlyRoot: true,
tmpfs: ["/tmp", "/var/tmp", "/run"],
network: "none",
user: "1000:1000",
capDrop: ["ALL"],
env: { LANG: "C.UTF-8" },
setupCommand: "apt-get update & & apt-get install -y git curl jq",
pidsLimit: 256,
memory: "1g",
memorySwap: "2g",
cpus: 1,
ulimits: {
nofile: { soft: 1024, hard: 2048 },
nproc: 256,
},
seccompProfile: "/path/to/seccomp.json",
apparmorProfile: "openclaw-sandbox",
dns: ["1.1.1.1", "8.8.8.8"],
extraHosts: ["internal.service:10.0.0.5"],
binds: ["/home/user/source:/source:rw"],
},
browser: {
enabled: false,
image: "openclaw-sandbox-browser:bookworm-slim",
2026-02-21 14:01:40 +01:00
network: "openclaw-sandbox-browser",
2026-02-11 10:44:34 -05:00
cdpPort: 9222,
2026-02-21 14:01:40 +01:00
cdpSourceRange: "172.21.0.1/32",
2026-02-11 10:44:34 -05:00
vncPort: 5900,
noVncPort: 6080,
headless: false,
enableNoVnc: true,
allowHostControl: false,
autoStart: true,
autoStartTimeoutMs: 12000,
},
prune: {
idleHours: 24,
maxAgeDays: 7,
},
},
},
},
tools: {
sandbox: {
tools: {
allow: [
"exec",
"process",
"read",
"write",
"edit",
"apply_patch",
"sessions_list",
"sessions_history",
"sessions_send",
"sessions_spawn",
"session_status",
],
deny: ["browser", "canvas", "nodes", "cron", "discord", "gateway"],
},
},
},
}
```
< Accordion title = "Sandbox details" >
**Workspace access:**
- `none` : per-scope sandbox workspace under `~/.openclaw/sandboxes`
- `ro` : sandbox workspace at `/workspace` , agent workspace mounted read-only at `/agent`
- `rw` : agent workspace mounted read/write at `/workspace`
**Scope:**
- `session` : per-session container + workspace
- `agent` : one container + workspace per agent (default)
- `shared` : shared container and workspace (no cross-session isolation)
**`setupCommand` ** runs once after container creation (via `sh -lc` ). Needs network egress, writable root, root user.
2026-02-24 23:19:48 +00:00
**Containers default to `network: "none"` ** — set to `"bridge"` (or a custom bridge network) if the agent needs outbound access.
`"host"` is blocked. `"container:<id>"` is blocked by default unless you explicitly set
`sandbox.docker.dangerouslyAllowContainerNamespaceJoin: true` (break-glass).
2026-02-11 10:44:34 -05:00
**Inbound attachments** are staged into `media/inbound/*` in the active workspace.
**`docker.binds` ** mounts additional host directories; global and per-agent binds are merged.
2026-03-02 20:58:20 -06:00
**Sandboxed browser** (`sandbox.browser.enabled` ): Chromium + CDP in a container. noVNC URL injected into system prompt. Does not require `browser.enabled` in `openclaw.json` .
noVNC observer access uses VNC auth by default and OpenClaw emits a short-lived token URL (instead of exposing the password in the shared URL).
2026-02-11 10:44:34 -05:00
- `allowHostControl: false` (default) blocks sandboxed sessions from targeting the host browser.
2026-02-21 14:01:40 +01:00
- `network` defaults to `openclaw-sandbox-browser` (dedicated bridge network). Set to `bridge` only when you explicitly want global bridge connectivity.
- `cdpSourceRange` optionally restricts CDP ingress at the container edge to a CIDR range (for example `172.21.0.1/32` ).
2026-02-14 23:27:41 +09:00
- `sandbox.browser.binds` mounts additional host directories into the sandbox browser container only. When set (including `[]` ), it replaces `docker.binds` for the browser container.
2026-03-02 11:28:27 -08:00
- Launch defaults are defined in `scripts/sandbox-browser-entrypoint.sh` and tuned for container hosts:
- `--remote-debugging-address=127.0.0.1`
- `--remote-debugging-port=<derived from OPENCLAW_BROWSER_CDP_PORT>`
- `--user-data-dir=${HOME}/.chrome`
- `--no-first-run`
- `--no-default-browser-check`
- `--disable-3d-apis`
- `--disable-gpu`
- `--disable-software-rasterizer`
- `--disable-dev-shm-usage`
- `--disable-background-networking`
- `--disable-features=TranslateUI`
- `--disable-breakpad`
- `--disable-crash-reporter`
- `--renderer-process-limit=2`
- `--no-zygote`
- `--metrics-recording-only`
- `--disable-extensions` (default enabled)
- `--disable-3d-apis` , `--disable-software-rasterizer` , and `--disable-gpu` are
enabled by default and can be disabled with
`OPENCLAW_BROWSER_DISABLE_GRAPHICS_FLAGS=0` if WebGL/3D usage requires it.
- `OPENCLAW_BROWSER_DISABLE_EXTENSIONS=0` re-enables extensions if your workflow
depends on them.
- `--renderer-process-limit=2` can be changed with
`OPENCLAW_BROWSER_RENDERER_PROCESS_LIMIT=<N>` ; set `0` to use Chromium's
default process limit.
- plus `--no-sandbox` and `--disable-setuid-sandbox` when `noSandbox` is enabled.
- Defaults are the container image baseline; use a custom browser image with a custom
entrypoint to change container defaults.
2026-02-11 10:44:34 -05:00
< / Accordion >
Build images:
```bash
scripts/sandbox-setup.sh # main sandbox image
scripts/sandbox-browser-setup.sh # optional browser image
```
### `agents.list` (per-agent overrides)
```json5
{
agents: {
list: [
{
id: "main",
default: true,
name: "Main Agent",
workspace: "~/.openclaw/workspace",
agentDir: "~/.openclaw/agents/main/agent",
model: "anthropic/claude-opus-4-6", // or { primary, fallbacks }
2026-02-23 18:45:30 +00:00
params: { cacheRetention: "none" }, // overrides matching defaults.models params by key
2026-02-11 10:44:34 -05:00
identity: {
name: "Samantha",
theme: "helpful sloth",
emoji: "🦥",
avatar: "avatars/samantha.png",
},
groupChat: { mentionPatterns: ["@openclaw "] },
sandbox: { mode: "off" },
2026-03-05 09:38:12 +01:00
runtime: {
type: "acp",
acp: {
agent: "codex",
backend: "acpx",
mode: "persistent",
cwd: "/workspace/openclaw",
},
},
2026-02-11 10:44:34 -05:00
subagents: { allowAgents: ["*"] },
tools: {
profile: "coding",
allow: ["browser"],
deny: ["canvas"],
elevated: { enabled: true },
},
},
],
},
}
```
- `id` : stable agent id (required).
- `default` : when multiple are set, first wins (warning logged). If none set, first list entry is default.
2026-02-17 10:40:25 -05:00
- `model` : string form overrides `primary` only; object form `{ primary, fallbacks }` overrides both (`[]` disables global fallbacks). Cron jobs that only override `primary` still inherit default fallbacks unless you set `fallbacks: []` .
2026-02-23 18:45:30 +00:00
- `params` : per-agent stream params merged over the selected model entry in `agents.defaults.models` . Use this for agent-specific overrides like `cacheRetention` , `temperature` , or `maxTokens` without duplicating the whole model catalog.
2026-03-05 09:38:12 +01:00
- `runtime` : optional per-agent runtime descriptor. Use `type: "acp"` with `runtime.acp` defaults (`agent` , `backend` , `mode` , `cwd` ) when the agent should default to ACP harness sessions.
2026-02-11 10:44:34 -05:00
- `identity.avatar` : workspace-relative path, `http(s)` URL, or `data:` URI.
- `identity` derives defaults: `ackReaction` from `emoji` , `mentionPatterns` from `name` /`emoji` .
- `subagents.allowAgents` : allowlist of agent ids for `sessions_spawn` (`["*"]` = any; default: same agent only).
2026-03-02 01:10:39 +00:00
- Sandbox inheritance guard: if the requester session is sandboxed, `sessions_spawn` rejects targets that would run unsandboxed.
2026-02-11 10:44:34 -05:00
---
## Multi-agent routing
Run multiple isolated agents inside one Gateway. See [Multi-Agent ](/concepts/multi-agent ).
```json5
{
agents: {
list: [
{ id: "home", default: true, workspace: "~/.openclaw/workspace-home" },
{ id: "work", workspace: "~/.openclaw/workspace-work" },
],
},
bindings: [
{ agentId: "home", match: { channel: "whatsapp", accountId: "personal" } },
{ agentId: "work", match: { channel: "whatsapp", accountId: "biz" } },
],
}
```
### Binding match fields
2026-03-05 09:38:12 +01:00
- `type` (optional): `route` for normal routing (missing type defaults to route), `acp` for persistent ACP conversation bindings.
2026-02-11 10:44:34 -05:00
- `match.channel` (required)
- `match.accountId` (optional; `*` = any account; omitted = default account)
- `match.peer` (optional; `{ kind: direct|group|channel, id }` )
- `match.guildId` / `match.teamId` (optional; channel-specific)
2026-03-05 09:38:12 +01:00
- `acp` (optional; only for `type: "acp"` ): `{ mode, label, cwd, backend }`
2026-02-11 10:44:34 -05:00
**Deterministic match order:**
1. `match.peer`
2. `match.guildId`
3. `match.teamId`
4. `match.accountId` (exact, no peer/guild/team)
5. `match.accountId: "*"` (channel-wide)
6. Default agent
Within each tier, the first matching `bindings` entry wins.
2026-03-05 09:38:12 +01:00
For `type: "acp"` entries, OpenClaw resolves by exact conversation identity (`match.channel` + account + `match.peer.id` ) and does not use the route binding tier order above.
2026-02-11 10:44:34 -05:00
### Per-agent access profiles
< Accordion title = "Full access (no sandbox)" >
```json5
{
agents: {
list: [
{
id: "personal",
workspace: "~/.openclaw/workspace-personal",
sandbox: { mode: "off" },
},
],
},
}
```
< / Accordion >
< Accordion title = "Read-only tools + workspace" >
```json5
{
agents: {
list: [
{
id: "family",
workspace: "~/.openclaw/workspace-family",
sandbox: { mode: "all", scope: "agent", workspaceAccess: "ro" },
tools: {
allow: [
"read",
"sessions_list",
"sessions_history",
"sessions_send",
"sessions_spawn",
"session_status",
],
deny: ["write", "edit", "apply_patch", "exec", "process", "browser"],
},
},
],
},
}
```
< / Accordion >
< Accordion title = "No filesystem access (messaging only)" >
```json5
{
agents: {
list: [
{
id: "public",
workspace: "~/.openclaw/workspace-public",
sandbox: { mode: "all", scope: "agent", workspaceAccess: "none" },
tools: {
allow: [
"sessions_list",
"sessions_history",
"sessions_send",
"sessions_spawn",
"session_status",
"whatsapp",
"telegram",
"slack",
"discord",
"gateway",
],
deny: [
"read",
"write",
"edit",
"apply_patch",
"exec",
"process",
"browser",
"canvas",
"nodes",
"cron",
"gateway",
"image",
],
},
},
],
},
}
```
< / Accordion >
See [Multi-Agent Sandbox & Tools ](/tools/multi-agent-sandbox-tools ) for precedence details.
---
## Session
```json5
{
session: {
scope: "per-sender",
dmScope: "main", // main | per-peer | per-channel-peer | per-account-channel-peer
identityLinks: {
alice: ["telegram:123456789", "discord:987654321012345678"],
},
reset: {
mode: "daily", // daily | idle
atHour: 4,
idleMinutes: 60,
},
resetByType: {
thread: { mode: "daily", atHour: 4 },
direct: { mode: "idle", idleMinutes: 240 },
group: { mode: "idle", idleMinutes: 120 },
},
resetTriggers: ["/new", "/reset"],
store: "~/.openclaw/agents/{agentId}/sessions/sessions.json",
2026-02-25 23:53:43 +00:00
parentForkMaxTokens: 100000, // skip parent-thread fork above this token count (0 disables)
2026-02-11 10:44:34 -05:00
maintenance: {
mode: "warn", // warn | enforce
pruneAfter: "30d",
maxEntries: 500,
rotateBytes: "10mb",
2026-02-23 17:39:48 -05:00
resetArchiveRetention: "30d", // duration or false
maxDiskBytes: "500mb", // optional hard budget
highWaterBytes: "400mb", // optional cleanup target
2026-02-11 10:44:34 -05:00
},
2026-02-21 19:59:50 +01:00
threadBindings: {
enabled: true,
2026-02-27 10:02:39 +01:00
idleHours: 24, // default inactivity auto-unfocus in hours (`0` disables)
maxAgeHours: 0, // default hard max age in hours (`0` disables)
2026-02-21 19:59:50 +01:00
},
2026-02-11 10:44:34 -05:00
mainKey: "main", // legacy (runtime always uses "main")
agentToAgent: { maxPingPongTurns: 5 },
sendPolicy: {
rules: [{ action: "deny", match: { channel: "discord", chatType: "group" } }],
default: "allow",
},
},
}
```
< Accordion title = "Session field details" >
- **`dmScope` **: how DMs are grouped.
- `main` : all DMs share the main session.
- `per-peer` : isolate by sender id across channels.
- `per-channel-peer` : isolate per channel + sender (recommended for multi-user inboxes).
- `per-account-channel-peer` : isolate per account + channel + sender (recommended for multi-account).
- **`identityLinks` **: map canonical ids to provider-prefixed peers for cross-channel session sharing.
- **`reset` **: primary reset policy. `daily` resets at `atHour` local time; `idle` resets after `idleMinutes` . When both configured, whichever expires first wins.
- **`resetByType` **: per-type overrides (`direct` , `group` , `thread` ). Legacy `dm` accepted as alias for `direct` .
2026-02-25 23:53:43 +00:00
- **`parentForkMaxTokens` **: max parent-session `totalTokens` allowed when creating a forked thread session (default `100000` ).
- If parent `totalTokens` is above this value, OpenClaw starts a fresh thread session instead of inheriting parent transcript history.
- Set `0` to disable this guard and always allow parent forking.
2026-02-11 10:44:34 -05:00
- **`mainKey` **: legacy field. Runtime now always uses `"main"` for the main direct-chat bucket.
2026-02-15 02:41:30 +00:00
- **`sendPolicy` **: match by `channel` , `chatType` (`direct|group|channel` , with legacy `dm` alias), `keyPrefix` , or `rawKeyPrefix` . First deny wins.
2026-02-23 17:39:48 -05:00
- **`maintenance` **: session-store cleanup + retention controls.
- `mode` : `warn` emits warnings only; `enforce` applies cleanup.
- `pruneAfter` : age cutoff for stale entries (default `30d` ).
- `maxEntries` : maximum number of entries in `sessions.json` (default `500` ).
- `rotateBytes` : rotate `sessions.json` when it exceeds this size (default `10mb` ).
- `resetArchiveRetention` : retention for `*.reset.<timestamp>` transcript archives. Defaults to `pruneAfter` ; set `false` to disable.
- `maxDiskBytes` : optional sessions-directory disk budget. In `warn` mode it logs warnings; in `enforce` mode it removes oldest artifacts/sessions first.
- `highWaterBytes` : optional target after budget cleanup. Defaults to `80%` of `maxDiskBytes` .
2026-02-21 19:59:50 +01:00
- **`threadBindings` **: global defaults for thread-bound session features.
- `enabled` : master default switch (providers can override; Discord uses `channels.discord.threadBindings.enabled` )
2026-02-27 10:02:39 +01:00
- `idleHours` : default inactivity auto-unfocus in hours (`0` disables; providers can override)
- `maxAgeHours` : default hard max age in hours (`0` disables; providers can override)
2026-02-11 10:44:34 -05:00
< / Accordion >
---
## Messages
```json5
{
messages: {
responsePrefix: "🦞", // or "auto"
ackReaction: "👀",
ackReactionScope: "group-mentions", // group-mentions | group-all | direct | all
removeAckAfterReply: false,
queue: {
mode: "collect", // steer | followup | collect | steer-backlog | steer+backlog | queue | interrupt
debounceMs: 1000,
cap: 20,
drop: "summarize", // old | new | summarize
byChannel: {
whatsapp: "collect",
telegram: "collect",
},
},
inbound: {
debounceMs: 2000, // 0 disables
byChannel: {
whatsapp: 5000,
slack: 1500,
},
},
},
}
```
### Response prefix
Per-channel/account overrides: `channels.<channel>.responsePrefix` , `channels.<channel>.accounts.<id>.responsePrefix` .
Resolution (most specific wins): account → channel → global. `""` disables and stops cascade. `"auto"` derives `[{identity.name}]` .
**Template variables:**
| Variable | Description | Example |
| ----------------- | ---------------------- | --------------------------- |
| `{model}` | Short model name | `claude-opus-4-6` |
| `{modelFull}` | Full model identifier | `anthropic/claude-opus-4-6` |
| `{provider}` | Provider name | `anthropic` |
| `{thinkingLevel}` | Current thinking level | `high` , `low` , `off` |
| `{identity.name}` | Agent identity name | (same as `"auto"` ) |
Variables are case-insensitive. `{think}` is an alias for `{thinkingLevel}` .
### Ack reaction
- Defaults to active agent's `identity.emoji` , otherwise `"👀"` . Set `""` to disable.
2026-02-15 11:29:51 -06:00
- Per-channel overrides: `channels.<channel>.ackReaction` , `channels.<channel>.accounts.<id>.ackReaction` .
- Resolution order: account → channel → `messages.ackReaction` → identity fallback.
2026-02-11 10:44:34 -05:00
- Scope: `group-mentions` (default), `group-all` , `direct` , `all` .
- `removeAckAfterReply` : removes ack after reply (Slack/Discord/Telegram/Google Chat only).
### Inbound debounce
Batches rapid text-only messages from the same sender into a single agent turn. Media/attachments flush immediately. Control commands bypass debouncing.
### TTS (text-to-speech)
```json5
{
messages: {
tts: {
auto: "always", // off | always | inbound | tagged
mode: "final", // final | all
provider: "elevenlabs",
summaryModel: "openai/gpt-4.1-mini",
modelOverrides: { enabled: true },
maxTextLength: 4000,
timeoutMs: 30000,
prefsPath: "~/.openclaw/settings/tts.json",
elevenlabs: {
apiKey: "elevenlabs_api_key",
baseUrl: "https://api.elevenlabs.io",
voiceId: "voice_id",
modelId: "eleven_multilingual_v2",
seed: 42,
applyTextNormalization: "auto",
languageCode: "en",
voiceSettings: {
stability: 0.5,
similarityBoost: 0.75,
style: 0.0,
useSpeakerBoost: true,
speed: 1.0,
},
},
openai: {
apiKey: "openai_api_key",
2026-03-05 16:57:40 -05:00
baseUrl: "https://api.openai.com/v1",
2026-02-11 10:44:34 -05:00
model: "gpt-4o-mini-tts",
voice: "alloy",
},
},
},
}
```
- `auto` controls auto-TTS. `/tts off|always|inbound|tagged` overrides per session.
- `summaryModel` overrides `agents.defaults.model.primary` for auto-summary.
2026-02-21 13:15:53 +01:00
- `modelOverrides` is enabled by default; `modelOverrides.allowProvider` defaults to `false` (opt-in).
2026-02-11 10:44:34 -05:00
- API keys fall back to `ELEVENLABS_API_KEY` /`XI_API_KEY` and `OPENAI_API_KEY` .
2026-03-05 16:57:40 -05:00
- `openai.baseUrl` overrides the OpenAI TTS endpoint. Resolution order is config, then `OPENAI_TTS_BASE_URL` , then `https://api.openai.com/v1` .
- When `openai.baseUrl` points to a non-OpenAI endpoint, OpenClaw treats it as an OpenAI-compatible TTS server and relaxes model/voice validation.
2026-02-11 10:44:34 -05:00
---
## Talk
Defaults for Talk mode (macOS/iOS/Android).
```json5
{
talk: {
voiceId: "elevenlabs_voice_id",
voiceAliases: {
Clawd: "EXAVITQu4vr4xnSDxMaL",
Roger: "CwhRBWXzGAHq8TQ4Fs17",
},
modelId: "eleven_v3",
outputFormat: "mp3_44100_128",
apiKey: "elevenlabs_api_key",
2026-03-08 17:58:15 +11:00
silenceTimeoutMs: 1500,
2026-02-11 10:44:34 -05:00
interruptOnSpeech: true,
},
}
```
- Voice IDs fall back to `ELEVENLABS_VOICE_ID` or `SAG_VOICE_ID` .
2026-03-02 20:58:20 -06:00
- `apiKey` and `providers.*.apiKey` accept plaintext strings or SecretRef objects.
- `ELEVENLABS_API_KEY` fallback applies only when no Talk API key is configured.
2026-02-11 10:44:34 -05:00
- `voiceAliases` lets Talk directives use friendly names.
2026-03-08 17:58:15 +11:00
- `silenceTimeoutMs` controls how long Talk mode waits after user silence before it sends the transcript. Unset keeps the platform default pause window (`700` ms on macOS and Android, `900` ms on iOS).
2026-02-11 10:44:34 -05:00
---
## Tools
### Tool profiles
`tools.profile` sets a base allowlist before `tools.allow` /`tools.deny` :
2026-03-07 16:40:51 +00:00
Local onboarding defaults new local configs to `tools.profile: "coding"` when unset (existing explicit profiles are preserved).
2026-03-02 18:15:43 +00:00
2026-02-11 10:44:34 -05:00
| Profile | Includes |
| ----------- | ----------------------------------------------------------------------------------------- |
| `minimal` | `session_status` only |
| `coding` | `group:fs` , `group:runtime` , `group:sessions` , `group:memory` , `image` |
| `messaging` | `group:messaging` , `sessions_list` , `sessions_history` , `sessions_send` , `session_status` |
| `full` | No restriction (same as unset) |
### Tool groups
| Group | Tools |
| ------------------ | ---------------------------------------------------------------------------------------- |
| `group:runtime` | `exec` , `process` (`bash` is accepted as an alias for `exec` ) |
| `group:fs` | `read` , `write` , `edit` , `apply_patch` |
| `group:sessions` | `sessions_list` , `sessions_history` , `sessions_send` , `sessions_spawn` , `session_status` |
| `group:memory` | `memory_search` , `memory_get` |
| `group:web` | `web_search` , `web_fetch` |
| `group:ui` | `browser` , `canvas` |
| `group:automation` | `cron` , `gateway` |
| `group:messaging` | `message` |
| `group:nodes` | `nodes` |
| `group:openclaw` | All built-in tools (excludes provider plugins) |
### `tools.allow` / `tools.deny`
Global tool allow/deny policy (deny wins). Case-insensitive, supports `*` wildcards. Applied even when Docker sandbox is off.
```json5
{
tools: { deny: ["browser", "canvas"] },
}
```
### `tools.byProvider`
Further restrict tools for specific providers or models. Order: base profile → provider profile → allow/deny.
```json5
{
tools: {
profile: "coding",
byProvider: {
"google-antigravity": { profile: "minimal" },
"openai/gpt-5.2": { allow: ["group:fs", "sessions_list"] },
},
},
}
```
### `tools.elevated`
Controls elevated (host) exec access:
```json5
{
tools: {
elevated: {
enabled: true,
allowFrom: {
whatsapp: ["+15555550123"],
2026-02-24 01:01:51 +00:00
discord: ["1234567890123", "987654321098765432"],
2026-02-11 10:44:34 -05:00
},
},
},
}
```
- Per-agent override (`agents.list[].tools.elevated` ) can only further restrict.
- `/elevated on|off|ask|full` stores state per session; inline directives apply to single message.
- Elevated `exec` runs on the host, bypasses sandboxing.
### `tools.exec`
```json5
{
tools: {
exec: {
backgroundMs: 10000,
timeoutSec: 1800,
cleanupMs: 1800000,
notifyOnExit: true,
2026-02-14 18:36:35 -05:00
notifyOnExitEmptySuccess: false,
2026-02-11 10:44:34 -05:00
applyPatch: {
enabled: false,
allowModels: ["gpt-5.2"],
},
},
},
}
```
2026-02-17 00:17:01 +01:00
### `tools.loopDetection`
Tool-loop safety checks are **disabled by default** . Set `enabled: true` to activate detection.
Settings can be defined globally in `tools.loopDetection` and overridden per-agent at `agents.list[].tools.loopDetection` .
```json5
{
tools: {
loopDetection: {
enabled: true,
historySize: 30,
warningThreshold: 10,
criticalThreshold: 20,
globalCircuitBreakerThreshold: 30,
detectors: {
genericRepeat: true,
knownPollNoProgress: true,
pingPong: true,
},
},
},
}
```
- `historySize` : max tool-call history retained for loop analysis.
- `warningThreshold` : repeating no-progress pattern threshold for warnings.
- `criticalThreshold` : higher repeating threshold for blocking critical loops.
- `globalCircuitBreakerThreshold` : hard stop threshold for any no-progress run.
- `detectors.genericRepeat` : warn on repeated same-tool/same-args calls.
- `detectors.knownPollNoProgress` : warn/block on known poll tools (`process.poll` , `command_status` , etc.).
- `detectors.pingPong` : warn/block on alternating no-progress pair patterns.
- If `warningThreshold >= criticalThreshold` or `criticalThreshold >= globalCircuitBreakerThreshold` , validation fails.
2026-02-11 10:44:34 -05:00
### `tools.web`
```json5
{
tools: {
web: {
search: {
enabled: true,
apiKey: "brave_api_key", // or BRAVE_API_KEY env
maxResults: 5,
timeoutSeconds: 30,
cacheTtlMinutes: 15,
},
fetch: {
enabled: true,
maxChars: 50000,
maxCharsCap: 50000,
timeoutSeconds: 30,
cacheTtlMinutes: 15,
userAgent: "custom-ua",
},
},
},
}
```
### `tools.media`
Configures inbound media understanding (image/audio/video):
```json5
{
tools: {
media: {
concurrency: 2,
audio: {
enabled: true,
maxBytes: 20971520,
scope: {
default: "deny",
rules: [{ action: "allow", match: { chatType: "direct" } }],
},
models: [
{ provider: "openai", model: "gpt-4o-mini-transcribe" },
{ type: "cli", command: "whisper", args: ["--model", "base", "{{MediaPath}}"] },
],
},
video: {
enabled: true,
maxBytes: 52428800,
models: [{ provider: "google", model: "gemini-3-flash-preview" }],
},
},
},
}
```
< Accordion title = "Media model entry fields" >
**Provider entry** (`type: "provider"` or omitted):
- `provider` : API provider id (`openai` , `anthropic` , `google` /`gemini` , `groq` , etc.)
- `model` : model id override
2026-03-02 20:58:20 -06:00
- `profile` / `preferredProfile` : `auth-profiles.json` profile selection
2026-02-11 10:44:34 -05:00
**CLI entry** (`type: "cli"` ):
- `command` : executable to run
- `args` : templated args (supports `{{MediaPath}}` , `{{Prompt}}` , `{{MaxChars}}` , etc.)
**Common fields:**
- `capabilities` : optional list (`image` , `audio` , `video` ). Defaults: `openai` /`anthropic` /`minimax` → image, `google` → image+audio+video, `groq` → audio.
- `prompt` , `maxChars` , `maxBytes` , `timeoutSeconds` , `language` : per-entry overrides.
- Failures fall back to the next entry.
2026-03-02 20:58:20 -06:00
Provider auth follows standard order: `auth-profiles.json` → env vars → `models.providers.*.apiKey` .
2026-02-11 10:44:34 -05:00
< / Accordion >
### `tools.agentToAgent`
```json5
{
tools: {
agentToAgent: {
enabled: false,
allow: ["home", "work"],
},
},
}
```
2026-02-16 03:43:51 +01:00
### `tools.sessions`
Controls which sessions can be targeted by the session tools (`sessions_list` , `sessions_history` , `sessions_send` ).
Default: `tree` (current session + sessions spawned by it, such as subagents).
```json5
{
tools: {
sessions: {
// "self" | "tree" | "agent" | "all"
visibility: "tree",
},
},
}
```
Notes:
- `self` : only the current session key.
- `tree` : current session + sessions spawned by the current session (subagents).
- `agent` : any session belonging to the current agent id (can include other users if you run per-sender sessions under the same agent id).
- `all` : any session. Cross-agent targeting still requires `tools.agentToAgent` .
- Sandbox clamp: when the current session is sandboxed and `agents.defaults.sandbox.sessionToolsVisibility="spawned"` , visibility is forced to `tree` even if `tools.sessions.visibility="all"` .
sessions_spawn: inline attachments with redaction, lifecycle cleanup, and docs (#16761)
Add inline file attachment support for sessions_spawn (subagent runtime only):
- Schema: attachments[] (name, content, encoding, mimeType) and attachAs.mountPath hint
- Materialization: files written to .openclaw/attachments/<uuid>/ with manifest.json
- Validation: strict base64 decode, filename checks, size limits, duplicate detection
- Transcript redaction: sanitizeToolCallInputs redacts attachment content from persisted transcripts
- Lifecycle cleanup: safeRemoveAttachmentsDir with symlink-safe path containment check
- Config: tools.sessions_spawn.attachments (enabled, maxFiles, maxFileBytes, maxTotalBytes, retainOnSessionKeep)
- Registry: attachmentsDir/attachmentsRootDir/retainAttachmentsOnKeep on SubagentRunRecord
- ACP rejection: attachments rejected for runtime=acp with clear error message
- Docs: updated tools/index.md, concepts/session-tool.md, configuration-reference.md
- Tests: 85 new/updated tests across 5 test files
Fixes:
- Guard fs.rm in materialization catch block with try/catch (review concern #1)
- Remove unreachable fallback in safeRemoveAttachmentsDir (review concern #7)
- Move attachment cleanup out of retry path to avoid timing issues with announce loop
Co-authored-by: Tyler Yust <TYTYYUST@YAHOO.COM>
Co-authored-by: napetrov <napetrov@users.noreply.github.com>
2026-03-01 21:33:51 -08:00
### `tools.sessions_spawn`
Controls inline attachment support for `sessions_spawn` .
```json5
{
tools: {
sessions_spawn: {
attachments: {
enabled: false, // opt-in: set true to allow inline file attachments
maxTotalBytes: 5242880, // 5 MB total across all files
maxFiles: 50,
maxFileBytes: 1048576, // 1 MB per file
retainOnSessionKeep: false, // keep attachments when cleanup="keep"
},
},
},
}
```
Notes:
- Attachments are only supported for `runtime: "subagent"` . ACP runtime rejects them.
- Files are materialized into the child workspace at `.openclaw/attachments/<uuid>/` with a `.manifest.json` .
- Attachment content is automatically redacted from transcript persistence.
- Base64 inputs are validated with strict alphabet/padding checks and a pre-decode size guard.
- File permissions are `0700` for directories and `0600` for files.
- Cleanup follows the `cleanup` policy: `delete` always removes attachments; `keep` retains them only when `retainOnSessionKeep: true` .
2026-02-11 10:44:34 -05:00
### `tools.subagents`
```json5
{
agents: {
defaults: {
subagents: {
2026-03-03 00:02:25 +00:00
model: "minimax/MiniMax-M2.5",
2026-02-11 10:44:34 -05:00
maxConcurrent: 1,
2026-02-24 04:22:25 +00:00
runTimeoutSeconds: 900,
2026-02-11 10:44:34 -05:00
archiveAfterMinutes: 60,
},
},
},
}
```
- `model` : default model for spawned sub-agents. If omitted, sub-agents inherit the caller's model.
2026-02-24 04:22:25 +00:00
- `runTimeoutSeconds` : default timeout (seconds) for `sessions_spawn` when the tool call omits `runTimeoutSeconds` . `0` means no timeout.
2026-02-11 10:44:34 -05:00
- Per-subagent tool policy: `tools.subagents.tools.allow` / `tools.subagents.tools.deny` .
---
## Custom providers and base URLs
OpenClaw uses the pi-coding-agent model catalog. Add custom providers via `models.providers` in config or `~/.openclaw/agents/<agentId>/agent/models.json` .
```json5
{
models: {
mode: "merge", // merge (default) | replace
providers: {
"custom-proxy": {
baseUrl: "http://localhost:4000/v1",
apiKey: "LITELLM_KEY",
api: "openai-completions", // openai-completions | openai-responses | anthropic-messages | google-generative-ai
models: [
{
id: "llama-3.1-8b",
name: "Llama 3.1 8B",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 128000,
maxTokens: 32000,
},
],
},
},
},
}
```
- Use `authHeader: true` + `headers` for custom auth needs.
- Override agent config root with `OPENCLAW_AGENT_DIR` (or `PI_CODING_AGENT_DIR` ).
2026-02-26 16:46:36 +08:00
- Merge precedence for matching provider IDs:
2026-03-07 11:28:39 -06:00
- Non-empty agent `models.json` `baseUrl` values win.
- Non-empty agent `apiKey` values win only when that provider is not SecretRef-managed in current config/auth-profile context.
- SecretRef-managed provider `apiKey` values are refreshed from source markers (`ENV_VAR_NAME` for env refs, `secretref-managed` for file/exec refs) instead of persisting resolved secrets.
2026-02-26 16:46:36 +08:00
- Empty or missing agent `apiKey` /`baseUrl` fall back to `models.providers` in config.
2026-02-27 17:20:47 -08:00
- Matching model `contextWindow` /`maxTokens` use the higher value between explicit config and implicit catalog values.
2026-02-26 16:46:36 +08:00
- Use `models.mode: "replace"` when you want config to fully rewrite `models.json` .
2026-02-11 10:44:34 -05:00
2026-02-26 22:35:43 -05:00
### Provider field details
- `models.mode` : provider catalog behavior (`merge` or `replace` ).
- `models.providers` : custom provider map keyed by provider id.
- `models.providers.*.api` : request adapter (`openai-completions` , `openai-responses` , `anthropic-messages` , `google-generative-ai` , etc).
- `models.providers.*.apiKey` : provider credential (prefer SecretRef/env substitution).
- `models.providers.*.auth` : auth strategy (`api-key` , `token` , `oauth` , `aws-sdk` ).
2026-02-27 17:20:47 -08:00
- `models.providers.*.injectNumCtxForOpenAICompat` : for Ollama + `openai-completions` , inject `options.num_ctx` into requests (default: `true` ).
2026-02-26 22:35:43 -05:00
- `models.providers.*.authHeader` : force credential transport in the `Authorization` header when required.
- `models.providers.*.baseUrl` : upstream API base URL.
- `models.providers.*.headers` : extra static headers for proxy/tenant routing.
- `models.providers.*.models` : explicit provider model catalog entries.
2026-03-03 07:13:49 +05:30
- `models.providers.*.models.*.compat.supportsDeveloperRole` : optional compatibility hint. For `api: "openai-completions"` with a non-empty non-native `baseUrl` (host not `api.openai.com` ), OpenClaw forces this to `false` at runtime. Empty/omitted `baseUrl` keeps default OpenAI behavior.
2026-02-26 22:35:43 -05:00
- `models.bedrockDiscovery` : Bedrock auto-discovery settings root.
- `models.bedrockDiscovery.enabled` : turn discovery polling on/off.
- `models.bedrockDiscovery.region` : AWS region for discovery.
- `models.bedrockDiscovery.providerFilter` : optional provider-id filter for targeted discovery.
- `models.bedrockDiscovery.refreshInterval` : polling interval for discovery refresh.
- `models.bedrockDiscovery.defaultContextWindow` : fallback context window for discovered models.
- `models.bedrockDiscovery.defaultMaxTokens` : fallback max output tokens for discovered models.
2026-02-11 10:44:34 -05:00
### Provider examples
< Accordion title = "Cerebras (GLM 4.6 / 4.7)" >
```json5
{
env: { CEREBRAS_API_KEY: "sk-..." },
agents: {
defaults: {
model: {
primary: "cerebras/zai-glm-4.7",
fallbacks: ["cerebras/zai-glm-4.6"],
},
models: {
"cerebras/zai-glm-4.7": { alias: "GLM 4.7 (Cerebras)" },
"cerebras/zai-glm-4.6": { alias: "GLM 4.6 (Cerebras)" },
},
},
},
models: {
mode: "merge",
providers: {
cerebras: {
baseUrl: "https://api.cerebras.ai/v1",
apiKey: "${CEREBRAS_API_KEY}",
api: "openai-completions",
models: [
{ id: "zai-glm-4.7", name: "GLM 4.7 (Cerebras)" },
{ id: "zai-glm-4.6", name: "GLM 4.6 (Cerebras)" },
],
},
},
},
}
```
Use `cerebras/zai-glm-4.7` for Cerebras; `zai/glm-4.7` for Z.AI direct.
< / Accordion >
< Accordion title = "OpenCode Zen" >
```json5
{
agents: {
defaults: {
model: { primary: "opencode/claude-opus-4-6" },
models: { "opencode/claude-opus-4-6": { alias: "Opus" } },
},
},
}
```
Set `OPENCODE_API_KEY` (or `OPENCODE_ZEN_API_KEY` ). Shortcut: `openclaw onboard --auth-choice opencode-zen` .
< / Accordion >
< Accordion title = "Z.AI (GLM-4.7)" >
```json5
{
agents: {
defaults: {
model: { primary: "zai/glm-4.7" },
models: { "zai/glm-4.7": {} },
},
},
}
```
Set `ZAI_API_KEY` . `z.ai/*` and `z-ai/*` are accepted aliases. Shortcut: `openclaw onboard --auth-choice zai-api-key` .
- General endpoint: `https://api.z.ai/api/paas/v4`
- Coding endpoint (default): `https://api.z.ai/api/coding/paas/v4`
- For the general endpoint, define a custom provider with the base URL override.
< / Accordion >
< Accordion title = "Moonshot AI (Kimi)" >
```json5
{
env: { MOONSHOT_API_KEY: "sk-..." },
agents: {
defaults: {
model: { primary: "moonshot/kimi-k2.5" },
models: { "moonshot/kimi-k2.5": { alias: "Kimi K2.5" } },
},
},
models: {
mode: "merge",
providers: {
moonshot: {
baseUrl: "https://api.moonshot.ai/v1",
apiKey: "${MOONSHOT_API_KEY}",
api: "openai-completions",
models: [
{
id: "kimi-k2.5",
name: "Kimi K2.5",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 256000,
maxTokens: 8192,
},
],
},
},
},
}
```
For the China endpoint: `baseUrl: "https://api.moonshot.cn/v1"` or `openclaw onboard --auth-choice moonshot-api-key-cn` .
< / Accordion >
< Accordion title = "Kimi Coding" >
```json5
{
env: { KIMI_API_KEY: "sk-..." },
agents: {
defaults: {
model: { primary: "kimi-coding/k2p5" },
models: { "kimi-coding/k2p5": { alias: "Kimi K2.5" } },
},
},
}
```
Anthropic-compatible, built-in provider. Shortcut: `openclaw onboard --auth-choice kimi-code-api-key` .
< / Accordion >
< Accordion title = "Synthetic (Anthropic-compatible)" >
```json5
{
env: { SYNTHETIC_API_KEY: "sk-..." },
agents: {
defaults: {
2026-03-03 00:02:25 +00:00
model: { primary: "synthetic/hf:MiniMaxAI/MiniMax-M2.5" },
models: { "synthetic/hf:MiniMaxAI/MiniMax-M2.5": { alias: "MiniMax M2.5" } },
2026-02-11 10:44:34 -05:00
},
},
models: {
mode: "merge",
providers: {
synthetic: {
baseUrl: "https://api.synthetic.new/anthropic",
apiKey: "${SYNTHETIC_API_KEY}",
api: "anthropic-messages",
models: [
{
2026-03-03 00:02:25 +00:00
id: "hf:MiniMaxAI/MiniMax-M2.5",
name: "MiniMax M2.5",
2026-02-11 10:44:34 -05:00
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 192000,
maxTokens: 65536,
},
],
},
},
},
}
```
Base URL should omit `/v1` (Anthropic client appends it). Shortcut: `openclaw onboard --auth-choice synthetic-api-key` .
< / Accordion >
2026-03-03 00:02:25 +00:00
< Accordion title = "MiniMax M2.5 (direct)" >
2026-02-11 10:44:34 -05:00
```json5
{
agents: {
defaults: {
2026-03-03 00:02:25 +00:00
model: { primary: "minimax/MiniMax-M2.5" },
2026-02-11 10:44:34 -05:00
models: {
2026-03-03 00:02:25 +00:00
"minimax/MiniMax-M2.5": { alias: "Minimax" },
2026-02-11 10:44:34 -05:00
},
},
},
models: {
mode: "merge",
providers: {
minimax: {
baseUrl: "https://api.minimax.io/anthropic",
apiKey: "${MINIMAX_API_KEY}",
api: "anthropic-messages",
models: [
{
2026-03-03 00:02:25 +00:00
id: "MiniMax-M2.5",
name: "MiniMax M2.5",
2026-02-11 10:44:34 -05:00
reasoning: false,
input: ["text"],
cost: { input: 15, output: 60, cacheRead: 2, cacheWrite: 10 },
contextWindow: 200000,
maxTokens: 8192,
},
],
},
},
},
}
```
Set `MINIMAX_API_KEY` . Shortcut: `openclaw onboard --auth-choice minimax-api` .
< / Accordion >
< Accordion title = "Local models (LM Studio)" >
2026-03-03 00:02:25 +00:00
See [Local Models ](/gateway/local-models ). TL;DR: run MiniMax M2.5 via LM Studio Responses API on serious hardware; keep hosted models merged for fallback.
2026-02-11 10:44:34 -05:00
< / Accordion >
---
## Skills
```json5
{
skills: {
allowBundled: ["gemini", "peekaboo"],
load: {
extraDirs: ["~/Projects/agent-scripts/skills"],
},
install: {
preferBrew: true,
nodeManager: "npm", // npm | pnpm | yarn
},
entries: {
"nano-banana-pro": {
2026-02-25 17:58:10 -06:00
apiKey: { source: "env", provider: "default", id: "GEMINI_API_KEY" }, // or plaintext string
2026-02-11 10:44:34 -05:00
env: { GEMINI_API_KEY: "GEMINI_KEY_HERE" },
},
peekaboo: { enabled: true },
sag: { enabled: false },
},
},
}
```
- `allowBundled` : optional allowlist for bundled skills only (managed/workspace skills unaffected).
- `entries.<skillKey>.enabled: false` disables a skill even if bundled/installed.
2026-02-24 16:26:51 -06:00
- `entries.<skillKey>.apiKey` : convenience for skills declaring a primary env var (plaintext string or SecretRef object).
2026-02-11 10:44:34 -05:00
---
## Plugins
```json5
{
plugins: {
enabled: true,
allow: ["voice-call"],
deny: [],
load: {
paths: ["~/Projects/oss/voice-call-extension"],
},
entries: {
"voice-call": {
enabled: true,
2026-03-05 18:15:54 -05:00
hooks: {
allowPromptInjection: false,
},
2026-02-11 10:44:34 -05:00
config: { provider: "twilio" },
},
},
},
}
```
- Loaded from `~/.openclaw/extensions` , `<workspace>/.openclaw/extensions` , plus `plugins.load.paths` .
- **Config changes require a gateway restart.**
- `allow` : optional allowlist (only listed plugins load). `deny` wins.
2026-02-26 22:35:43 -05:00
- `plugins.entries.<id>.apiKey` : plugin-level API key convenience field (when supported by the plugin).
- `plugins.entries.<id>.env` : plugin-scoped env var map.
2026-03-05 18:15:54 -05:00
- `plugins.entries.<id>.hooks.allowPromptInjection` : when `false` , core blocks `before_prompt_build` and ignores prompt-mutating fields from legacy `before_agent_start` , while preserving legacy `modelOverride` and `providerOverride` .
2026-02-26 22:35:43 -05:00
- `plugins.entries.<id>.config` : plugin-defined config object (validated by plugin schema).
- `plugins.slots.memory` : pick the active memory plugin id, or `"none"` to disable memory plugins.
2026-03-06 08:53:14 -05:00
- `plugins.slots.contextEngine` : pick the active context engine plugin id; defaults to `"legacy"` unless you install and select another engine.
2026-02-26 22:35:43 -05:00
- `plugins.installs` : CLI-managed install metadata used by `openclaw plugins update` .
- Includes `source` , `spec` , `sourcePath` , `installPath` , `version` , `resolvedName` , `resolvedVersion` , `resolvedSpec` , `integrity` , `shasum` , `resolvedAt` , `installedAt` .
- Treat `plugins.installs.*` as managed state; prefer CLI commands over manual edits.
2026-02-11 10:44:34 -05:00
See [Plugins ](/tools/plugin ).
---
## Browser
```json5
{
browser: {
enabled: true,
evaluateEnabled: true,
defaultProfile: "chrome",
2026-02-24 01:51:44 +00:00
ssrfPolicy: {
dangerouslyAllowPrivateNetwork: true, // default trusted-network mode
// allowPrivateNetwork: true, // legacy alias
// hostnameAllowlist: ["*.example.com", "example.com"],
// allowedHostnames: ["localhost"],
},
2026-02-11 10:44:34 -05:00
profiles: {
openclaw: { cdpPort: 18800, color: "#FF4500 " },
work: { cdpPort: 18801, color: "#0066CC " },
remote: { cdpUrl: "http://10.0.0.42:9222", color: "#00AA00 " },
},
color: "#FF4500 ",
// headless: false,
// noSandbox: false,
2026-03-02 11:28:27 -08:00
// extraArgs: [],
2026-02-11 10:44:34 -05:00
// executablePath: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
// attachOnly: false,
},
}
```
- `evaluateEnabled: false` disables `act:evaluate` and `wait --fn` .
2026-02-24 01:51:44 +00:00
- `ssrfPolicy.dangerouslyAllowPrivateNetwork` defaults to `true` when unset (trusted-network model).
- Set `ssrfPolicy.dangerouslyAllowPrivateNetwork: false` for strict public-only browser navigation.
- `ssrfPolicy.allowPrivateNetwork` remains supported as a legacy alias.
- In strict mode, use `ssrfPolicy.hostnameAllowlist` and `ssrfPolicy.allowedHostnames` for explicit exceptions.
2026-02-11 10:44:34 -05:00
- Remote profiles are attach-only (start/stop/reset disabled).
- Auto-detect order: default browser if Chromium-based → Chrome → Brave → Edge → Chromium → Chrome Canary.
- Control service: loopback only (port derived from `gateway.port` , default `18791` ).
2026-03-02 11:28:27 -08:00
- `extraArgs` appends extra launch flags to local Chromium startup (for example
`--disable-gpu` , window sizing, or debug flags).
2026-02-11 10:44:34 -05:00
---
## UI
```json5
{
ui: {
seamColor: "#FF4500 ",
assistant: {
name: "OpenClaw",
avatar: "CB", // emoji, short text, image URL, or data URI
},
},
}
```
- `seamColor` : accent color for native app UI chrome (Talk Mode bubble tint, etc.).
- `assistant` : Control UI identity override. Falls back to active agent identity.
---
## Gateway
```json5
{
gateway: {
mode: "local", // local | remote
port: 18789,
bind: "loopback",
auth: {
2026-02-19 02:35:50 -05:00
mode: "token", // none | token | password | trusted-proxy
2026-02-11 10:44:34 -05:00
token: "your-token",
// password: "your-password", // or OPENCLAW_GATEWAY_PASSWORD
2026-02-14 12:44:25 +01:00
// trustedProxy: { userHeader: "x-forwarded-user" }, // for mode=trusted-proxy; see /gateway/trusted-proxy-auth
2026-02-11 10:44:34 -05:00
allowTailscale: true,
2026-02-13 15:32:38 +01:00
rateLimit: {
maxAttempts: 10,
windowMs: 60000,
lockoutMs: 300000,
exemptLoopback: true,
},
2026-02-11 10:44:34 -05:00
},
tailscale: {
mode: "off", // off | serve | funnel
resetOnExit: false,
},
controlUi: {
enabled: true,
basePath: "/openclaw",
// root: "dist/control-ui",
2026-02-24 01:52:15 +00:00
// allowedOrigins: ["https://control.example.com"], // required for non-loopback Control UI
// dangerouslyAllowHostHeaderOriginFallback: false, // dangerous Host-header origin fallback mode
2026-02-11 10:44:34 -05:00
// allowInsecureAuth: false,
// dangerouslyDisableDeviceAuth: false,
},
remote: {
url: "ws://gateway.tailnet:18789",
transport: "ssh", // ssh | direct
token: "your-token",
// password: "your-password",
},
trustedProxies: ["10.0.0.1"],
2026-02-21 13:32:25 +01:00
// Optional. Default false.
allowRealIpFallback: false,
2026-02-13 14:28:50 +01:00
tools: {
// Additional /tools/invoke HTTP denies
deny: ["browser"],
// Remove tools from the default HTTP deny list
allow: ["gateway"],
},
2026-02-11 10:44:34 -05:00
},
}
```
< Accordion title = "Gateway field details" >
- `mode` : `local` (run gateway) or `remote` (connect to remote gateway). Gateway refuses to start unless `local` .
- `port` : single multiplexed port for WS + HTTP. Precedence: `--port` > `OPENCLAW_GATEWAY_PORT` > `gateway.port` > `18789` .
- `bind` : `auto` , `loopback` (default), `lan` (`0.0.0.0` ), `tailnet` (Tailscale IP only), or `custom` .
2026-03-02 09:15:27 +05:30
- **Legacy bind aliases**: use bind mode values in `gateway.bind` (`auto` , `loopback` , `lan` , `tailnet` , `custom` ), not host aliases (`0.0.0.0` , `127.0.0.1` , `localhost` , `::` , `::1` ).
- **Docker note**: the default `loopback` bind listens on `127.0.0.1` inside the container. With Docker bridge networking (`-p 18789:18789` ), traffic arrives on `eth0` , so the gateway is unreachable. Use `--network host` , or set `bind: "lan"` (or `bind: "custom"` with `customBindHost: "0.0.0.0"` ) to listen on all interfaces.
2026-02-11 10:44:34 -05:00
- **Auth**: required by default. Non-loopback binds require a shared token/password. Onboarding wizard generates a token by default.
2026-03-05 12:53:56 -06:00
- If both `gateway.auth.token` and `gateway.auth.password` are configured (including SecretRefs), set `gateway.auth.mode` explicitly to `token` or `password` . Startup and service install/repair flows fail when both are configured and mode is unset.
2026-02-26 22:35:43 -05:00
- `gateway.auth.mode: "none"` : explicit no-auth mode. Use only for trusted local loopback setups; this is intentionally not offered by onboarding prompts.
- `gateway.auth.mode: "trusted-proxy"` : delegate auth to an identity-aware reverse proxy and trust identity headers from `gateway.trustedProxies` (see [Trusted Proxy Auth ](/gateway/trusted-proxy-auth )).
- `gateway.auth.allowTailscale` : when `true` , Tailscale Serve identity headers can satisfy Control UI/WebSocket auth (verified via `tailscale whois` ); HTTP API endpoints still require token/password auth. This tokenless flow assumes the gateway host is trusted. Defaults to `true` when `tailscale.mode = "serve"` .
- `gateway.auth.rateLimit` : optional failed-auth limiter. Applies per client IP and per auth scope (shared-secret and device-token are tracked independently). Blocked attempts return `429` + `Retry-After` .
- `gateway.auth.rateLimit.exemptLoopback` defaults to `true` ; set `false` when you intentionally want localhost traffic rate-limited too (for test setups or strict proxy deployments).
2026-02-26 01:22:28 +01:00
- Browser-origin WS auth attempts are always throttled with loopback exemption disabled (defense-in-depth against browser-based localhost brute force).
2026-02-11 10:44:34 -05:00
- `tailscale.mode` : `serve` (tailnet only, loopback bind) or `funnel` (public, requires auth).
2026-02-26 01:22:28 +01:00
- `controlUi.allowedOrigins` : explicit browser-origin allowlist for Gateway WebSocket connects. Required when browser clients are expected from non-loopback origins.
2026-02-24 01:52:15 +00:00
- `controlUi.dangerouslyAllowHostHeaderOriginFallback` : dangerous mode that enables Host-header origin fallback for deployments that intentionally rely on Host-header origin policy.
2026-02-11 10:44:34 -05:00
- `remote.transport` : `ssh` (default) or `direct` (ws/wss). For `direct` , `remote.url` must be `ws://` or `wss://` .
2026-03-01 23:49:45 -05:00
- `OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1` : client-side break-glass override that allows plaintext `ws://` to trusted private-network IPs; default remains loopback-only for plaintext.
2026-02-26 15:59:39 +01:00
- `gateway.remote.token` / `.password` are remote-client credential fields. They do not configure gateway auth by themselves.
- Local gateway call paths can use `gateway.remote.*` as fallback when `gateway.auth.*` is unset.
2026-02-11 10:44:34 -05:00
- `trustedProxies` : reverse proxy IPs that terminate TLS. Only list proxies you control.
2026-02-21 13:32:25 +01:00
- `allowRealIpFallback` : when `true` , the gateway accepts `X-Real-IP` if `X-Forwarded-For` is missing. Default `false` for fail-closed behavior.
2026-02-13 14:28:50 +01:00
- `gateway.tools.deny` : extra tool names blocked for HTTP `POST /tools/invoke` (extends default deny list).
- `gateway.tools.allow` : remove tool names from the default HTTP deny list.
2026-02-11 10:44:34 -05:00
< / Accordion >
### OpenAI-compatible endpoints
- Chat Completions: disabled by default. Enable with `gateway.http.endpoints.chatCompletions.enabled: true` .
- Responses API: `gateway.http.endpoints.responses.enabled` .
2026-02-13 01:38:15 +01:00
- Responses URL-input hardening:
- `gateway.http.endpoints.responses.maxUrlParts`
- `gateway.http.endpoints.responses.files.urlAllowlist`
- `gateway.http.endpoints.responses.images.urlAllowlist`
2026-02-23 19:47:09 +00:00
- Optional response hardening header:
- `gateway.http.securityHeaders.strictTransportSecurity` (set only for HTTPS origins you control; see [Trusted Proxy Auth ](/gateway/trusted-proxy-auth#tls-termination-and-hsts ))
2026-02-11 10:44:34 -05:00
### Multi-instance isolation
Run multiple gateways on one host with unique ports and state dirs:
```bash
OPENCLAW_CONFIG_PATH=~/.openclaw/a.json \
OPENCLAW_STATE_DIR=~/.openclaw-a \
openclaw gateway --port 19001
```
Convenience flags: `--dev` (uses `~/.openclaw-dev` + port `19001` ), `--profile <name>` (uses `~/.openclaw-<name>` ).
See [Multiple Gateways ](/gateway/multiple-gateways ).
---
## Hooks
```json5
{
hooks: {
enabled: true,
token: "shared-secret",
path: "/hooks",
maxBodyBytes: 262144,
2026-02-13 02:09:01 +01:00
defaultSessionKey: "hook:ingress",
allowRequestSessionKey: false,
allowedSessionKeyPrefixes: ["hook:"],
2026-02-11 10:44:34 -05:00
allowedAgentIds: ["hooks", "main"],
presets: ["gmail"],
2026-02-14 13:45:58 +01:00
transformsDir: "~/.openclaw/hooks/transforms",
2026-02-11 10:44:34 -05:00
mappings: [
{
match: { path: "gmail" },
action: "agent",
agentId: "hooks",
wakeMode: "now",
name: "Gmail",
sessionKey: "hook:gmail:{{messages[0].id}}",
messageTemplate: "From: {{messages[0].from}}\nSubject: {{messages[0].subject}}\n{{messages[0].snippet}}",
deliver: true,
channel: "last",
model: "openai/gpt-5.2-mini",
},
],
},
}
```
Auth: `Authorization: Bearer <token>` or `x-openclaw-token: <token>` .
**Endpoints:**
- `POST /hooks/wake` → `{ text, mode?: "now"|"next-heartbeat" }`
- `POST /hooks/agent` → `{ message, name?, agentId?, sessionKey?, wakeMode?, deliver?, channel?, to?, model?, thinking?, timeoutSeconds? }`
2026-02-13 02:09:01 +01:00
- `sessionKey` from request payload is accepted only when `hooks.allowRequestSessionKey=true` (default: `false` ).
2026-02-11 10:44:34 -05:00
- `POST /hooks/<name>` → resolved via `hooks.mappings`
< Accordion title = "Mapping details" >
- `match.path` matches sub-path after `/hooks` (e.g. `/hooks/gmail` → `gmail` ).
- `match.source` matches a payload field for generic paths.
- Templates like `{{messages[0].subject}}` read from the payload.
- `transform` can point to a JS/TS module returning a hook action.
2026-02-14 14:06:24 +01:00
- `transform.module` must be a relative path and stays within `hooks.transformsDir` (absolute paths and traversal are rejected).
2026-02-11 10:44:34 -05:00
- `agentId` routes to a specific agent; unknown IDs fall back to default.
- `allowedAgentIds` : restricts explicit routing (`*` or omitted = allow all, `[]` = deny all).
2026-02-13 02:09:01 +01:00
- `defaultSessionKey` : optional fixed session key for hook agent runs without explicit `sessionKey` .
- `allowRequestSessionKey` : allow `/hooks/agent` callers to set `sessionKey` (default: `false` ).
- `allowedSessionKeyPrefixes` : optional prefix allowlist for explicit `sessionKey` values (request + mapping), e.g. `["hook:"]` .
2026-02-11 10:44:34 -05:00
- `deliver: true` sends final reply to a channel; `channel` defaults to `last` .
- `model` overrides LLM for this hook run (must be allowed if model catalog is set).
< / Accordion >
### Gmail integration
```json5
{
hooks: {
gmail: {
account: "openclaw@gmail .com",
topic: "projects/< project-id > /topics/gog-gmail-watch",
subscription: "gog-gmail-watch-push",
pushToken: "shared-push-token",
hookUrl: "http://127.0.0.1:18789/hooks/gmail",
includeBody: true,
maxBytes: 20000,
renewEveryMinutes: 720,
serve: { bind: "127.0.0.1", port: 8788, path: "/" },
tailscale: { mode: "funnel", path: "/gmail-pubsub" },
model: "openrouter/meta-llama/llama-3.3-70b-instruct:free",
thinking: "off",
},
},
}
```
- Gateway auto-starts `gog gmail watch serve` on boot when configured. Set `OPENCLAW_SKIP_GMAIL_WATCHER=1` to disable.
- Don't run a separate `gog gmail watch serve` alongside the Gateway.
---
## Canvas host
```json5
{
canvasHost: {
root: "~/.openclaw/workspace/canvas",
liveReload: true,
// enabled: false, // or OPENCLAW_SKIP_CANVAS_HOST=1
},
}
```
2026-02-14 14:55:10 +01:00
- Serves agent-editable HTML/CSS/JS and A2UI over HTTP under the Gateway port:
- `http://<gateway-host>:<gateway.port>/__openclaw__/canvas/`
- `http://<gateway-host>:<gateway.port>/__openclaw__/a2ui/`
- Local-only: keep `gateway.bind: "loopback"` (default).
- Non-loopback binds: canvas routes require Gateway auth (token/password/trusted-proxy), same as other Gateway HTTP surfaces.
2026-02-19 15:50:42 +01:00
- Node WebViews typically don't send auth headers; after a node is paired and connected, the Gateway advertises node-scoped capability URLs for canvas/A2UI access.
- Capability URLs are bound to the active node WS session and expire quickly. IP-based fallback is not used.
2026-02-11 10:44:34 -05:00
- Injects live-reload client into served HTML.
- Auto-creates starter `index.html` when empty.
- Also serves A2UI at `/__openclaw__/a2ui/` .
- Changes require a gateway restart.
- Disable live reload for large directories or `EMFILE` errors.
---
## Discovery
### mDNS (Bonjour)
```json5
{
discovery: {
mdns: {
mode: "minimal", // minimal | full | off
},
},
}
```
- `minimal` (default): omit `cliPath` + `sshPort` from TXT records.
- `full` : include `cliPath` + `sshPort` .
- Hostname defaults to `openclaw` . Override with `OPENCLAW_MDNS_HOSTNAME` .
### Wide-area (DNS-SD)
```json5
{
discovery: {
wideArea: { enabled: true },
},
}
```
Writes a unicast DNS-SD zone under `~/.openclaw/dns/` . For cross-network discovery, pair with a DNS server (CoreDNS recommended) + Tailscale split DNS.
Setup: `openclaw dns setup --apply` .
---
## Environment
### `env` (inline env vars)
```json5
{
env: {
OPENROUTER_API_KEY: "sk-or-...",
vars: {
GROQ_API_KEY: "gsk-...",
},
shellEnv: {
enabled: true,
timeoutMs: 15000,
},
},
}
```
- Inline env vars are only applied if the process env is missing the key.
- `.env` files: CWD `.env` + `~/.openclaw/.env` (neither overrides existing vars).
- `shellEnv` : imports missing expected keys from your login shell profile.
- See [Environment ](/help/environment ) for full precedence.
### Env var substitution
Reference env vars in any config string with `${VAR_NAME}` :
```json5
{
gateway: {
auth: { token: "${OPENCLAW_GATEWAY_TOKEN}" },
},
}
```
- Only uppercase names matched: `[A-Z_][A-Z0-9_]*` .
- Missing/empty vars throw an error at config load.
- Escape with `$${VAR}` for a literal `${VAR}` .
- Works with `$include` .
---
2026-02-24 16:26:51 -06:00
## Secrets
Secret refs are additive: plaintext values still work.
### `SecretRef`
Use one object shape:
```json5
2026-02-25 17:58:10 -06:00
{ source: "env" | "file" | "exec", provider: "default", id: "..." }
2026-02-24 16:26:51 -06:00
```
Validation:
2026-02-25 17:58:10 -06:00
- `provider` pattern: `^[a-z][a-z0-9_-]{0,63}$`
2026-02-24 16:26:51 -06:00
- `source: "env"` id pattern: `^[A-Z][A-Z0-9_]{0,127}$`
- `source: "file"` id: absolute JSON pointer (for example `"/providers/openai/apiKey"` )
2026-02-25 17:58:10 -06:00
- `source: "exec"` id pattern: `^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$`
2026-02-24 16:26:51 -06:00
2026-03-02 20:58:20 -06:00
### Supported credential surface
2026-02-24 16:26:51 -06:00
2026-03-02 20:58:20 -06:00
- Canonical matrix: [SecretRef Credential Surface ](/reference/secretref-credential-surface )
- `secrets apply` targets supported `openclaw.json` credential paths.
- `auth-profiles.json` refs are included in runtime resolution and audit coverage.
2026-02-24 16:26:51 -06:00
2026-02-25 17:58:10 -06:00
### Secret providers config
2026-02-24 16:26:51 -06:00
```json5
{
secrets: {
2026-02-25 17:58:10 -06:00
providers: {
default: { source: "env" }, // optional explicit env provider
filemain: {
source: "file",
path: "~/.openclaw/secrets.json",
2026-02-25 23:17:31 -06:00
mode: "json",
2026-02-24 16:26:51 -06:00
timeoutMs: 5000,
},
2026-02-25 17:58:10 -06:00
vault: {
source: "exec",
command: "/usr/local/bin/openclaw-vault-resolver",
passEnv: ["PATH", "VAULT_ADDR"],
},
},
defaults: {
env: "default",
file: "filemain",
exec: "vault",
2026-02-24 16:26:51 -06:00
},
},
}
```
Notes:
2026-02-25 23:17:31 -06:00
- `file` provider supports `mode: "json"` and `mode: "singleValue"` (`id` must be `"value"` in singleValue mode).
2026-02-25 23:25:23 -06:00
- `exec` provider requires an absolute `command` path and uses protocol payloads on stdin/stdout.
- By default, symlink command paths are rejected. Set `allowSymlinkCommand: true` to allow symlink paths while validating the resolved target path.
- If `trustedDirs` is configured, the trusted-dir check applies to the resolved target path.
2026-02-25 23:17:31 -06:00
- `exec` child environment is minimal by default; pass required variables explicitly with `passEnv` .
2026-02-25 17:58:10 -06:00
- Secret refs are resolved at activation time into an in-memory snapshot, then request paths read the snapshot only.
2026-03-02 20:58:20 -06:00
- Active-surface filtering applies during activation: unresolved refs on enabled surfaces fail startup/reload, while inactive surfaces are skipped with diagnostics.
2026-02-24 16:26:51 -06:00
---
2026-02-11 10:44:34 -05:00
## Auth storage
```json5
{
auth: {
profiles: {
"anthropic:me@example .com": { provider: "anthropic", mode: "oauth", email: "me@example .com" },
"anthropic:work": { provider: "anthropic", mode: "api_key" },
},
order: {
anthropic: ["anthropic:me@example .com", "anthropic:work"],
},
},
}
```
2026-03-02 20:58:20 -06:00
- Per-agent profiles are stored at `<agentDir>/auth-profiles.json` .
- `auth-profiles.json` supports value-level refs (`keyRef` for `api_key` , `tokenRef` for `token` ).
2026-02-24 16:26:51 -06:00
- Static runtime credentials come from in-memory resolved snapshots; legacy static `auth.json` entries are scrubbed when discovered.
2026-02-11 10:44:34 -05:00
- Legacy OAuth imports from `~/.openclaw/credentials/oauth.json` .
- See [OAuth ](/concepts/oauth ).
2026-02-25 20:29:39 -06:00
- Secrets runtime behavior and `audit/configure/apply` tooling: [Secrets Management ](/gateway/secrets ).
2026-02-11 10:44:34 -05:00
---
## Logging
```json5
{
logging: {
level: "info",
file: "/tmp/openclaw/openclaw.log",
consoleLevel: "info",
consoleStyle: "pretty", // pretty | compact | json
redactSensitive: "tools", // off | tools
redactPatterns: ["\\bTOKEN\\b\\s*[=:]\\s*([\"']?)([^\\s\"']+)\\1"],
},
}
```
- Default log file: `/tmp/openclaw/openclaw-YYYY-MM-DD.log` .
- Set `logging.file` for a stable path.
- `consoleLevel` bumps to `debug` when `--verbose` .
---
2026-03-03 00:31:42 +00:00
## CLI
```json5
{
cli: {
banner: {
taglineMode: "off", // random | default | off
},
},
}
```
- `cli.banner.taglineMode` controls banner tagline style:
- `"random"` (default): rotating funny/seasonal taglines.
- `"default"` : fixed neutral tagline (`All your chats, one OpenClaw.` ).
- `"off"` : no tagline text (banner title/version still shown).
- To hide the entire banner (not just taglines), set env `OPENCLAW_HIDE_BANNER=1` .
---
2026-02-11 10:44:34 -05:00
## Wizard
Metadata written by CLI wizards (`onboard` , `configure` , `doctor` ):
```json5
{
wizard: {
lastRunAt: "2026-01-01T00:00:00.000Z",
lastRunVersion: "2026.1.4",
lastRunCommit: "abc1234",
lastRunCommand: "configure",
lastRunMode: "local",
},
}
```
---
## Identity
```json5
{
agents: {
list: [
{
id: "main",
identity: {
name: "Samantha",
theme: "helpful sloth",
emoji: "🦥",
avatar: "avatars/samantha.png",
},
},
],
},
}
```
Written by the macOS onboarding assistant. Derives defaults:
- `messages.ackReaction` from `identity.emoji` (falls back to 👀)
- `mentionPatterns` from `identity.name` /`identity.emoji`
- `avatar` accepts: workspace-relative path, `http(s)` URL, or `data:` URI
---
## Bridge (legacy, removed)
Current builds no longer include the TCP bridge. Nodes connect over the Gateway WebSocket. `bridge.*` keys are no longer part of the config schema (validation fails until removed; `openclaw doctor --fix` can strip unknown keys).
< Accordion title = "Legacy bridge config (historical reference)" >
```json
{
"bridge": {
"enabled": true,
"port": 18790,
"bind": "tailnet",
"tls": {
"enabled": true,
"autoGenerate": true
}
}
}
```
< / Accordion >
---
## Cron
```json5
{
cron: {
enabled: true,
maxConcurrentRuns: 2,
2026-02-16 02:36:00 -08:00
webhook: "https://example.invalid/legacy", // deprecated fallback for stored notify:true jobs
2026-02-15 16:14:17 -08:00
webhookToken: "replace-with-dedicated-token", // optional bearer token for outbound webhook auth
2026-02-11 10:44:34 -05:00
sessionRetention: "24h", // duration string or false
2026-02-23 17:39:48 -05:00
runLog: {
maxBytes: "2mb", // default 2_000_000 bytes
keepLines: 2000, // default 2000
},
2026-02-11 10:44:34 -05:00
},
}
```
2026-02-23 17:39:48 -05:00
- `sessionRetention` : how long to keep completed isolated cron run sessions before pruning from `sessions.json` . Also controls cleanup of archived deleted cron transcripts. Default: `24h` ; set `false` to disable.
- `runLog.maxBytes` : max size per run log file (`cron/runs/<jobId>.jsonl` ) before pruning. Default: `2_000_000` bytes.
- `runLog.keepLines` : newest lines retained when run-log pruning is triggered. Default: `2000` .
2026-02-16 02:36:00 -08:00
- `webhookToken` : bearer token used for cron webhook POST delivery (`delivery.mode = "webhook"` ), if omitted no auth header is sent.
- `webhook` : deprecated legacy fallback webhook URL (http/https) used only for stored jobs that still have `notify: true` .
2026-02-11 10:44:34 -05:00
See [Cron Jobs ](/automation/cron-jobs ).
---
## Media model template variables
2026-02-26 22:35:43 -05:00
Template placeholders expanded in `tools.media.models[].args` :
2026-02-11 10:44:34 -05:00
| Variable | Description |
| ------------------ | ------------------------------------------------- |
| `{{Body}}` | Full inbound message body |
| `{{RawBody}}` | Raw body (no history/sender wrappers) |
| `{{BodyStripped}}` | Body with group mentions stripped |
| `{{From}}` | Sender identifier |
| `{{To}}` | Destination identifier |
| `{{MessageSid}}` | Channel message id |
| `{{SessionId}}` | Current session UUID |
| `{{IsNewSession}}` | `"true"` when new session created |
| `{{MediaUrl}}` | Inbound media pseudo-URL |
| `{{MediaPath}}` | Local media path |
| `{{MediaType}}` | Media type (image/audio/document/…) |
| `{{Transcript}}` | Audio transcript |
| `{{Prompt}}` | Resolved media prompt for CLI entries |
| `{{MaxChars}}` | Resolved max output chars for CLI entries |
| `{{ChatType}}` | `"direct"` or `"group"` |
| `{{GroupSubject}}` | Group subject (best effort) |
| `{{GroupMembers}}` | Group members preview (best effort) |
| `{{SenderName}}` | Sender display name (best effort) |
| `{{SenderE164}}` | Sender phone number (best effort) |
| `{{Provider}}` | Provider hint (whatsapp, telegram, discord, etc.) |
---
## Config includes (`$include`)
Split config into multiple files:
```json5
// ~/.openclaw/openclaw.json
{
gateway: { port: 18789 },
agents: { $include: "./agents.json5" },
broadcast: {
$include: ["./clients/mueller.json5", "./clients/schmidt.json5"],
},
}
```
**Merge behavior:**
- Single file: replaces the containing object.
- Array of files: deep-merged in order (later overrides earlier).
- Sibling keys: merged after includes (override included values).
- Nested includes: up to 10 levels deep.
2026-03-02 20:58:20 -06:00
- Paths: resolved relative to the including file, but must stay inside the top-level config directory (`dirname` of `openclaw.json` ). Absolute/`../` forms are allowed only when they still resolve inside that boundary.
2026-02-11 10:44:34 -05:00
- Errors: clear messages for missing files, parse errors, and circular includes.
---
_Related: [Configuration ](/gateway/configuration ) · [Configuration Examples ](/gateway/configuration-examples ) · [Doctor ](/gateway/doctor )_