2025-12-09 17:51:05 +00:00
---
2026-01-04 14:32:47 +00:00
summary: "All configuration options for ~/.clawdbot/clawdbot.json with examples"
2025-12-09 17:51:05 +00:00
read_when:
- Adding or modifying config fields
---
2025-12-03 15:45:32 +00:00
# Configuration 🔧
2026-01-10 05:14:09 +01:00
Clawdbot reads an optional **JSON5** config from `~/.clawdbot/clawdbot.json` (comments + trailing commas allowed).
2025-12-03 15:45:32 +00:00
2026-01-10 05:14:09 +01:00
If the file is missing, Clawdbot uses safe-ish defaults (embedded Pi agent + per-sender sessions + workspace `~/clawd` ). You usually only need a config to:
2026-01-13 06:16:43 +00:00
- restrict who can trigger the bot (`channels.whatsapp.allowFrom` , `channels.telegram.allowFrom` , etc.)
- control group allowlists + mention behavior (`channels.whatsapp.groups` , `channels.telegram.groups` , `channels.discord.guilds` , `agents.list[].groupChat` )
2025-12-24 00:22:57 +00:00
- customize message prefixes (`messages` )
2026-01-09 12:44:23 +00:00
- set the agent's workspace (`agents.defaults.workspace` or `agents.list[].workspace` )
- tune the embedded agent defaults (`agents.defaults` ) and session behavior (`session` )
- set per-agent identity (`agents.list[].identity` )
2025-12-03 15:45:32 +00:00
2026-01-05 22:22:15 -08:00
> **New to configuration?** Check out the [Configuration Examples](/gateway/configuration-examples) guide for complete examples with detailed explanations!
2026-01-03 16:04:19 +01:00
## Schema + UI hints
The Gateway exposes a JSON Schema representation of the config via `config.schema` for UI editors.
The Control UI renders a form from this schema, with a **Raw JSON** editor as an escape hatch.
2026-01-16 14:22:03 -06:00
Channel plugins and extensions can register schema + UI hints for their config, so channel settings
stay schema-driven across apps without hard-coded forms.
2026-01-03 16:04:19 +01:00
Hints (labels, grouping, sensitive fields) ship alongside the schema so clients can render
better forms without hard-coding config knowledge.
2026-01-08 01:29:56 +01:00
## Apply + restart (RPC)
Use `config.apply` to validate + write the full config and restart the Gateway in one step.
It writes a restart sentinel and pings the last active session after the Gateway comes back.
Params:
- `raw` (string) — JSON5 payload for the entire config
2026-01-15 04:05:01 +00:00
- `baseHash` (optional) — config hash from `config.get` (required when a config already exists)
2026-01-08 01:29:56 +01:00
- `sessionKey` (optional) — last active session key for the wake-up ping
- `restartDelayMs` (optional) — delay before restart (default 2000)
Example (via `gateway call` ):
```bash
2026-01-15 04:05:01 +00:00
clawdbot gateway call config.get --params '{}' # capture payload.hash
2026-01-08 01:29:56 +01:00
clawdbot gateway call config.apply --params '{
2026-01-09 12:44:23 +00:00
"raw": "{\\n agents: { defaults: { workspace: \\"~/clawd\\" } }\\n}\\n",
2026-01-15 04:05:01 +00:00
"baseHash": "< hash-from-config.get > ",
2026-01-08 01:29:56 +01:00
"sessionKey": "agent:main:whatsapp:dm:+15555550123",
"restartDelayMs": 1000
}'
```
2026-01-03 16:04:19 +01:00
2026-01-15 04:05:01 +00:00
## Partial updates (RPC)
Use `config.patch` to merge a partial update into the existing config without clobbering
unrelated keys. It applies JSON merge patch semantics:
- objects merge recursively
- `null` deletes a key
- arrays replace
Params:
- `raw` (string) — JSON5 payload containing just the keys to change
- `baseHash` (required) — config hash from `config.get`
Example:
```bash
clawdbot gateway call config.get --params '{}' # capture payload.hash
clawdbot gateway call config.patch --params '{
"raw": "{\\n channels: { telegram: { groups: { \\"*\\": { requireMention: false } } } }\\n}\\n",
"baseHash": "< hash-from-config.get > "
}'
```
2025-12-13 13:25:49 +00:00
## Minimal config (recommended starting point)
2025-12-03 15:45:32 +00:00
2025-12-13 13:25:49 +00:00
```json5
2025-12-03 15:45:32 +00:00
{
2026-01-09 12:44:23 +00:00
agents: { defaults: { workspace: "~/clawd" } },
2026-01-13 06:16:43 +00:00
channels: { whatsapp: { allowFrom: ["+15555550123"] } }
2025-12-03 15:45:32 +00:00
}
```
2026-01-03 21:35:44 +01:00
Build the default image once with:
```bash
scripts/sandbox-setup.sh
```
2026-01-02 17:25:33 -03:00
## Self-chat mode (recommended for group control)
To prevent the bot from responding to WhatsApp @-mentions in groups (only respond to specific text triggers):
```json5
{
2026-01-09 12:44:23 +00:00
agents: {
defaults: { workspace: "~/clawd" },
list: [
{
id: "main",
groupChat: { mentionPatterns: ["@clawd ", "reisponde"] }
}
]
},
2026-01-13 06:16:43 +00:00
channels: {
whatsapp: {
// Allowlist is DMs only; including your own number enables self-chat mode.
allowFrom: ["+15555550123"],
groups: { "*": { requireMention: true } }
}
2026-01-02 17:25:33 -03:00
}
}
```
2026-01-11 15:01:02 +01:00
## Config Includes (`$include`)
Split your config into multiple files using the `$include` directive. This is useful for:
- Organizing large configs (e.g., per-client agent definitions)
- Sharing common settings across environments
- Keeping sensitive configs separate
### Basic usage
```json5
// ~/.clawdbot/clawdbot.json
{
gateway: { port: 18789 },
// Include a single file (replaces the key's value)
agents: { "$include": "./agents.json5" },
// Include multiple files (deep-merged in order)
broadcast: {
"$include": [
"./clients/mueller.json5",
"./clients/schmidt.json5"
]
}
}
```
```json5
// ~/.clawdbot/agents.json5
{
defaults: { sandbox: { mode: "all", scope: "session" } },
list: [
{ id: "main", workspace: "~/clawd" }
]
}
```
### Merge behavior
- **Single file**: Replaces the object containing `$include`
- **Array of files**: Deep-merges files in order (later files override earlier ones)
- **With sibling keys**: Sibling keys are merged after includes (override included values)
2026-01-12 00:12:03 +00:00
- **Sibling keys + arrays/primitives**: Not supported (included content must be an object)
2026-01-11 15:01:02 +01:00
```json5
// Sibling keys override included values
{
"$include": "./base.json5", // { a: 1, b: 2 }
b: 99 // Result: { a: 1, b: 99 }
}
```
### Nested includes
Included files can themselves contain `$include` directives (up to 10 levels deep):
```json5
// clients/mueller.json5
{
agents: { "$include": "./mueller/agents.json5" },
broadcast: { "$include": "./mueller/broadcast.json5" }
}
```
### Path resolution
- **Relative paths**: Resolved relative to the including file
- **Absolute paths**: Used as-is
- **Parent directories**: `../` references work as expected
```json5
{ "$include": "./sub/config.json5" } // relative
{ "$include": "/etc/clawdbot/base.json5" } // absolute
{ "$include": "../shared/common.json5" } // parent dir
```
### Error handling
- **Missing file**: Clear error with resolved path
- **Parse error**: Shows which included file failed
- **Circular includes**: Detected and reported with include chain
### Example: Multi-client legal setup
```json5
// ~/.clawdbot/clawdbot.json
{
gateway: { port: 18789, auth: { token: "secret" } },
// Common agent defaults
agents: {
defaults: {
sandbox: { mode: "all", scope: "session" }
},
// Merge agent lists from all clients
list: { "$include": [
"./clients/mueller/agents.json5",
"./clients/schmidt/agents.json5"
]}
},
// Merge broadcast configs
broadcast: { "$include": [
"./clients/mueller/broadcast.json5",
"./clients/schmidt/broadcast.json5"
]},
2026-01-13 06:16:43 +00:00
channels: { whatsapp: { groupPolicy: "allowlist" } }
2026-01-11 15:01:02 +01:00
}
```
```json5
// ~/.clawdbot/clients/mueller/agents.json5
[
{ id: "mueller-transcribe", workspace: "~/clients/mueller/transcribe" },
{ id: "mueller-docs", workspace: "~/clients/mueller/docs" }
]
```
```json5
// ~/.clawdbot/clients/mueller/broadcast.json5
{
"120363403215116621@g .us": ["mueller-transcribe", "mueller-docs"]
}
```
2025-12-13 13:25:49 +00:00
## Common options
2025-12-03 15:45:32 +00:00
2026-01-05 01:03:01 +01:00
### Env vars + `.env`
2026-01-10 05:14:09 +01:00
Clawdbot reads env vars from the parent process (shell, launchd/systemd, CI, etc.).
2026-01-05 01:03:01 +01:00
Additionally, it loads:
- `.env` from the current working directory (if present)
- a global fallback `.env` from `~/.clawdbot/.env` (aka `$CLAWDBOT_STATE_DIR/.env` )
Neither `.env` file overrides existing env vars.
2026-01-08 22:37:06 +01:00
You can also provide inline env vars in config. These are only applied if the
process env is missing the key (same non-overriding rule):
```json5
{
env: {
OPENROUTER_API_KEY: "sk-or-...",
vars: {
GROQ_API_KEY: "gsk-..."
}
}
}
```
See [/environment ](/environment ) for full precedence and sources.
2026-01-05 01:03:01 +01:00
### `env.shellEnv` (optional)
2026-01-10 05:14:09 +01:00
Opt-in convenience: if enabled and none of the expected keys are set yet, Clawdbot runs your login shell and imports only the missing expected keys (never overrides).
2026-01-05 01:03:01 +01:00
This effectively sources your shell profile.
```json5
{
env: {
shellEnv: {
enabled: true,
timeoutMs: 15000
}
}
}
```
Env var equivalent:
- `CLAWDBOT_LOAD_SHELL_ENV=1`
- `CLAWDBOT_SHELL_ENV_TIMEOUT_MS=15000`
2026-01-16 16:32:07 -05:00
### Env var substitution in config
You can reference environment variables directly in any config string value using
`${VAR_NAME}` syntax. Variables are substituted at config load time, before validation.
```json5
{
models: {
providers: {
"vercel-gateway": {
apiKey: "${VERCEL_GATEWAY_API_KEY}"
}
}
},
gateway: {
auth: {
token: "${CLAWDBOT_GATEWAY_TOKEN}"
}
}
}
```
**Rules:**
- Only uppercase env var names are matched: `[A-Z_][A-Z0-9_]*`
- Missing or empty env vars throw an error at config load
- Escape with `$${VAR}` to output a literal `${VAR}`
- Works with `$include` (included files also get substitution)
**Inline substitution:**
```json5
{
models: {
providers: {
custom: {
baseUrl: "${CUSTOM_API_BASE}/v1" // → "https://api.example.com/v1"
}
}
}
}
```
2026-01-05 06:46:20 +01:00
### Auth storage (OAuth + API keys)
2026-01-06 18:25:52 +00:00
Clawdbot stores **per-agent** auth profiles (OAuth + API keys) in:
- `<agentDir>/auth-profiles.json` (default: `~/.clawdbot/agents/<agentId>/agent/auth-profiles.json` )
2026-01-06 00:56:29 +00:00
2026-01-08 09:29:29 +01:00
See also: [/concepts/oauth ](/concepts/oauth )
2026-01-06 00:56:29 +00:00
Legacy OAuth imports:
2026-01-05 21:53:37 +01:00
- `~/.clawdbot/credentials/oauth.json` (or `$CLAWDBOT_STATE_DIR/credentials/oauth.json` )
2026-01-05 06:46:20 +01:00
2026-01-06 00:56:29 +00:00
The embedded Pi agent maintains a runtime cache at:
2026-01-06 18:25:52 +00:00
- `<agentDir>/auth.json` (managed automatically; don’ t edit manually)
Legacy agent dir (pre multi-agent):
- `~/.clawdbot/agent/*` (migrated by `clawdbot doctor` into `~/.clawdbot/agents/<defaultAgentId>/agent/*` )
2026-01-05 06:46:20 +01:00
2026-01-05 21:53:37 +01:00
Overrides:
2026-01-06 00:56:29 +00:00
- OAuth dir (legacy import only): `CLAWDBOT_OAUTH_DIR`
2026-01-06 21:33:53 +00:00
- Agent dir (default agent root override): `CLAWDBOT_AGENT_DIR` (preferred), `PI_CODING_AGENT_DIR` (legacy)
2026-01-05 06:46:20 +01:00
2026-01-06 00:56:29 +00:00
On first use, Clawdbot imports `oauth.json` entries into `auth-profiles.json` .
2026-01-08 09:29:29 +01:00
Clawdbot also auto-syncs OAuth tokens from external CLIs into `auth-profiles.json` (when present on the gateway host):
2026-01-08 23:17:08 +01:00
- Claude Code → `anthropic:claude-cli`
- macOS: Keychain item "Claude Code-credentials" (choose "Always Allow" to avoid launchd prompts)
- Linux/Windows: `~/.claude/.credentials.json`
2026-01-08 09:29:29 +01:00
- `~/.codex/auth.json` (Codex CLI) → `openai-codex:codex-cli`
2026-01-06 00:56:29 +00:00
### `auth`
Optional metadata for auth profiles. This does **not** store secrets; it maps
profile IDs to a provider + mode (and optional email) and defines the provider
rotation order used for failover.
```json5
{
auth: {
profiles: {
2026-01-07 06:29:43 +00:00
"anthropic:me@example .com": { provider: "anthropic", mode: "oauth", email: "me@example .com" },
2026-01-06 00:56:29 +00:00
"anthropic:work": { provider: "anthropic", mode: "api_key" }
},
order: {
2026-01-07 06:29:43 +00:00
anthropic: ["anthropic:me@example .com", "anthropic:work"]
2026-01-06 00:56:29 +00:00
}
}
}
```
2026-01-05 06:46:20 +01:00
2026-01-15 02:35:20 +00:00
Note: `anthropic:claude-cli` should use `mode: "oauth"` even when the stored
credential is a setup-token. Clawdbot auto-migrates older configs that used
`mode: "token"` .
2026-01-09 12:44:23 +00:00
### `agents.list[].identity`
2025-12-14 04:21:27 +00:00
2026-01-09 12:44:23 +00:00
Optional per-agent identity used for defaults and UX. This is written by the macOS onboarding assistant.
2025-12-14 04:21:27 +00:00
2026-01-10 05:14:09 +01:00
If set, Clawdbot derives defaults (only when you haven’ t set them explicitly):
2026-01-09 12:44:23 +00:00
- `messages.ackReaction` from the **active agent** ’ s `identity.emoji` (falls back to 👀)
- `agents.list[].groupChat.mentionPatterns` from the agent’ s `identity.name` /`identity.emoji` (so “@Samantha ” works in groups across Telegram/Slack/Discord/iMessage/WhatsApp)
2025-12-14 04:21:27 +00:00
```json5
{
2026-01-09 12:44:23 +00:00
agents: {
list: [
{ id: "main", identity: { name: "Samantha", theme: "helpful sloth", emoji: "🦥" } }
]
}
2025-12-14 04:21:27 +00:00
}
```
2026-01-01 19:14:14 +01:00
### `wizard`
2026-01-08 07:16:05 +01:00
Metadata written by CLI wizards (`onboard` , `configure` , `doctor` ).
2026-01-01 19:14:14 +01:00
```json5
{
wizard: {
lastRunAt: "2026-01-01T00:00:00.000Z",
2026-01-04 18:49:23 +01:00
lastRunVersion: "2026.1.4",
2026-01-01 19:14:14 +01:00
lastRunCommit: "abc1234",
lastRunCommand: "configure",
lastRunMode: "local"
}
}
```
2025-12-03 15:45:32 +00:00
### `logging`
2026-01-04 14:32:47 +00:00
- Default log file: `/tmp/clawdbot/clawdbot-YYYY-MM-DD.log`
- If you want a stable path, set `logging.file` to `/tmp/clawdbot/clawdbot.log` .
2025-12-21 13:23:42 +00:00
- Console output can be tuned separately via:
- `logging.consoleLevel` (defaults to `info` , bumps to `debug` when `--verbose` )
- `logging.consoleStyle` (`pretty` | `compact` | `json` )
2026-01-06 00:41:12 +01:00
- Tool summaries can be redacted to avoid leaking secrets:
- `logging.redactSensitive` (`off` | `tools` , default: `tools` )
- `logging.redactPatterns` (array of regex strings; overrides defaults)
2025-12-13 13:25:49 +00:00
```json5
{
2025-12-21 13:23:42 +00:00
logging: {
level: "info",
2026-01-04 14:32:47 +00:00
file: "/tmp/clawdbot/clawdbot.log",
2025-12-21 13:23:42 +00:00
consoleLevel: "info",
2026-01-06 00:41:12 +01:00
consoleStyle: "pretty",
redactSensitive: "tools",
redactPatterns: [
// Example: override defaults with your own rules.
"\\bTOKEN\\b\\s*[=:]\\s*([\"']?)([^\\s\"']+)\\1",
"/\\bsk-[A-Za-z0-9_-]{8,}\\b/gi"
]
2025-12-21 13:23:42 +00:00
}
2025-12-13 13:25:49 +00:00
}
```
2025-12-03 15:45:32 +00:00
2026-01-13 06:16:43 +00:00
### `channels.whatsapp.dmPolicy`
2026-01-06 17:51:38 +01:00
Controls how WhatsApp direct chats (DMs) are handled:
- `"pairing"` (default): unknown senders get a pairing code; owner must approve
2026-01-13 06:16:43 +00:00
- `"allowlist"` : only allow senders in `channels.whatsapp.allowFrom` (or paired allow store)
- `"open"` : allow all inbound DMs (**requires** `channels.whatsapp.allowFrom` to include `"*"` )
2026-01-06 17:51:38 +01:00
- `"disabled"` : ignore all inbound DMs
2026-01-13 07:15:57 +00:00
Pairing codes expire after 1 hour; the bot only sends a pairing code when a new request is created. Pending DM pairing requests are capped at **3 per channel** by default.
2026-01-07 05:06:04 +01:00
2026-01-06 17:51:38 +01:00
Pairing approvals:
2026-01-10 16:36:43 +01:00
- `clawdbot pairing list whatsapp`
- `clawdbot pairing approve whatsapp <code>`
2026-01-06 17:51:38 +01:00
2026-01-13 06:16:43 +00:00
### `channels.whatsapp.allowFrom`
2025-12-03 15:45:32 +00:00
2026-01-06 06:40:42 +00:00
Allowlist of E.164 phone numbers that may trigger WhatsApp auto-replies (**DMs only**).
2026-01-13 06:16:43 +00:00
If empty and `channels.whatsapp.dmPolicy="pairing"` , unknown senders will receive a pairing code.
For groups, use `channels.whatsapp.groupPolicy` + `channels.whatsapp.groupAllowFrom` .
2025-12-03 15:45:32 +00:00
2025-12-13 13:25:49 +00:00
```json5
{
2026-01-13 06:16:43 +00:00
channels: {
whatsapp: {
dmPolicy: "pairing", // pairing | allowlist | open | disabled
allowFrom: ["+15555550123", "+447700900123"],
textChunkLimit: 4000, // optional outbound chunk size (chars)
mediaMaxMb: 50 // optional inbound media cap (MB)
}
2026-01-03 01:27:37 +01:00
}
2025-12-13 13:25:49 +00:00
}
2025-12-03 15:45:32 +00:00
```
2026-01-15 06:21:05 +00:00
### `channels.whatsapp.sendReadReceipts`
Controls whether inbound WhatsApp messages are marked as read (blue ticks). Default: `true` .
Self-chat mode always skips read receipts, even when enabled.
Per-account override: `channels.whatsapp.accounts.<id>.sendReadReceipts` .
```json5
{
channels: {
whatsapp: { sendReadReceipts: false }
}
}
```
2026-01-13 06:16:43 +00:00
### `channels.whatsapp.accounts` (multi-account)
2026-01-06 18:25:52 +00:00
Run multiple WhatsApp accounts in one gateway:
```json5
{
2026-01-13 06:16:43 +00:00
channels: {
whatsapp: {
accounts: {
default: {}, // optional; keeps the default id stable
personal: {},
biz: {
// Optional override. Default: ~/.clawdbot/credentials/whatsapp/biz
// authDir: "~/.clawdbot/credentials/whatsapp/biz",
}
2026-01-06 18:25:52 +00:00
}
}
}
}
```
Notes:
- Outbound commands default to account `default` if present; otherwise the first configured account id (sorted).
- The legacy single-account Baileys auth dir is migrated by `clawdbot doctor` into `whatsapp/default` .
2026-01-13 06:16:43 +00:00
### `channels.telegram.accounts` / `channels.discord.accounts` / `channels.slack.accounts` / `channels.signal.accounts` / `channels.imessage.accounts`
2026-01-08 01:18:37 +01:00
2026-01-13 07:15:57 +00:00
Run multiple accounts per channel (each account has its own `accountId` and optional `name` ):
2026-01-08 01:18:37 +01:00
```json5
{
2026-01-13 06:16:43 +00:00
channels: {
telegram: {
accounts: {
default: {
name: "Primary bot",
botToken: "123456:ABC..."
},
alerts: {
name: "Alerts bot",
botToken: "987654:XYZ..."
}
2026-01-08 01:18:37 +01:00
}
}
}
}
```
Notes:
- `default` is used when `accountId` is omitted (CLI + routing).
- Env tokens only apply to the **default** account.
2026-01-13 07:15:57 +00:00
- Base channel settings (group policy, mention gating, etc.) apply to all accounts unless overridden per account.
2026-01-09 12:44:23 +00:00
- Use `bindings[].match.accountId` to route each account to a different agents.defaults.
2026-01-08 01:18:37 +01:00
2026-01-09 12:44:23 +00:00
### Group chat mention gating (`agents.list[].groupChat` + `messages.groupChat`)
2026-01-02 17:25:33 -03:00
Group messages default to **require mention** (either metadata mention or regex patterns). Applies to WhatsApp, Telegram, Discord, and iMessage group chats.
2026-01-02 22:23:00 +01:00
2026-01-02 17:25:33 -03:00
**Mention types:**
2026-01-13 06:16:43 +00:00
- **Metadata mentions**: Native platform @-mentions (e.g., WhatsApp tap-to-mention). Ignored in WhatsApp self-chat mode (see `channels.whatsapp.allowFrom` ).
2026-01-09 12:44:23 +00:00
- **Text patterns**: Regex patterns defined in `agents.list[].groupChat.mentionPatterns` . Always checked regardless of self-chat mode.
2026-01-06 01:38:36 +01:00
- Mention gating is enforced only when mention detection is possible (native mentions or at least one `mentionPattern` ).
2026-01-02 22:23:00 +01:00
```json5
{
2026-01-09 12:44:23 +00:00
messages: {
groupChat: { historyLimit: 50 }
},
agents: {
list: [
{ id: "main", groupChat: { mentionPatterns: ["@clawd ", "clawdbot", "clawd"] } }
]
2026-01-02 22:23:00 +01:00
}
}
```
2026-01-13 07:15:57 +00:00
`messages.groupChat.historyLimit` sets the global default for group history context. Channels can override with `channels.<channel>.historyLimit` (or `channels.<channel>.accounts.*.historyLimit` for multi-account). Set `0` to disable history wrapping.
2026-01-10 18:53:38 +01:00
2026-01-15 02:22:29 +00:00
#### DM history limits
DM conversations use session-based history managed by the agent. You can limit the number of user turns retained per DM session:
```json5
{
channels: {
telegram: {
dmHistoryLimit: 30, // limit DM sessions to 30 user turns
dms: {
"123456789": { historyLimit: 50 } // per-user override (user ID)
}
}
}
}
```
Resolution order:
1. Per-DM override: `channels.<provider>.dms[userId].historyLimit`
2. Provider default: `channels.<provider>.dmHistoryLimit`
3. No limit (all history retained)
Supported providers: `telegram` , `whatsapp` , `discord` , `slack` , `signal` , `imessage` , `msteams` .
2026-01-08 22:57:08 +01:00
Per-agent override (takes precedence when set, even `[]` ):
```json5
{
2026-01-09 12:44:23 +00:00
agents: {
list: [
{ id: "work", groupChat: { mentionPatterns: ["@workbot ", "\\+15555550123"] } },
{ id: "personal", groupChat: { mentionPatterns: ["@homebot ", "\\+15555550999"] } }
]
2026-01-08 22:57:08 +01:00
}
}
```
2026-01-13 07:15:57 +00:00
Mention gating defaults live per channel (`channels.whatsapp.groups` , `channels.telegram.groups` , `channels.imessage.groups` , `channels.discord.guilds` ). When `*.groups` is set, it also acts as a group allowlist; include `"*"` to allow all groups.
2025-12-03 15:45:32 +00:00
2026-01-02 17:25:33 -03:00
To respond **only** to specific text triggers (ignoring native @-mentions):
2025-12-13 13:25:49 +00:00
```json5
{
2026-01-13 06:16:43 +00:00
channels: {
whatsapp: {
// Include your own number to enable self-chat mode (ignore native @-mentions).
allowFrom: ["+15555550123"],
groups: { "*": { requireMention: true } }
}
2026-01-02 17:25:33 -03:00
},
2026-01-09 12:44:23 +00:00
agents: {
list: [
{
id: "main",
groupChat: {
// Only these text patterns will trigger responses
mentionPatterns: ["reisponde", "@clawd "]
}
}
]
2025-12-13 13:25:49 +00:00
}
}
```
2025-12-13 02:34:11 +00:00
2026-01-13 07:15:57 +00:00
### Group policy (per channel)
2026-01-06 06:40:42 +00:00
2026-01-13 06:16:43 +00:00
Use `channels.*.groupPolicy` to control whether group/room messages are accepted at all:
2026-01-06 06:40:42 +00:00
```json5
{
2026-01-13 06:16:43 +00:00
channels: {
whatsapp: {
groupPolicy: "allowlist",
groupAllowFrom: ["+15551234567"]
},
telegram: {
groupPolicy: "allowlist",
groupAllowFrom: ["tg:123456789", "@alice "]
},
signal: {
groupPolicy: "allowlist",
groupAllowFrom: ["+15551234567"]
},
imessage: {
groupPolicy: "allowlist",
groupAllowFrom: ["chat_id:123"]
},
msteams: {
groupPolicy: "allowlist",
groupAllowFrom: ["user@org .com"]
},
discord: {
groupPolicy: "allowlist",
guilds: {
"GUILD_ID": {
channels: { help: { allow: true } }
}
2026-01-06 06:40:42 +00:00
}
2026-01-13 06:16:43 +00:00
},
slack: {
groupPolicy: "allowlist",
channels: { "#general ": { allow: true } }
2026-01-06 06:40:42 +00:00
}
}
}
```
Notes:
2026-01-12 08:21:50 +00:00
- `"open"` : groups bypass allowlists; mention-gating still applies.
2026-01-06 06:40:42 +00:00
- `"disabled"` : block all group/room messages.
- `"allowlist"` : only allow groups/rooms that match the configured allowlist.
2026-01-18 00:41:57 +00:00
- `channels.defaults.groupPolicy` sets the default when a provider’ s `groupPolicy` is unset.
2026-01-12 08:31:59 +00:00
- WhatsApp/Telegram/Signal/iMessage/Microsoft Teams use `groupAllowFrom` (fallback: explicit `allowFrom` ).
2026-01-13 06:16:43 +00:00
- Discord/Slack use channel allowlists (`channels.discord.guilds.*.channels` , `channels.slack.channels` ).
2026-01-06 06:40:42 +00:00
- Group DMs (Discord/Slack) are still controlled by `dm.groupEnabled` + `dm.groupChannels` .
2026-01-18 00:41:57 +00:00
- Default is `groupPolicy: "allowlist"` (unless overridden by `channels.defaults.groupPolicy` ); if no allowlist is configured, group messages are blocked.
2026-01-06 06:40:42 +00:00
2026-01-09 12:44:23 +00:00
### Multi-agent routing (`agents.list` + `bindings`)
2026-01-06 18:25:52 +00:00
2026-01-09 12:44:23 +00:00
Run multiple isolated agents (separate workspace, `agentDir` , sessions) inside one Gateway.
Inbound messages are routed to an agent via bindings.
2026-01-06 18:25:52 +00:00
2026-01-09 12:44:23 +00:00
- `agents.list[]` : per-agent overrides.
- `id` : stable agent id (required).
- `default` : optional; when multiple are set, the first wins and a warning is logged.
If none are set, the **first entry** in the list is the default agent.
2026-01-07 09:58:54 +01:00
- `name` : display name for the agent.
2026-01-09 12:44:23 +00:00
- `workspace` : default `~/clawd-<agentId>` (for `main` , falls back to `agents.defaults.workspace` ).
2026-01-06 18:25:52 +00:00
- `agentDir` : default `~/.clawdbot/agents/<agentId>/agent` .
2026-01-09 14:59:02 +01:00
- `model` : per-agent default model, overrides `agents.defaults.model` for that agent.
- string form: `"provider/model"` , overrides only `agents.defaults.model.primary`
2026-01-09 16:14:11 +01:00
- object form: `{ primary, fallbacks }` (fallbacks override `agents.defaults.model.fallbacks` ; `[]` disables global fallbacks for that agent)
2026-01-09 12:44:23 +00:00
- `identity` : per-agent name/theme/emoji (used for mention patterns + ack reactions).
- `groupChat` : per-agent mention-gating (`mentionPatterns` ).
- `sandbox` : per-agent sandbox config (overrides `agents.defaults.sandbox` ).
2026-01-07 11:58:49 +01:00
- `mode` : `"off"` | `"non-main"` | `"all"`
2026-01-07 12:24:12 +01:00
- `workspaceAccess` : `"none"` | `"ro"` | `"rw"`
2026-01-07 11:58:49 +01:00
- `scope` : `"session"` | `"agent"` | `"shared"`
- `workspaceRoot` : custom sandbox workspace root
2026-01-08 01:06:09 +01:00
- `docker` : per-agent docker overrides (e.g. `image` , `network` , `env` , `setupCommand` , limits; ignored when `scope: "shared"` )
2026-01-08 01:17:49 +01:00
- `browser` : per-agent sandboxed browser overrides (ignored when `scope: "shared"` )
- `prune` : per-agent sandbox pruning overrides (ignored when `scope: "shared"` )
2026-01-08 06:55:28 +00:00
- `subagents` : per-agent sub-agent defaults.
- `allowAgents` : allowlist of agent ids for `sessions_spawn` from this agent (`["*"]` = allow any; default: only same agent)
2026-01-09 12:44:23 +00:00
- `tools` : per-agent tool restrictions (applied before sandbox tool policy).
2026-01-13 06:28:15 +00:00
- `profile` : base tool profile (applied before allow/deny)
2026-01-07 11:58:49 +01:00
- `allow` : array of allowed tool names
- `deny` : array of denied tool names (deny wins)
2026-01-09 12:44:23 +00:00
- `agents.defaults` : shared agent defaults (model, workspace, sandbox, etc.).
- `bindings[]` : routes inbound messages to an `agentId` .
2026-01-13 07:15:57 +00:00
- `match.channel` (required)
2026-01-06 18:25:52 +00:00
- `match.accountId` (optional; `*` = any account; omitted = default account)
- `match.peer` (optional; `{ kind: dm|group|channel, id }` )
2026-01-13 07:15:57 +00:00
- `match.guildId` / `match.teamId` (optional; channel-specific)
2026-01-06 18:25:52 +00:00
Deterministic match order:
1) `match.peer`
2) `match.guildId`
3) `match.teamId`
4) `match.accountId` (exact, no peer/guild/team)
2026-01-13 07:15:57 +00:00
5) `match.accountId: "*"` (channel-wide, no peer/guild/team)
2026-01-09 12:44:23 +00:00
6) default agent (`agents.list[].default` , else first list entry, else `"main"` )
2026-01-06 18:25:52 +00:00
2026-01-09 12:44:23 +00:00
Within each match tier, the first matching entry in `bindings` wins.
2026-01-06 18:25:52 +00:00
2026-01-07 20:31:23 +01:00
#### Per-agent access profiles (multi-agent)
Each agent can carry its own sandbox + tool policy. Use this to mix access
levels in one gateway:
- **Full access** (personal agent)
- **Read-only** tools + workspace
- **No filesystem access** (messaging/session tools only)
See [Multi-Agent Sandbox & Tools ](/multi-agent-sandbox-tools ) for precedence and
additional examples.
Full access (no sandbox):
```json5
{
2026-01-09 12:44:23 +00:00
agents: {
list: [
{
id: "personal",
2026-01-07 20:31:23 +01:00
workspace: "~/clawd-personal",
sandbox: { mode: "off" }
}
2026-01-09 12:44:23 +00:00
]
2026-01-07 20:31:23 +01:00
}
}
```
Read-only tools + read-only workspace:
```json5
{
2026-01-09 12:44:23 +00:00
agents: {
list: [
{
id: "family",
2026-01-07 20:31:23 +01:00
workspace: "~/clawd-family",
sandbox: {
mode: "all",
scope: "agent",
workspaceAccess: "ro"
},
tools: {
2026-01-09 23:35:35 +00:00
allow: ["read", "sessions_list", "sessions_history", "sessions_send", "sessions_spawn", "session_status"],
2026-01-12 03:42:49 +00:00
deny: ["write", "edit", "apply_patch", "exec", "process", "browser"]
2026-01-07 20:31:23 +01:00
}
}
2026-01-09 12:44:23 +00:00
]
2026-01-07 20:31:23 +01:00
}
}
```
No filesystem access (messaging/session tools enabled):
```json5
{
2026-01-09 12:44:23 +00:00
agents: {
list: [
{
id: "public",
2026-01-07 20:31:23 +01:00
workspace: "~/clawd-public",
sandbox: {
mode: "all",
scope: "agent",
workspaceAccess: "none"
},
tools: {
2026-01-09 23:35:35 +00:00
allow: ["sessions_list", "sessions_history", "sessions_send", "sessions_spawn", "session_status", "whatsapp", "telegram", "slack", "discord", "gateway"],
2026-01-12 03:42:49 +00:00
deny: ["read", "write", "edit", "apply_patch", "exec", "process", "browser", "canvas", "nodes", "cron", "gateway", "image"]
2026-01-07 20:31:23 +01:00
}
}
2026-01-09 12:44:23 +00:00
]
2026-01-07 20:31:23 +01:00
}
}
```
2026-01-06 18:25:52 +00:00
Example: two WhatsApp accounts → two agents:
```json5
{
2026-01-09 12:44:23 +00:00
agents: {
list: [
{ id: "home", default: true, workspace: "~/clawd-home" },
{ id: "work", workspace: "~/clawd-work" }
]
2026-01-06 18:25:52 +00:00
},
2026-01-09 12:44:23 +00:00
bindings: [
2026-01-13 07:15:57 +00:00
{ agentId: "home", match: { channel: "whatsapp", accountId: "personal" } },
{ agentId: "work", match: { channel: "whatsapp", accountId: "biz" } }
2026-01-09 12:44:23 +00:00
],
2026-01-13 06:16:43 +00:00
channels: {
whatsapp: {
accounts: {
personal: {},
biz: {},
}
2026-01-06 18:25:52 +00:00
}
}
}
```
2026-01-09 12:44:23 +00:00
### `tools.agentToAgent` (optional)
2026-01-06 18:25:52 +00:00
Agent-to-agent messaging is opt-in:
```json5
{
2026-01-09 12:44:23 +00:00
tools: {
2026-01-06 18:25:52 +00:00
agentToAgent: {
enabled: false,
allow: ["home", "work"]
}
}
}
```
2026-01-09 12:44:23 +00:00
### `messages.queue`
2025-12-26 13:35:44 +01:00
Controls how inbound messages behave when an agent run is already active.
```json5
{
2026-01-09 12:44:23 +00:00
messages: {
2025-12-26 13:35:44 +01:00
queue: {
2026-01-03 04:26:36 +01:00
mode: "collect", // steer | followup | collect | steer-backlog (steer+backlog ok) | interrupt (queue=steer legacy)
debounceMs: 1000,
cap: 20,
drop: "summarize", // old | new | summarize
2026-01-13 07:15:57 +00:00
byChannel: {
2026-01-03 04:26:36 +01:00
whatsapp: "collect",
telegram: "collect",
2026-01-03 18:44:07 +01:00
discord: "collect",
2026-01-03 04:26:36 +01:00
imessage: "collect",
webchat: "collect"
2025-12-26 13:35:44 +01:00
}
}
}
}
```
2026-01-15 15:07:19 -08:00
### `messages.inbound`
Debounce rapid inbound messages from the **same sender** so multiple back-to-back
messages become a single agent turn. Debouncing is scoped per channel + conversation
and uses the most recent message for reply threading/IDs.
```json5
{
messages: {
inbound: {
debounceMs: 2000, // 0 disables
byChannel: {
whatsapp: 5000,
slack: 1500,
discord: 1500
}
}
}
}
```
Notes:
- Debounce batches **text-only** messages; media/attachments flush immediately.
- Control commands (e.g. `/queue` , `/new` ) bypass debouncing so they stay standalone.
2026-01-06 14:17:56 -06:00
### `commands` (chat command handling)
Controls how chat commands are enabled across connectors.
```json5
{
commands: {
2026-01-12 15:05:01 +05:30
native: "auto", // register native commands when supported (auto)
2026-01-06 14:17:56 -06:00
text: true, // parse slash commands in chat messages
2026-01-12 15:05:01 +05:30
bash: false, // allow ! (alias: /bash) (host-only; requires tools.elevated allowlists)
bashForegroundMs: 2000, // bash foreground window (0 backgrounds immediately)
2026-01-11 02:17:10 +01:00
config: false, // allow /config (writes to disk)
debug: false, // allow /debug (runtime-only overrides)
2026-01-09 05:49:11 +00:00
restart: false, // allow /restart + gateway restart tool
2026-01-06 14:17:56 -06:00
useAccessGroups: true // enforce access-group allowlists/policies for commands
}
}
```
Notes:
- Text commands must be sent as a **standalone** message and use the leading `/` (no plain-text aliases).
- `commands.text: false` disables parsing chat messages for commands.
2026-01-13 06:16:43 +00:00
- `commands.native: "auto"` (default) turns on native commands for Discord/Telegram and leaves Slack off; unsupported channels stay text-only.
- Set `commands.native: true|false` to force all, or override per channel with `channels.discord.commands.native` , `channels.telegram.commands.native` , `channels.slack.commands.native` (bool or `"auto"` ). `false` clears previously registered commands on Discord/Telegram at startup; Slack commands are managed in the Slack app.
2026-01-16 08:20:48 +00:00
- `channels.telegram.customCommands` adds extra Telegram bot menu entries. Names are normalized; conflicts with native commands are ignored.
2026-01-13 06:16:43 +00:00
- `commands.bash: true` enables `! <cmd>` to run host shell commands (`/bash <cmd>` also works as an alias). Requires `tools.elevated.enabled` and allowlisting the sender in `tools.elevated.allowFrom.<channel>` .
2026-01-12 15:05:01 +05:30
- `commands.bashForegroundMs` controls how long bash waits before backgrounding. While a bash job is running, new `! <cmd>` requests are rejected (one at a time).
2026-01-11 02:17:10 +01:00
- `commands.config: true` enables `/config` (reads/writes `clawdbot.json` ).
2026-01-15 01:41:11 +00:00
- `channels.<provider>.configWrites` gates config mutations initiated by that channel (default: true). This applies to `/config set|unset` plus provider-specific auto-migrations (Telegram supergroup ID changes, Slack channel ID changes).
2026-01-11 02:17:10 +01:00
- `commands.debug: true` enables `/debug` (runtime-only overrides).
2026-01-09 05:49:11 +00:00
- `commands.restart: true` enables `/restart` and the gateway tool restart action.
2026-01-06 14:17:56 -06:00
- `commands.useAccessGroups: false` allows commands to bypass access-group allowlists/policies.
2026-01-13 07:15:57 +00:00
### `web` (WhatsApp web channel runtime)
2025-12-26 16:54:53 +00:00
2026-01-13 07:15:57 +00:00
WhatsApp runs through the gateway’ s web channel (Baileys Web). It starts automatically when a linked session exists.
2025-12-26 16:54:53 +00:00
Set `web.enabled: false` to keep it off by default.
```json5
{
web: {
enabled: true,
heartbeatSeconds: 60,
reconnect: {
initialMs: 2000,
maxMs: 120000,
factor: 1.4,
jitter: 0.2,
maxAttempts: 0
}
}
}
```
2026-01-13 06:16:43 +00:00
### `channels.telegram` (bot transport)
2025-12-26 16:54:53 +00:00
2026-01-17 00:12:41 +00:00
Clawdbot starts Telegram only when a `channels.telegram` config section exists. The bot token is resolved from `channels.telegram.botToken` (or `channels.telegram.tokenFile` ), with `TELEGRAM_BOT_TOKEN` as a fallback for the default account.
2026-01-13 06:16:43 +00:00
Set `channels.telegram.enabled: false` to disable automatic startup.
Multi-account support lives under `channels.telegram.accounts` (see the multi-account section above). Env tokens only apply to the default account.
2026-01-15 01:41:11 +00:00
Set `channels.telegram.configWrites: false` to block Telegram-initiated config writes (including supergroup ID migrations and `/config set|unset` ).
2025-12-26 16:54:53 +00:00
```json5
{
2026-01-13 06:16:43 +00:00
channels: {
telegram: {
enabled: true,
botToken: "your-bot-token",
dmPolicy: "pairing", // pairing | allowlist | open | disabled
allowFrom: ["tg:123456789"], // optional; "open" requires ["*"]
groups: {
"*": { requireMention: true },
"-1001234567890": {
allowFrom: ["@admin "],
systemPrompt: "Keep answers brief.",
topics: {
"99": {
requireMention: false,
skills: ["search"],
systemPrompt: "Stay on topic."
}
2026-01-07 11:59:48 +01:00
}
2026-01-07 11:23:09 +01:00
}
2026-01-13 06:16:43 +00:00
},
2026-01-16 08:20:48 +00:00
customCommands: [
{ command: "backup", description: "Git backup" },
{ command: "generate", description: "Create an image" }
],
2026-01-13 06:16:43 +00:00
historyLimit: 50, // include last N group messages as context (0 disables)
replyToMode: "first", // off | first | all
streamMode: "partial", // off | partial | block (draft streaming; separate from block streaming)
draftChunk: { // optional; only for streamMode=block
minChars: 200,
maxChars: 800,
breakPreference: "paragraph" // paragraph | newline | sentence
},
actions: { reactions: true, sendMessage: true }, // tool action gates (false disables)
2026-01-16 20:51:39 +00:00
reactionNotifications: "own", // off | own | all
2026-01-13 06:16:43 +00:00
mediaMaxMb: 5,
retry: { // outbound retry policy
attempts: 3,
minDelayMs: 400,
maxDelayMs: 30000,
jitter: 0.1
},
proxy: "socks5://localhost:9050",
webhookUrl: "https://example.com/telegram-webhook",
webhookSecret: "secret",
webhookPath: "/telegram-webhook"
}
2025-12-26 16:54:53 +00:00
}
}
```
2026-01-07 11:08:11 +01:00
Draft streaming notes:
- Uses Telegram `sendMessageDraft` (draft bubble, not a real message).
- Requires **private chat topics** (message_thread_id in DMs; bot has topics enabled).
- `/reasoning stream` streams reasoning into the draft, then sends the final answer.
2026-01-07 17:48:19 +00:00
Retry policy defaults and behavior are documented in [Retry policy ](/concepts/retry ).
2026-01-07 11:08:11 +01:00
2026-01-13 06:16:43 +00:00
### `channels.discord` (bot transport)
2025-12-15 10:11:18 -06:00
Configure the Discord bot by setting the bot token and optional gating:
2026-01-13 06:16:43 +00:00
Multi-account support lives under `channels.discord.accounts` (see the multi-account section above). Env tokens only apply to the default account.
2025-12-15 10:11:18 -06:00
```json5
{
2026-01-13 06:16:43 +00:00
channels: {
discord: {
enabled: true,
token: "your-bot-token",
mediaMaxMb: 8, // clamp inbound media size
allowBots: false, // allow bot-authored messages
actions: { // tool action gates (false disables)
reactions: true,
stickers: true,
polls: true,
permissions: true,
messages: true,
threads: true,
pins: true,
search: true,
memberInfo: true,
roleInfo: true,
roles: false,
channelInfo: true,
voiceStatus: true,
events: true,
moderation: false
},
replyToMode: "off", // off | first | all
dm: {
enabled: true, // disable all DMs when false
policy: "pairing", // pairing | allowlist | open | disabled
allowFrom: ["1234567890", "steipete"], // optional DM allowlist ("open" requires ["*"])
groupEnabled: false, // enable group DMs
groupChannels: ["clawd-dm"] // optional group DM allowlist
},
guilds: {
"123456789012345678": { // guild id (preferred) or slug
slug: "friends-of-clawd",
requireMention: false, // per-guild default
reactionNotifications: "own", // off | own | all | allowlist
users: ["987654321098765432"], // optional per-guild user allowlist
channels: {
general: { allow: true },
help: {
allow: true,
requireMention: true,
users: ["987654321098765432"],
skills: ["docs"],
systemPrompt: "Short answers only."
}
2026-01-07 11:23:09 +01:00
}
2026-01-02 11:15:52 +01:00
}
2026-01-13 06:16:43 +00:00
},
historyLimit: 20, // include last N guild messages as context
textChunkLimit: 2000, // optional outbound text chunk size (chars)
maxLinesPerMessage: 17, // soft max lines per message (Discord UI clipping)
retry: { // outbound retry policy
attempts: 3,
minDelayMs: 500,
maxDelayMs: 30000,
jitter: 0.1
2026-01-02 11:15:52 +01:00
}
2026-01-07 17:48:19 +00:00
}
2025-12-15 10:11:18 -06:00
}
}
```
2026-01-17 00:12:41 +00:00
Clawdbot starts Discord only when a `channels.discord` config section exists. The token is resolved from `channels.discord.token` , with `DISCORD_BOT_TOKEN` as a fallback for the default account (unless `channels.discord.enabled` is `false` ). Use `user:<id>` (DM) or `channel:<id>` (guild channel) when specifying delivery targets for cron/CLI commands; bare numeric IDs are ambiguous and rejected.
2026-01-02 11:15:52 +01:00
Guild slugs are lowercase with spaces replaced by `-` ; channel keys use the slugged channel name (no leading `#` ). Prefer guild ids as keys to avoid rename ambiguity.
2026-01-13 06:16:43 +00:00
Bot-authored messages are ignored by default. Enable with `channels.discord.allowBots` (own messages are still filtered to prevent self-reply loops).
2026-01-03 12:31:50 -06:00
Reaction notification modes:
- `off` : no reaction events.
2026-01-03 18:48:10 +00:00
- `own` : reactions on the bot's own messages (default).
2026-01-03 12:31:50 -06:00
- `all` : all reactions on all messages.
2026-01-03 18:48:10 +00:00
- `allowlist` : reactions from `guilds.<id>.users` on all messages (empty list disables).
2026-01-13 06:16:43 +00:00
Outbound text is chunked by `channels.discord.textChunkLimit` (default 2000). Discord clients can clip very tall messages, so `channels.discord.maxLinesPerMessage` (default 17) splits long multi-line replies even when under 2000 chars.
2026-01-07 17:48:19 +00:00
Retry policy defaults and behavior are documented in [Retry policy ](/concepts/retry ).
2026-01-03 01:27:37 +01:00
2026-01-13 06:16:43 +00:00
### `channels.slack` (socket mode)
2026-01-03 23:12:11 -06:00
Slack runs in Socket Mode and requires both a bot token and app token:
```json5
{
2026-01-13 06:16:43 +00:00
channels: {
slack: {
2026-01-03 23:12:11 -06:00
enabled: true,
2026-01-13 06:16:43 +00:00
botToken: "xoxb-...",
appToken: "xapp-...",
dm: {
enabled: true,
policy: "pairing", // pairing | allowlist | open | disabled
allowFrom: ["U123", "U456", "*"], // optional; "open" requires ["*"]
groupEnabled: false,
groupChannels: ["G123"]
},
channels: {
C123: { allow: true, requireMention: true, allowBots: false },
"#general ": {
allow: true,
requireMention: true,
allowBots: false,
users: ["U123"],
skills: ["docs"],
systemPrompt: "Short answers only."
}
},
historyLimit: 50, // include last N channel/group messages as context (0 disables)
allowBots: false,
reactionNotifications: "own", // off | own | all | allowlist
reactionAllowlist: ["U123"],
replyToMode: "off", // off | first | all
2026-01-15 08:01:15 +00:00
thread: {
historyScope: "thread", // thread | channel
inheritParent: false
},
2026-01-13 06:16:43 +00:00
actions: {
reactions: true,
messages: true,
pins: true,
memberInfo: true,
emojiList: true
},
slashCommand: {
enabled: true,
name: "clawd",
sessionPrefix: "slack:slash",
ephemeral: true
},
textChunkLimit: 4000,
mediaMaxMb: 20
}
2026-01-03 23:12:11 -06:00
}
}
```
2026-01-13 06:16:43 +00:00
Multi-account support lives under `channels.slack.accounts` (see the multi-account section above). Env tokens only apply to the default account.
2026-01-08 01:18:37 +01:00
2026-01-04 14:32:47 +00:00
Clawdbot starts Slack when the provider is enabled and both tokens are set (via config or `SLACK_BOT_TOKEN` + `SLACK_APP_TOKEN` ). Use `user:<id>` (DM) or `channel:<id>` when specifying delivery targets for cron/CLI commands.
2026-01-15 01:41:11 +00:00
Set `channels.slack.configWrites: false` to block Slack-initiated config writes (including channel ID migrations and `/config set|unset` ).
2026-01-03 23:12:11 -06:00
2026-01-13 06:16:43 +00:00
Bot-authored messages are ignored by default. Enable with `channels.slack.allowBots` or `channels.slack.channels.<id>.allowBots` .
2026-01-08 08:49:16 +01:00
2026-01-03 23:12:11 -06:00
Reaction notification modes:
- `off` : no reaction events.
- `own` : reactions on the bot's own messages (default).
- `all` : all reactions on all messages.
2026-01-13 06:16:43 +00:00
- `allowlist` : reactions from `channels.slack.reactionAllowlist` on all messages (empty list disables).
2026-01-03 23:12:11 -06:00
2026-01-15 08:01:15 +00:00
Thread session isolation:
- `channels.slack.thread.historyScope` controls whether thread history is per-thread (`thread` , default) or shared across the channel (`channel` ).
- `channels.slack.thread.inheritParent` controls whether new thread sessions inherit the parent channel transcript (default: false).
2026-01-03 23:12:11 -06:00
Slack action groups (gate `slack` tool actions):
| Action group | Default | Notes |
| --- | --- | --- |
| reactions | enabled | React + list reactions |
| messages | enabled | Read/send/edit/delete |
| pins | enabled | Pin/unpin/list |
| memberInfo | enabled | Member info |
| emojiList | enabled | Custom emoji list |
2026-01-09 23:53:28 +01:00
2026-01-13 06:16:43 +00:00
### `channels.signal` (signal-cli)
2026-01-09 23:53:28 +01:00
Signal reactions can emit system events (shared reaction tooling):
```json5
{
2026-01-13 06:16:43 +00:00
channels: {
signal: {
reactionNotifications: "own", // off | own | all | allowlist
reactionAllowlist: ["+15551234567", "uuid:123e4567-e89b-12d3-a456-426614174000"],
historyLimit: 50 // include last N group messages as context (0 disables)
}
2026-01-09 23:53:28 +01:00
}
}
```
Reaction notification modes:
- `off` : no reaction events.
- `own` : reactions on the bot's own messages (default).
- `all` : all reactions on all messages.
2026-01-13 06:16:43 +00:00
- `allowlist` : reactions from `channels.signal.reactionAllowlist` on all messages (empty list disables).
2026-01-09 23:53:28 +01:00
2026-01-13 06:16:43 +00:00
### `channels.imessage` (imsg CLI)
2026-01-02 01:11:04 +01:00
2026-01-04 14:32:47 +00:00
Clawdbot spawns `imsg rpc` (JSON-RPC over stdio). No daemon or port required.
2026-01-02 01:11:04 +01:00
```json5
{
2026-01-13 06:16:43 +00:00
channels: {
imessage: {
enabled: true,
cliPath: "imsg",
dbPath: "~/Library/Messages/chat.db",
2026-01-17 00:39:48 +00:00
remoteHost: "user@gateway -host", // SCP for remote attachments when using SSH wrapper
2026-01-13 06:16:43 +00:00
dmPolicy: "pairing", // pairing | allowlist | open | disabled
allowFrom: ["+15555550123", "user@example .com", "chat_id:123"],
historyLimit: 50, // include last N group messages as context (0 disables)
includeAttachments: false,
mediaMaxMb: 16,
service: "auto",
region: "US"
}
2026-01-02 01:11:04 +01:00
}
}
```
2026-01-13 06:16:43 +00:00
Multi-account support lives under `channels.imessage.accounts` (see the multi-account section above).
2026-01-08 01:18:37 +01:00
2026-01-02 01:11:04 +01:00
Notes:
- Requires Full Disk Access to the Messages DB.
- The first send will prompt for Messages automation permission.
- Prefer `chat_id:<id>` targets. Use `imsg chats --limit 20` to list chats.
2026-01-13 06:16:43 +00:00
- `channels.imessage.cliPath` can point to a wrapper script (e.g. `ssh` to another Mac that runs `imsg rpc` ); use SSH keys to avoid password prompts.
2026-01-17 00:39:48 +00:00
- For remote SSH wrappers, set `channels.imessage.remoteHost` to fetch attachments via SCP when `includeAttachments` is enabled.
2026-01-08 01:18:37 +01:00
Example wrapper:
```bash
#!/usr/bin/env bash
2026-01-17 00:39:48 +00:00
exec ssh -T gateway-host imsg "$@"
2026-01-08 01:18:37 +01:00
```
2026-01-02 01:11:04 +01:00
2026-01-09 12:44:23 +00:00
### `agents.defaults.workspace`
2025-12-13 02:34:11 +00:00
2025-12-17 11:29:12 +01:00
Sets the **single global workspace directory** used by the agent for file operations.
2025-12-13 02:34:11 +00:00
2025-12-17 11:29:12 +01:00
Default: `~/clawd` .
2025-12-13 02:34:11 +00:00
2025-12-17 11:29:12 +01:00
```json5
{
2026-01-09 12:44:23 +00:00
agents: { defaults: { workspace: "~/clawd" } }
2025-12-17 11:29:12 +01:00
}
```
2025-12-14 04:21:27 +00:00
2026-01-09 12:44:23 +00:00
If `agents.defaults.sandbox` is enabled, non-main sessions can override this with their
own per-scope workspaces under `agents.defaults.sandbox.workspaceRoot` .
2026-01-03 21:35:44 +01:00
2026-01-09 12:44:23 +00:00
### `agents.defaults.skipBootstrap`
2026-01-06 19:50:06 +01:00
Disables automatic creation of the workspace bootstrap files (`AGENTS.md` , `SOUL.md` , `TOOLS.md` , `IDENTITY.md` , `USER.md` , and `BOOTSTRAP.md` ).
Use this for pre-seeded deployments where your workspace files come from a repo.
```json5
{
2026-01-09 12:44:23 +00:00
agents: { defaults: { skipBootstrap: true } }
2026-01-06 19:50:06 +01:00
}
```
2026-01-13 04:24:17 +00:00
### `agents.defaults.bootstrapMaxChars`
Max characters of each workspace bootstrap file injected into the system prompt
before truncation. Default: `20000` .
When a file exceeds this limit, Clawdbot logs a warning and injects a truncated
head/tail with a marker.
```json5
{
agents: { defaults: { bootstrapMaxChars: 20000 } }
}
```
2026-01-09 12:44:23 +00:00
### `agents.defaults.userTimezone`
2026-01-05 23:02:13 +00:00
Sets the user’ s timezone for **system prompt context** (not for timestamps in
message envelopes). If unset, Clawdbot uses the host timezone at runtime.
```json5
{
2026-01-09 12:44:23 +00:00
agents: { defaults: { userTimezone: "America/Chicago" } }
2026-01-05 23:02:13 +00:00
}
```
2026-01-15 22:26:31 +00:00
### `agents.defaults.timeFormat`
Controls the **time format** shown in the system prompt’ s Current Date & Time section.
Default: `auto` (OS preference).
```json5
{
agents: { defaults: { timeFormat: "auto" } } // auto | 12 | 24
}
```
2025-12-24 00:22:57 +00:00
### `messages`
2026-01-06 03:28:35 +00:00
Controls inbound/outbound prefixes and optional ack reactions.
2026-01-09 19:03:17 +00:00
See [Messages ](/concepts/messages ) for queueing, sessions, and streaming context.
2025-12-24 00:22:57 +00:00
```json5
{
messages: {
2026-01-09 19:28:59 +00:00
responsePrefix: "🦞", // or "auto"
2026-01-06 03:28:35 +00:00
ackReaction: "👀",
2026-01-10 02:11:51 +01:00
ackReactionScope: "group-mentions",
removeAckAfterReply: false
2025-12-24 00:22:57 +00:00
}
}
```
2026-01-05 19:43:54 +01:00
`responsePrefix` is applied to **all outbound replies** (tool summaries, block
2026-01-13 08:25:22 +00:00
streaming, final replies) across channels unless already present.
2026-01-05 19:43:54 +01:00
2026-01-16 10:53:32 +00:00
If `messages.responsePrefix` is unset, no prefix is applied by default. WhatsApp self-chat
replies are the exception: they default to `[{identity.name}]` when set, otherwise
`[clawdbot]` , so same-phone conversations stay legible.
2026-01-09 19:28:59 +00:00
Set it to `"auto"` to derive `[{identity.name}]` for the routed agent (when set).
2026-01-09 16:39:32 +01:00
feat: add dynamic template variables to messages.responsePrefix (#923)
Adds support for template variables in `messages.responsePrefix` that
resolve dynamically at runtime with the actual model used (including
after fallback).
Supported variables (case-insensitive):
- {model} - short model name (e.g., "claude-opus-4-5", "gpt-4o")
- {modelFull} - full model identifier (e.g., "anthropic/claude-opus-4-5")
- {provider} - provider name (e.g., "anthropic", "openai")
- {thinkingLevel} or {think} - thinking level ("high", "low", "off")
- {identity.name} or {identityName} - agent identity name
Example: "[{model} | think:{thinkingLevel}]" → "[claude-opus-4-5 | think:high]"
Variables show the actual model used after fallback, not the intended
model. Unresolved variables remain as literal text.
Implementation:
- New module: src/auto-reply/reply/response-prefix-template.ts
- Template interpolation in normalize-reply.ts via context provider
- onModelSelected callback in agent-runner-execution.ts
- Updated all 6 provider message handlers (web, signal, discord,
telegram, slack, imessage)
- 27 unit tests covering all variables and edge cases
- Documentation in docs/gateway/configuration.md and JSDoc
Fixes #923
2026-01-14 23:05:08 -05:00
#### Template variables
The `responsePrefix` string can include template variables that resolve dynamically:
| Variable | Description | Example |
|----------|-------------|---------|
| `{model}` | Short model name | `claude-opus-4-5` , `gpt-4o` |
| `{modelFull}` | Full model identifier | `anthropic/claude-opus-4-5` |
| `{provider}` | Provider name | `anthropic` , `openai` |
| `{thinkingLevel}` | Current thinking level | `high` , `low` , `off` |
| `{identity.name}` | Agent identity name | (same as `"auto"` mode) |
Variables are case-insensitive (`{MODEL}` = `{model}` ). `{think}` is an alias for `{thinkingLevel}` .
Unresolved variables remain as literal text.
```json5
{
messages: {
responsePrefix: "[{model} | think:{thinkingLevel}]"
}
}
```
Example output: `[claude-opus-4-5 | think:high] Here's my response...`
2026-01-13 06:16:43 +00:00
WhatsApp inbound prefix is configured via `channels.whatsapp.messagePrefix` (deprecated:
2026-01-09 19:28:59 +00:00
`messages.messagePrefix` ). Default stays **unchanged** : `"[clawdbot]"` when
2026-01-13 06:16:43 +00:00
`channels.whatsapp.allowFrom` is empty, otherwise `""` (no prefix). When using
2026-01-09 19:28:59 +00:00
`"[clawdbot]"` , Clawdbot will instead use `[{identity.name}]` when the routed
agent has `identity.name` set.
2026-01-09 16:39:32 +01:00
2026-01-06 03:28:35 +00:00
`ackReaction` sends a best-effort emoji reaction to acknowledge inbound messages
2026-01-13 08:25:22 +00:00
on channels that support reactions (Slack/Discord/Telegram). Defaults to the
2026-01-09 12:44:23 +00:00
active agent’ s `identity.emoji` when set, otherwise `"👀"` . Set it to `""` to disable.
2026-01-06 03:28:35 +00:00
`ackReactionScope` controls when reactions fire:
- `group-mentions` (default): only when a group/room requires mentions **and** the bot was mentioned
- `group-all` : all group/room messages
- `direct` : direct messages only
- `all` : all messages
2026-01-10 02:11:51 +01:00
`removeAckAfterReply` removes the bot’ s ack reaction after a reply is sent
(Slack/Discord/Telegram only). Default: `false` .
2025-12-29 23:21:05 +01:00
### `talk`
Defaults for Talk mode (macOS/iOS/Android). Voice IDs fall back to `ELEVENLABS_VOICE_ID` or `SAG_VOICE_ID` when unset.
2025-12-30 01:57:45 +01:00
`apiKey` falls back to `ELEVENLABS_API_KEY` (or the gateway’ s shell profile) when unset.
2025-12-30 11:35:29 +01:00
`voiceAliases` lets Talk directives use friendly names (e.g. `"voice":"Clawd"` ).
2025-12-29 23:21:05 +01:00
```json5
{
talk: {
voiceId: "elevenlabs_voice_id",
2025-12-30 11:35:29 +01:00
voiceAliases: {
Clawd: "EXAVITQu4vr4xnSDxMaL",
Roger: "CwhRBWXzGAHq8TQ4Fs17"
},
2025-12-29 23:21:05 +01:00
modelId: "eleven_v3",
outputFormat: "mp3_44100_128",
2025-12-30 01:57:45 +01:00
apiKey: "elevenlabs_api_key",
2025-12-29 23:21:05 +01:00
interruptOnSpeech: true
}
}
```
2026-01-09 12:44:23 +00:00
### `agents.defaults`
2025-12-17 11:29:12 +01:00
2025-12-26 00:43:44 +01:00
Controls the embedded agent runtime (model/thinking/verbose/timeouts).
2026-01-09 12:44:23 +00:00
`agents.defaults.models` defines the configured model catalog (and acts as the allowlist for `/model` ).
`agents.defaults.model.primary` sets the default model; `agents.defaults.model.fallbacks` are global failovers.
`agents.defaults.imageModel` is optional and is **only used if the primary model lacks image input** .
Each `agents.defaults.models` entry can include:
2026-01-08 04:13:34 +01:00
- `alias` (optional model shortcut, e.g. `/opus` ).
- `params` (optional provider-specific API params passed through to the model request).
2026-01-11 23:55:14 +00:00
`params` is also applied to streaming runs (embedded agent + compaction). Supported keys today: `temperature` , `maxTokens` . These merge with call-time options; caller-supplied values win. `temperature` is an advanced knob—leave unset unless you know the model’ s defaults and need a change.
Example:
```json5
{
agents: {
defaults: {
models: {
"anthropic/claude-sonnet-4-5-20250929": {
params: { temperature: 0.6 }
},
"openai/gpt-5.2": {
params: { maxTokens: 8192 }
}
}
}
}
}
```
2026-01-08 04:13:34 +01:00
Z.AI GLM-4.x models automatically enable thinking mode unless you:
- set `--thinking off` , or
2026-01-09 12:44:23 +00:00
- define `agents.defaults.models["zai/<model>"].params.thinking` yourself.
2025-12-13 02:34:11 +00:00
2026-01-06 00:56:29 +00:00
Clawdbot also ships a few built-in alias shorthands. Defaults only apply when the model
2026-01-09 12:44:23 +00:00
is already present in `agents.defaults.models` :
2026-01-05 01:02:30 +01:00
- `opus` -> `anthropic/claude-opus-4-5`
- `sonnet` -> `anthropic/claude-sonnet-4-5`
- `gpt` -> `openai/gpt-5.2`
- `gpt-mini` -> `openai/gpt-5-mini`
- `gemini` -> `google/gemini-3-pro-preview`
- `gemini-flash` -> `google/gemini-3-flash-preview`
If you configure the same alias name (case-insensitive) yourself, your value wins (defaults never override).
2026-01-13 07:15:18 +00:00
Example: Opus 4.5 primary with MiniMax M2.1 fallback (hosted MiniMax):
```json5
{
agents: {
defaults: {
models: {
"anthropic/claude-opus-4-5": { alias: "opus" },
"minimax/MiniMax-M2.1": { alias: "minimax" }
},
model: {
primary: "anthropic/claude-opus-4-5",
fallbacks: ["minimax/MiniMax-M2.1"]
}
}
}
}
```
MiniMax auth: set `MINIMAX_API_KEY` (env) or configure `models.providers.minimax` .
2026-01-10 23:31:25 +00:00
#### `agents.defaults.cliBackends` (CLI fallback)
Optional CLI backends for text-only fallback runs (no tool calls). These are useful as a
backup path when API providers fail. Image pass-through is supported when you configure
an `imageArg` that accepts file paths.
Notes:
- CLI backends are **text-first** ; tools are always disabled.
- Sessions are supported when `sessionArg` is set; session ids are persisted per backend.
- For `claude-cli` , defaults are wired in. Override the command path if PATH is minimal
(launchd/systemd).
Example:
```json5
{
agents: {
defaults: {
cliBackends: {
"claude-cli": {
command: "/opt/homebrew/bin/claude"
},
"my-cli": {
command: "my-cli",
args: ["--json"],
output: "json",
modelArg: "--model",
sessionArg: "--session",
sessionMode: "existing",
systemPromptArg: "--system",
systemPromptWhen: "first",
imageArg: "--image",
imageMode: "repeat"
}
}
}
}
}
```
2025-12-13 02:34:11 +00:00
```json5
{
2026-01-09 12:44:23 +00:00
agents: {
defaults: {
models: {
"anthropic/claude-opus-4-5": { alias: "Opus" },
"anthropic/claude-sonnet-4-1": { alias: "Sonnet" },
"openrouter/deepseek/deepseek-r1:free": {},
"zai/glm-4.7": {
alias: "GLM",
params: {
thinking: {
type: "enabled",
clear_thinking: false
}
2026-01-08 04:13:34 +01:00
}
}
2026-01-09 12:44:23 +00:00
},
model: {
primary: "anthropic/claude-opus-4-5",
fallbacks: [
"openrouter/deepseek/deepseek-r1:free",
"openrouter/meta-llama/llama-3.3-70b-instruct:free"
]
},
imageModel: {
primary: "openrouter/qwen/qwen-2.5-vl-72b-instruct:free",
fallbacks: [
"openrouter/google/gemini-2.0-flash-vision:free"
]
},
thinkingDefault: "low",
verboseDefault: "off",
elevatedDefault: "on",
timeoutSeconds: 600,
mediaMaxMb: 5,
heartbeat: {
every: "30m",
target: "last"
},
maxConcurrent: 3,
subagents: {
2026-01-12 18:08:16 +00:00
model: "minimax/MiniMax-M2.1",
2026-01-09 12:44:23 +00:00
maxConcurrent: 1,
archiveAfterMinutes: 60
},
2026-01-12 02:49:55 +00:00
exec: {
2026-01-09 12:44:23 +00:00
backgroundMs: 10000,
timeoutSec: 1800,
cleanupMs: 1800000
},
contextTokens: 200000
}
2025-12-24 00:22:57 +00:00
}
2025-12-13 02:34:11 +00:00
}
```
2026-01-09 12:44:23 +00:00
#### `agents.defaults.contextPruning` (tool-result pruning)
2026-01-07 18:03:35 +01:00
2026-01-09 12:44:23 +00:00
`agents.defaults.contextPruning` prunes **old tool results** from the in-memory context right before a request is sent to the LLM.
2026-01-07 18:03:35 +01:00
It does **not** modify the session history on disk (`*.jsonl` remains complete).
This is intended to reduce token usage for chatty agents that accumulate large tool outputs over time.
High level:
- Never touches user/assistant messages.
- Protects the last `keepLastAssistants` assistant messages (no tool results after that point are pruned).
2026-01-07 18:15:54 +01:00
- Protects the bootstrap prefix (nothing before the first user message is pruned).
2026-01-07 18:03:35 +01:00
- Modes:
- `adaptive` : soft-trims oversized tool results (keep head/tail) when the estimated context ratio crosses `softTrimRatio` .
Then hard-clears the oldest eligible tool results when the estimated context ratio crosses `hardClearRatio` **and**
there’ s enough prunable tool-result bulk (`minPrunableToolChars` ).
- `aggressive` : always replaces eligible tool results before the cutoff with the `hardClear.placeholder` (no ratio checks).
Soft vs hard pruning (what changes in the context sent to the LLM):
- **Soft-trim**: only for *oversized* tool results. Keeps the beginning + end and inserts `...` in the middle.
- Before: `toolResult("…very long output…")`
- After: `toolResult("HEAD…\n...\n…TAIL\n\n[Tool result trimmed: …]")`
- **Hard-clear**: replaces the entire tool result with the placeholder.
- Before: `toolResult("…very long output…")`
- After: `toolResult("[Old tool result content cleared]")`
Notes / current limitations:
- Tool results containing **image blocks are skipped** (never trimmed/cleared) right now.
- The estimated “context ratio” is based on **characters** (approximate), not exact tokens.
- If the session doesn’ t contain at least `keepLastAssistants` assistant messages yet, pruning is skipped.
- In `aggressive` mode, `hardClear.enabled` is ignored (eligible tool results are always replaced with `hardClear.placeholder` ).
2026-01-08 22:23:03 +01:00
Default (adaptive):
2026-01-07 18:03:35 +01:00
```json5
{
2026-01-09 12:44:23 +00:00
agents: { defaults: { contextPruning: { mode: "adaptive" } } }
2026-01-07 18:03:35 +01:00
}
```
2026-01-08 22:23:03 +01:00
To disable:
```json5
{
2026-01-09 12:44:23 +00:00
agents: { defaults: { contextPruning: { mode: "off" } } }
2026-01-08 22:23:03 +01:00
}
```
2026-01-07 18:03:35 +01:00
Defaults (when `mode` is `"adaptive"` or `"aggressive"` ):
- `keepLastAssistants` : `3`
- `softTrimRatio` : `0.3` (adaptive only)
- `hardClearRatio` : `0.5` (adaptive only)
- `minPrunableToolChars` : `50000` (adaptive only)
- `softTrim` : `{ maxChars: 4000, headChars: 1500, tailChars: 1500 }` (adaptive only)
- `hardClear` : `{ enabled: true, placeholder: "[Old tool result content cleared]" }`
Example (aggressive, minimal):
```json5
{
2026-01-09 12:44:23 +00:00
agents: { defaults: { contextPruning: { mode: "aggressive" } } }
2026-01-07 18:03:35 +01:00
}
```
Example (adaptive tuned):
```json5
{
2026-01-09 12:44:23 +00:00
agents: {
defaults: {
contextPruning: {
mode: "adaptive",
keepLastAssistants: 3,
softTrimRatio: 0.3,
hardClearRatio: 0.5,
minPrunableToolChars: 50000,
softTrim: { maxChars: 4000, headChars: 1500, tailChars: 1500 },
hardClear: { enabled: true, placeholder: "[Old tool result content cleared]" },
// Optional: restrict pruning to specific tools (deny wins; supports "*" wildcards)
tools: { deny: ["browser", "canvas"] },
}
2026-01-07 18:03:35 +01:00
}
}
}
```
See [/concepts/session-pruning ](/concepts/session-pruning ) for behavior details.
2026-01-12 05:28:17 +00:00
#### `agents.defaults.compaction` (reserve headroom + memory flush)
2026-01-15 07:07:37 +00:00
`agents.defaults.compaction.mode` selects the compaction summarization strategy. Defaults to `default` ; set `safeguard` to enable chunked summarization for very long histories. See [/concepts/compaction ](/concepts/compaction ).
2026-01-10 18:34:40 -06:00
2026-01-12 05:28:17 +00:00
`agents.defaults.compaction.reserveTokensFloor` enforces a minimum `reserveTokens`
value for Pi compaction (default: `20000` ). Set it to `0` to disable the floor.
`agents.defaults.compaction.memoryFlush` runs a **silent** agentic turn before
auto-compaction, instructing the model to store durable memories on disk (e.g.
`memory/YYYY-MM-DD.md` ). It triggers when the session token estimate crosses a
soft threshold below the compaction limit.
Defaults:
- `memoryFlush.enabled` : `true`
- `memoryFlush.softThresholdTokens` : `4000`
- `memoryFlush.prompt` / `memoryFlush.systemPrompt` : built-in defaults with `NO_REPLY`
2026-01-12 06:33:14 +00:00
- Note: memory flush is skipped when the session workspace is read-only
(`agents.defaults.sandbox.workspaceAccess: "ro"` or `"none"` ).
2026-01-12 05:28:17 +00:00
Example (tuned):
```json5
{
agents: {
defaults: {
compaction: {
2026-01-10 18:34:40 -06:00
mode: "safeguard",
2026-01-12 05:28:17 +00:00
reserveTokensFloor: 24000,
memoryFlush: {
enabled: true,
softThresholdTokens: 6000,
systemPrompt: "Session nearing compaction. Store durable memories now.",
prompt: "Write any lasting notes to memory/YYYY-MM-DD.md; reply with NO_REPLY if nothing to store."
}
}
}
}
}
```
2026-01-03 16:45:53 +01:00
Block streaming:
2026-01-09 22:40:58 +01:00
- `agents.defaults.blockStreamingDefault` : `"on"` /`"off"` (default off).
2026-01-13 07:15:57 +00:00
- Channel overrides: `*.blockStreaming` (and per-account variants) to force block streaming on/off.
Non-Telegram channels require an explicit `*.blockStreaming: true` to enable block replies.
2026-01-09 12:44:23 +00:00
- `agents.defaults.blockStreamingBreak` : `"text_end"` or `"message_end"` (default: text_end).
- `agents.defaults.blockStreamingChunk` : soft chunking for streamed blocks. Defaults to
2026-01-03 16:45:53 +01:00
800– 1200 chars, prefers paragraph breaks (`\n\n` ), then newlines, then sentences.
Example:
```json5
{
2026-01-09 12:44:23 +00:00
agents: { defaults: { blockStreamingChunk: { minChars: 800, maxChars: 1200 } } }
2026-01-03 16:45:53 +01:00
}
```
2026-01-09 18:19:55 +00:00
- `agents.defaults.blockStreamingCoalesce` : merge streamed blocks before sending.
2026-01-09 22:31:12 +01:00
Defaults to `{ idleMs: 1000 }` and inherits `minChars` from `blockStreamingChunk`
2026-01-13 07:15:57 +00:00
with `maxChars` capped to the channel text limit. Signal/Slack/Discord default
2026-01-09 22:40:58 +01:00
to `minChars: 1500` unless overridden.
2026-01-13 07:15:57 +00:00
Channel overrides: `channels.whatsapp.blockStreamingCoalesce` , `channels.telegram.blockStreamingCoalesce` ,
2026-01-13 06:16:43 +00:00
`channels.discord.blockStreamingCoalesce` , `channels.slack.blockStreamingCoalesce` , `channels.signal.blockStreamingCoalesce` ,
`channels.imessage.blockStreamingCoalesce` , `channels.msteams.blockStreamingCoalesce` (and per-account variants).
2026-01-07 22:56:46 -05:00
- `agents.defaults.humanDelay` : randomized pause between **block replies** after the first.
Modes: `off` (default), `natural` (800– 2500ms), `custom` (use `minMs` /`maxMs` ).
Per-agent override: `agents.list[].humanDelay` .
Example:
```json5
{
agents: { defaults: { humanDelay: { mode: "natural" } } }
}
```
2026-01-07 17:15:53 +01:00
See [/concepts/streaming ](/concepts/streaming ) for behavior + chunking details.
2026-01-03 16:45:53 +01:00
2026-01-07 21:58:54 +00:00
Typing indicators:
2026-01-09 12:44:23 +00:00
- `agents.defaults.typingMode` : `"never" | "instant" | "thinking" | "message"` . Defaults to
2026-01-07 21:58:54 +00:00
`instant` for direct chats / mentions and `message` for unmentioned group chats.
2026-01-07 22:18:11 +00:00
- `session.typingMode` : per-session override for the mode.
2026-01-09 12:44:23 +00:00
- `agents.defaults.typingIntervalSeconds` : how often the typing signal is refreshed (default: 6s).
2026-01-07 21:58:54 +00:00
- `session.typingIntervalSeconds` : per-session override for the refresh interval.
See [/concepts/typing-indicators ](/concepts/typing-indicators ) for behavior details.
2026-01-09 12:44:23 +00:00
`agents.defaults.model.primary` should be set as `provider/model` (e.g. `anthropic/claude-opus-4-5` ).
Aliases come from `agents.defaults.models.*.alias` (e.g. `Opus` ).
2026-01-10 05:14:09 +01:00
If you omit the provider, Clawdbot currently assumes `anthropic` as a temporary
2025-12-26 00:43:44 +01:00
deprecation fallback.
2025-12-31 11:36:57 +01:00
Z.AI models are available as `zai/<model>` (e.g. `zai/glm-4.7` ) and require
`ZAI_API_KEY` (or legacy `Z_AI_API_KEY` ) in the environment.
2025-12-26 00:16:29 +01:00
2026-01-09 12:44:23 +00:00
`agents.defaults.heartbeat` configures periodic heartbeat runs:
2026-01-06 21:54:19 +00:00
- `every` : duration string (`ms` , `s` , `m` , `h` ); default unit minutes. Default:
`30m` . Set `0m` to disable.
2025-12-26 01:13:13 +01:00
- `model` : optional override model for heartbeat runs (`provider/model` ).
2026-01-10 22:26:20 +00:00
- `includeReasoning` : when `true` , heartbeats will also deliver the separate `Reasoning:` message when available (same shape as `/reasoning on` ). Default: `false` .
2026-01-13 07:15:57 +00:00
- `target` : optional delivery channel (`last` , `whatsapp` , `telegram` , `discord` , `slack` , `signal` , `imessage` , `none` ). Default: `last` .
- `to` : optional recipient override (channel-specific id, e.g. E.164 for WhatsApp, chat id for Telegram).
2026-01-16 00:46:07 +00:00
- `prompt` : optional override for the heartbeat body (default: `Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.` ). Overrides are sent verbatim; include a `Read HEARTBEAT.md` line if you still want the file read.
2026-01-12 11:06:37 +00:00
- `ackMaxChars` : max chars allowed after `HEARTBEAT_OK` before delivery (default: 300).
2025-12-26 01:13:13 +01:00
2026-01-16 00:46:07 +00:00
Per-agent heartbeats:
- Set `agents.list[].heartbeat` to enable or override heartbeat settings for a specific agent.
- If any agent entry defines `heartbeat` , **only those agents** run heartbeats; defaults
become the shared baseline for those agents.
2026-01-06 22:28:32 +00:00
Heartbeats run full agent turns. Shorter intervals burn more tokens; be mindful
of `every` , keep `HEARTBEAT.md` tiny, and/or choose a cheaper `model` .
2026-01-06 21:54:19 +00:00
2026-01-12 02:49:55 +00:00
`tools.exec` configures background exec defaults:
2026-01-03 20:15:02 +00:00
- `backgroundMs` : time before auto-background (ms, default 10000)
2025-12-25 17:58:19 +00:00
- `timeoutSec` : auto-kill after this runtime (seconds, default 1800)
- `cleanupMs` : how long to keep finished sessions in memory (ms, default 1800000)
2026-01-17 05:43:27 +00:00
- `notifyOnExit` : enqueue a system event + request heartbeat when backgrounded exec exits (default true)
2026-01-12 03:42:49 +00:00
- `applyPatch.enabled` : enable experimental `apply_patch` (OpenAI/OpenAI Codex only; default false)
- `applyPatch.allowModels` : optional allowlist of model ids (e.g. `gpt-5.2` or `openai/gpt-5.2` )
2026-01-17 05:43:27 +00:00
Note: `applyPatch` is only under `tools.exec` .
2025-12-25 17:58:19 +00:00
2026-01-15 04:07:29 +00:00
`tools.web` configures web search + fetch tools:
- `tools.web.search.enabled` (default: true when key is present)
2026-01-15 05:08:51 +00:00
- `tools.web.search.apiKey` (recommended: set via `clawdbot configure --section web` , or use `BRAVE_API_KEY` env var)
2026-01-15 04:07:29 +00:00
- `tools.web.search.maxResults` (1– 10, default 5)
- `tools.web.search.timeoutSeconds` (default 30)
- `tools.web.search.cacheTtlMinutes` (default 15)
2026-01-16 23:17:55 +00:00
- `tools.web.fetch.enabled` (default true)
2026-01-15 04:07:29 +00:00
- `tools.web.fetch.maxChars` (default 50000)
- `tools.web.fetch.timeoutSeconds` (default 30)
- `tools.web.fetch.cacheTtlMinutes` (default 15)
- `tools.web.fetch.userAgent` (optional override)
2026-01-16 23:17:55 +00:00
- `tools.web.fetch.readability` (default true; disable to use basic HTML cleanup only)
2026-01-17 00:00:15 +00:00
- `tools.web.fetch.firecrawl.enabled` (default true when an API key is set)
- `tools.web.fetch.firecrawl.apiKey` (optional; defaults to `FIRECRAWL_API_KEY` )
- `tools.web.fetch.firecrawl.baseUrl` (default https://api.firecrawl.dev)
- `tools.web.fetch.firecrawl.onlyMainContent` (default true)
- `tools.web.fetch.firecrawl.maxAgeMs` (optional)
- `tools.web.fetch.firecrawl.timeoutSeconds` (optional)
2026-01-15 04:07:29 +00:00
2026-01-17 03:52:37 +00:00
`tools.media` configures inbound media understanding (image/audio/video):
2026-01-17 04:38:20 +00:00
- `tools.media.models` : shared model list (capability-tagged; used after per-cap lists).
- `tools.media.concurrency` : max concurrent capability runs (default 2).
2026-01-17 03:52:37 +00:00
- `tools.media.image` / `tools.media.audio` / `tools.media.video` :
2026-01-17 04:38:20 +00:00
- `enabled` : opt-out switch (default true when models are configured).
2026-01-17 03:52:37 +00:00
- `prompt` : optional prompt override (image/video append a `maxChars` hint automatically).
- `maxChars` : max output characters (default 500 for image/video; unset for audio).
- `maxBytes` : max media size to send (defaults: image 10MB, audio 20MB, video 50MB).
- `timeoutSeconds` : request timeout (defaults: image 60s, audio 60s, video 120s).
- `language` : optional audio hint.
2026-01-17 04:38:20 +00:00
- `attachments` : attachment policy (`mode` , `maxAttachments` , `prefer` ).
2026-01-17 03:52:37 +00:00
- `scope` : optional gating (first match wins) with `match.channel` , `match.chatType` , or `match.keyPrefix` .
- `models` : ordered list of model entries; failures or oversize media fall back to the next entry.
- Each `models[]` entry:
- Provider entry (`type: "provider"` or omitted):
- `provider` : API provider id (`openai` , `anthropic` , `google` /`gemini` , `groq` , etc).
- `model` : model id override (required for image; defaults to `whisper-1` /`whisper-large-v3-turbo` for audio providers, and `gemini-3-flash-preview` for video).
- `profile` / `preferredProfile` : auth profile selection.
- CLI entry (`type: "cli"` ):
- `command` : executable to run.
- `args` : templated args (supports `{{MediaPath}}` , `{{Prompt}}` , `{{MaxChars}}` , etc).
2026-01-17 04:38:20 +00:00
- `capabilities` : optional list (`image` , `audio` , `video` ) to gate a shared entry. Defaults when omitted: `openai` /`anthropic` /`minimax` → image, `google` → image+audio+video, `groq` → audio.
2026-01-17 03:52:37 +00:00
- `prompt` , `maxChars` , `maxBytes` , `timeoutSeconds` , `language` can be overridden per entry.
If no models are configured (or `enabled: false` ), understanding is skipped; the model still receives the original attachments.
Provider auth follows the standard model auth order (auth profiles, env vars like `OPENAI_API_KEY` /`GROQ_API_KEY` /`GEMINI_API_KEY` , or `models.providers.*.apiKey` ).
Example:
```json5
{
tools: {
media: {
audio: {
enabled: true,
maxBytes: 20971520,
scope: {
default: "deny",
rules: [{ action: "allow", match: { chatType: "direct" } }]
},
models: [
{ provider: "openai", model: "whisper-1" },
{ type: "cli", command: "whisper", args: ["--model", "base", "{{MediaPath}}"] }
]
},
video: {
enabled: true,
maxBytes: 52428800,
models: [{ provider: "google", model: "gemini-3-flash-preview" }]
}
}
}
}
```
2026-01-09 12:44:23 +00:00
`agents.defaults.subagents` configures sub-agent defaults:
2026-01-12 18:08:16 +00:00
- `model` : default model for spawned sub-agents (string or `{ primary, fallbacks }` ). If omitted, sub-agents inherit the caller’ s model unless overridden per agent or per call.
2026-01-07 06:53:01 +01:00
- `maxConcurrent` : max concurrent sub-agent runs (default 1)
- `archiveAfterMinutes` : auto-archive sub-agent sessions after N minutes (default 60; set `0` to disable)
2026-01-09 12:44:23 +00:00
- Per-subagent tool policy: `tools.subagents.tools.allow` / `tools.subagents.tools.deny` (deny wins)
2026-01-07 06:53:01 +01:00
2026-01-13 06:28:15 +00:00
`tools.profile` sets a **base tool allowlist** before `tools.allow` /`tools.deny` :
- `minimal` : `session_status` only
- `coding` : `group:fs` , `group:runtime` , `group:sessions` , `group:memory` , `image`
- `messaging` : `group:messaging` , `sessions_list` , `sessions_history` , `sessions_send` , `session_status`
- `full` : no restriction (same as unset)
Per-agent override: `agents.list[].tools.profile` .
Example (messaging-only by default, allow Slack + Discord tools too):
```json5
{
tools: {
profile: "messaging",
allow: ["slack", "discord"]
}
}
```
Example (coding profile, but deny exec/process everywhere):
```json5
{
tools: {
profile: "coding",
deny: ["group:runtime"]
}
}
```
2026-01-13 09:59:36 +00:00
`tools.byProvider` lets you **further restrict** tools for specific providers (or a single `provider/model` ).
Per-agent override: `agents.list[].tools.byProvider` .
Order: base profile → provider profile → allow/deny policies.
Provider keys accept either `provider` (e.g. `google-antigravity` ) or `provider/model`
(e.g. `openai/gpt-5.2` ).
Example (keep global coding profile, but minimal tools for Google Antigravity):
```json5
{
tools: {
profile: "coding",
byProvider: {
"google-antigravity": { profile: "minimal" }
}
}
}
```
Example (provider/model-specific allowlist):
```json5
{
tools: {
allow: ["group:fs", "group:runtime", "sessions_list"],
byProvider: {
"openai/gpt-5.2": { allow: ["group:fs", "sessions_list"] }
}
}
}
```
2026-01-09 12:44:23 +00:00
`tools.allow` / `tools.deny` configure a global tool allow/deny policy (deny wins).
2026-01-05 00:05:21 +01:00
This is applied even when the Docker sandbox is **off** .
Example (disable browser/canvas everywhere):
```json5
{
2026-01-09 12:44:23 +00:00
tools: { deny: ["browser", "canvas"] }
2026-01-05 00:05:21 +01:00
}
```
2026-01-13 06:28:15 +00:00
Tool groups (shorthands) work in **global** and **per-agent** tool policies:
- `group:runtime` : `exec` , `bash` , `process`
- `group:fs` : `read` , `write` , `edit` , `apply_patch`
- `group:sessions` : `sessions_list` , `sessions_history` , `sessions_send` , `sessions_spawn` , `session_status`
- `group:memory` : `memory_search` , `memory_get`
2026-01-15 04:07:29 +00:00
- `group:web` : `web_search` , `web_fetch`
2026-01-13 06:28:15 +00:00
- `group:ui` : `browser` , `canvas`
- `group:automation` : `cron` , `gateway`
- `group:messaging` : `message`
- `group:nodes` : `nodes`
- `group:clawdbot` : all built-in Clawdbot tools (excludes provider plugins)
2026-01-12 02:49:55 +00:00
`tools.elevated` controls elevated (host) exec access:
2026-01-04 05:15:42 +00:00
- `enabled` : allow elevated mode (default true)
2026-01-13 07:15:57 +00:00
- `allowFrom` : per-channel allowlists (empty = disabled)
2026-01-04 05:15:42 +00:00
- `whatsapp` : E.164 numbers
- `telegram` : chat ids or usernames
2026-01-13 06:16:43 +00:00
- `discord` : user ids or usernames (falls back to `channels.discord.dm.allowFrom` if omitted)
2026-01-04 05:15:42 +00:00
- `signal` : E.164 numbers
- `imessage` : handles/chat ids
- `webchat` : session ids or usernames
Example:
```json5
{
2026-01-09 12:44:23 +00:00
tools: {
2026-01-04 05:15:42 +00:00
elevated: {
enabled: true,
allowFrom: {
whatsapp: ["+15555550123"],
discord: ["steipete", "1234567890123"]
}
}
}
}
```
2026-01-09 20:42:16 +00:00
Per-agent override (further restrict):
```json5
{
agents: {
list: [
{
id: "family",
tools: {
elevated: { enabled: false }
}
}
]
}
}
```
2026-01-08 22:37:06 +01:00
Notes:
2026-01-09 20:42:16 +00:00
- `tools.elevated` is the global baseline. `agents.list[].tools.elevated` can only further restrict (both must allow).
2026-01-08 22:37:06 +01:00
- `/elevated on|off` stores state per session key; inline directives apply to a single message.
2026-01-12 02:49:55 +00:00
- Elevated `exec` runs on the host and bypasses sandboxing.
- Tool policy still applies; if `exec` is denied, elevated cannot be used.
2026-01-08 22:37:06 +01:00
2026-01-09 12:44:23 +00:00
`agents.defaults.maxConcurrent` sets the maximum number of embedded agent runs that can
2025-12-25 23:50:52 +01:00
execute in parallel across sessions. Each session is still serialized (one run
per session key at a time). Default: 1.
2026-01-09 12:44:23 +00:00
### `agents.defaults.sandbox`
2026-01-03 21:35:44 +01:00
2026-01-07 02:31:51 +01:00
Optional **Docker sandboxing** for the embedded agent. Intended for non-main
sessions so they cannot access your host system.
2026-01-03 21:35:44 +01:00
2026-01-08 21:49:26 +01:00
Details: [Sandboxing ](/gateway/sandboxing )
2026-01-03 21:35:44 +01:00
Defaults (if enabled):
2026-01-07 02:31:51 +01:00
- scope: `"agent"` (one container + workspace per agent)
2026-01-03 21:35:44 +01:00
- Debian bookworm-slim based image
2026-01-07 09:32:49 +00:00
- agent workspace access: `workspaceAccess: "none"` (default)
- `"none"` : use a per-scope sandbox workspace under `~/.clawdbot/sandboxes`
2026-01-12 03:42:49 +00:00
- `"ro"` : keep the sandbox workspace at `/workspace` , and mount the agent workspace read-only at `/agent` (disables `write` /`edit` /`apply_patch` )
2026-01-07 09:32:49 +00:00
- `"rw"` : mount the agent workspace read/write at `/workspace`
2026-01-03 21:35:44 +01:00
- auto-prune: idle > 24h OR age > 7d
2026-01-12 03:42:49 +00:00
- tool policy: allow only `exec` , `process` , `read` , `write` , `edit` , `apply_patch` , `sessions_list` , `sessions_history` , `sessions_send` , `sessions_spawn` , `session_status` (deny wins)
2026-01-09 12:44:23 +00:00
- configure via `tools.sandbox.tools` , override per-agent via `agents.list[].tools.sandbox.tools`
2026-01-12 21:51:26 +00:00
- tool group shorthands supported in sandbox policy: `group:runtime` , `group:fs` , `group:sessions` , `group:memory` (see [Sandbox vs Tool Policy vs Elevated ](/gateway/sandbox-vs-tool-policy-vs-elevated#tool-groups-shorthands ))
2026-01-03 22:11:43 +01:00
- optional sandboxed browser (Chromium + CDP, noVNC observer)
2026-01-04 14:32:47 +00:00
- hardening knobs: `network` , `user` , `pidsLimit` , `memory` , `cpus` , `ulimits` , `seccompProfile` , `apparmorProfile`
2026-01-03 21:35:44 +01:00
2026-01-07 02:31:51 +01:00
Warning: `scope: "shared"` means a shared container and shared workspace. No
cross-session isolation. Use `scope: "session"` for per-session isolation.
Legacy: `perSession` is still supported (`true` → `scope: "session"` ,
`false` → `scope: "shared"` ).
2026-01-06 23:22:49 +01:00
2026-01-19 01:35:17 +00:00
`setupCommand` runs **once** after the container is created (inside the container via `sh -lc` ).
For package installs, ensure network egress, a writable root FS, and a root user.
2026-01-03 21:35:44 +01:00
```json5
{
2026-01-09 12:44:23 +00:00
agents: {
defaults: {
sandbox: {
mode: "non-main", // off | non-main | all
scope: "agent", // session | agent | shared (agent is default)
workspaceAccess: "none", // none | ro | rw
workspaceRoot: "~/.clawdbot/sandboxes",
docker: {
image: "clawdbot-sandbox:bookworm-slim",
containerPrefix: "clawdbot-sbx-",
workdir: "/workspace",
readOnlyRoot: true,
tmpfs: ["/tmp", "/var/tmp", "/run"],
network: "none",
user: "1000:1000",
capDrop: ["ALL"],
env: { LANG: "C.UTF-8" },
setupCommand: "apt-get update & & apt-get install -y git curl jq",
// Per-agent override (multi-agent): agents.list[].sandbox.docker.*
pidsLimit: 256,
memory: "1g",
memorySwap: "2g",
cpus: 1,
ulimits: {
nofile: { soft: 1024, hard: 2048 },
nproc: 256
},
seccompProfile: "/path/to/seccomp.json",
apparmorProfile: "clawdbot-sandbox",
dns: ["1.1.1.1", "8.8.8.8"],
2026-01-12 10:13:32 -07:00
extraHosts: ["internal.service:10.0.0.5"],
binds: ["/var/run/docker.sock:/var/run/docker.sock", "/home/user/source:/source:rw"]
2026-01-04 14:32:47 +00:00
},
2026-01-09 12:44:23 +00:00
browser: {
enabled: false,
image: "clawdbot-sandbox-browser:bookworm-slim",
containerPrefix: "clawdbot-sbx-browser-",
cdpPort: 9222,
vncPort: 5900,
noVncPort: 6080,
headless: false,
2026-01-10 02:06:05 +00:00
enableNoVnc: true,
2026-01-11 01:24:02 +01:00
allowHostControl: false,
2026-01-11 01:52:23 +01:00
allowedControlUrls: ["http://10.0.0.42:18791"],
allowedControlHosts: ["browser.lab.local", "10.0.0.42"],
allowedControlPorts: [18791],
2026-01-10 02:06:05 +00:00
autoStart: true,
autoStartTimeoutMs: 12000
2026-01-09 12:44:23 +00:00
},
prune: {
idleHours: 24, // 0 disables idle pruning
maxAgeDays: 7 // 0 disables max-age pruning
}
}
}
},
tools: {
sandbox: {
2026-01-03 21:35:44 +01:00
tools: {
2026-01-12 03:42:49 +00:00
allow: ["exec", "process", "read", "write", "edit", "apply_patch", "sessions_list", "sessions_history", "sessions_send", "sessions_spawn", "session_status"],
2026-01-03 21:35:44 +01:00
deny: ["browser", "canvas", "nodes", "cron", "discord", "gateway"]
}
}
}
}
```
2026-01-03 22:11:43 +01:00
Build the default sandbox image once with:
```bash
scripts/sandbox-setup.sh
```
2026-01-09 12:44:23 +00:00
Note: sandbox containers default to `network: "none"` ; set `agents.defaults.sandbox.docker.network`
2026-01-04 14:32:47 +00:00
to `"bridge"` (or your custom network) if the agent needs outbound access.
2026-01-07 09:32:49 +00:00
Note: inbound attachments are staged into the active workspace at `media/inbound/*` . With `workspaceAccess: "rw"` , that means files are written into the agent workspace.
2026-01-12 10:13:32 -07:00
Note: `docker.binds` mounts additional host directories; global and per-agent binds are merged.
2026-01-03 22:11:43 +01:00
Build the optional browser image with:
```bash
scripts/sandbox-browser-setup.sh
```
2026-01-09 12:44:23 +00:00
When `agents.defaults.sandbox.browser.enabled=true` , the browser tool uses a sandboxed
2026-01-03 22:11:43 +01:00
Chromium instance (CDP). If noVNC is enabled (default when headless=false),
the noVNC URL is injected into the system prompt so the agent can reference it.
This does not require `browser.enabled` in the main config; the sandbox control
URL is injected per session.
2026-01-11 01:24:02 +01:00
`agents.defaults.sandbox.browser.allowHostControl` (default: false) allows
sandboxed sessions to explicitly target the **host** browser control server
via the browser tool (`target: "host"` ). Leave this off if you want strict
sandbox isolation.
2026-01-11 01:52:23 +01:00
Allowlists for remote control:
- `allowedControlUrls` : exact control URLs permitted for `target: "custom"` .
- `allowedControlHosts` : hostnames permitted (hostname only, no port).
- `allowedControlPorts` : ports permitted (defaults: http=80, https=443).
2026-01-11 01:55:38 +01:00
Defaults: all allowlists are unset (no restriction). `allowHostControl` defaults to false.
2026-01-11 01:52:23 +01:00
2025-12-23 02:48:57 +01:00
### `models` (custom providers + base URLs)
2026-01-04 14:32:47 +00:00
Clawdbot uses the **pi-coding-agent** model catalog. You can add custom providers
2025-12-23 02:48:57 +01:00
(LiteLLM, local OpenAI-compatible servers, Anthropic proxies, etc.) by writing
2026-01-06 21:29:41 +00:00
`~/.clawdbot/agents/<agentId>/agent/models.json` or by defining the same schema inside your
2026-01-04 14:32:47 +00:00
Clawdbot config under `models.providers` .
2026-01-10 21:37:38 +01:00
Provider-by-provider overview + examples: [/concepts/model-providers ](/concepts/model-providers ).
2025-12-23 02:48:57 +01:00
2026-01-04 14:32:47 +00:00
When `models.providers` is present, Clawdbot writes/merges a `models.json` into
2026-01-06 21:29:41 +00:00
`~/.clawdbot/agents/<agentId>/agent/` on startup:
2025-12-23 02:48:57 +01:00
- default behavior: **merge** (keeps existing providers, overrides on name)
- set `models.mode: "replace"` to overwrite the file contents
2026-01-09 12:44:23 +00:00
Select the model via `agents.defaults.model.primary` (provider/model).
2025-12-23 02:48:57 +01:00
```json5
{
2026-01-09 12:44:23 +00:00
agents: {
defaults: {
model: { primary: "custom-proxy/llama-3.1-8b" },
models: {
"custom-proxy/llama-3.1-8b": {}
}
2026-01-06 00:56:29 +00:00
}
},
2025-12-23 02:48:57 +01:00
models: {
mode: "merge",
providers: {
"custom-proxy": {
baseUrl: "http://localhost:4000/v1",
apiKey: "LITELLM_KEY",
api: "openai-completions",
models: [
{
id: "llama-3.1-8b",
name: "Llama 3.1 8B",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 128000,
maxTokens: 32000
}
]
}
}
}
}
```
2026-01-10 01:26:03 +01:00
### OpenCode Zen (multi-model proxy)
2026-01-10 21:37:38 +01:00
OpenCode Zen is a multi-model gateway with per-model endpoints. Clawdbot uses
the built-in `opencode` provider from pi-ai; set `OPENCODE_API_KEY` (or
`OPENCODE_ZEN_API_KEY` ) from https://opencode.ai/auth.
2026-01-10 01:26:03 +01:00
Notes:
2026-01-10 21:37:38 +01:00
- Model refs use `opencode/<modelId>` (example: `opencode/claude-opus-4-5` ).
2026-01-10 01:26:03 +01:00
- If you enable an allowlist via `agents.defaults.models` , add each model you plan to use.
- Shortcut: `clawdbot onboard --auth-choice opencode-zen` .
```json5
{
agents: {
defaults: {
2026-01-10 21:37:38 +01:00
model: { primary: "opencode/claude-opus-4-5" },
models: { "opencode/claude-opus-4-5": { alias: "Opus" } }
2026-01-10 01:26:03 +01:00
}
}
}
```
2026-01-06 11:35:47 -03:00
### Z.AI (GLM-4.7) — provider alias support
Z.AI models are available via the built-in `zai` provider. Set `ZAI_API_KEY`
in your environment and reference the model by provider/model.
2026-01-10 16:32:21 +01:00
Shortcut: `clawdbot onboard --auth-choice zai-api-key` .
2026-01-06 11:35:47 -03:00
```json5
{
2026-01-09 12:44:23 +00:00
agents: {
defaults: {
model: { primary: "zai/glm-4.7" },
models: { "zai/glm-4.7": {} }
}
2026-01-06 11:35:47 -03:00
}
}
```
Notes:
- `z.ai/*` and `z-ai/*` are accepted aliases and normalize to `zai/*` .
- If `ZAI_API_KEY` is missing, requests to `zai/*` will fail with an auth error at runtime.
2026-01-06 11:49:37 -03:00
- Example error: `No API key found for provider "zai".`
2026-01-08 23:06:56 +01:00
- Z.AI’ s general API endpoint is `https://api.z.ai/api/paas/v4` . GLM coding
requests use the dedicated Coding endpoint `https://api.z.ai/api/coding/paas/v4` .
2026-01-06 11:35:47 -03:00
The built-in `zai` provider uses the Coding endpoint. If you need the general
endpoint, define a custom provider in `models.providers` with the base URL
override (see the custom providers section above).
- Use a fake placeholder in docs/configs; never commit real API keys.
2026-01-12 06:47:57 +00:00
### Moonshot AI (Kimi)
Use Moonshot's OpenAI-compatible endpoint:
```json5
{
env: { MOONSHOT_API_KEY: "sk-..." },
agents: {
defaults: {
model: { primary: "moonshot/kimi-k2-0905-preview" },
models: { "moonshot/kimi-k2-0905-preview": { alias: "Kimi K2" } }
}
},
models: {
mode: "merge",
providers: {
moonshot: {
baseUrl: "https://api.moonshot.ai/v1",
apiKey: "${MOONSHOT_API_KEY}",
api: "openai-completions",
models: [
{
id: "kimi-k2-0905-preview",
name: "Kimi K2 0905 Preview",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 256000,
maxTokens: 8192
}
]
}
}
}
}
```
Notes:
- Set `MOONSHOT_API_KEY` in the environment or use `clawdbot onboard --auth-choice moonshot-api-key` .
- Model ref: `moonshot/kimi-k2-0905-preview` .
- Use `https://api.moonshot.cn/v1` if you need the China endpoint.
2026-01-17 17:35:40 +00:00
### Kimi Code
Use Kimi Code's dedicated OpenAI-compatible endpoint (separate from Moonshot):
```json5
{
env: { KIMICODE_API_KEY: "sk-..." },
agents: {
defaults: {
model: { primary: "kimi-code/kimi-for-coding" },
models: { "kimi-code/kimi-for-coding": { alias: "Kimi Code" } }
}
},
models: {
mode: "merge",
providers: {
"kimi-code": {
baseUrl: "https://api.kimi.com/coding/v1",
apiKey: "${KIMICODE_API_KEY}",
api: "openai-completions",
models: [
{
id: "kimi-for-coding",
name: "Kimi For Coding",
reasoning: true,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 262144,
maxTokens: 32768,
headers: { "User-Agent": "KimiCLI/0.77" },
compat: { supportsDeveloperRole: false }
}
]
}
}
}
}
```
Notes:
- Set `KIMICODE_API_KEY` in the environment or use `clawdbot onboard --auth-choice kimi-code-api-key` .
- Model ref: `kimi-code/kimi-for-coding` .
2026-01-13 00:22:03 +00:00
### Synthetic (Anthropic-compatible)
Use Synthetic's Anthropic-compatible endpoint:
```json5
{
env: { SYNTHETIC_API_KEY: "sk-..." },
agents: {
defaults: {
model: { primary: "synthetic/hf:MiniMaxAI/MiniMax-M2.1" },
models: { "synthetic/hf:MiniMaxAI/MiniMax-M2.1": { alias: "MiniMax M2.1" } }
}
},
models: {
mode: "merge",
providers: {
synthetic: {
baseUrl: "https://api.synthetic.new/anthropic",
apiKey: "${SYNTHETIC_API_KEY}",
api: "anthropic-messages",
models: [
{
id: "hf:MiniMaxAI/MiniMax-M2.1",
name: "MiniMax M2.1",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 192000,
maxTokens: 65536
}
]
}
}
}
}
```
Notes:
- Set `SYNTHETIC_API_KEY` or use `clawdbot onboard --auth-choice synthetic-api-key` .
- Model ref: `synthetic/hf:MiniMaxAI/MiniMax-M2.1` .
- Base URL should omit `/v1` because the Anthropic client appends it.
2025-12-27 00:48:15 +00:00
### Local models (LM Studio) — recommended setup
2026-01-12 16:50:37 +00:00
See [/gateway/local-models ](/gateway/local-models ) for the current local guidance. TL;DR: run MiniMax M2.1 via LM Studio Responses API on serious hardware; keep hosted models merged for fallback.
2025-12-27 00:48:15 +00:00
2026-01-12 05:49:02 +00:00
### MiniMax M2.1
2026-01-09 13:56:00 -03:00
2026-01-12 05:49:02 +00:00
Use MiniMax M2.1 directly without LM Studio:
2026-01-09 13:56:00 -03:00
```json5
{
agent: {
model: { primary: "minimax/MiniMax-M2.1" },
models: {
"anthropic/claude-opus-4-5": { alias: "Opus" },
"minimax/MiniMax-M2.1": { alias: "Minimax" }
}
},
models: {
mode: "merge",
providers: {
minimax: {
baseUrl: "https://api.minimax.io/anthropic",
apiKey: "${MINIMAX_API_KEY}",
api: "anthropic-messages",
models: [
{
id: "MiniMax-M2.1",
name: "MiniMax M2.1",
reasoning: false,
input: ["text"],
2026-01-12 05:49:02 +00:00
// Pricing: update in models.json if you need exact cost tracking.
2026-01-09 13:56:00 -03:00
cost: { input: 15, output: 60, cacheRead: 2, cacheWrite: 10 },
contextWindow: 200000,
maxTokens: 8192
}
]
}
}
}
}
```
Notes:
2026-01-12 05:49:02 +00:00
- Set `MINIMAX_API_KEY` environment variable or use `clawdbot onboard --auth-choice minimax-api` .
- Available model: `MiniMax-M2.1` (default).
- Update pricing in `models.json` if you need exact cost tracking.
2026-01-09 13:56:00 -03:00
2026-01-12 05:57:49 +00:00
### Cerebras (GLM 4.6 / 4.7)
Use Cerebras via their OpenAI-compatible endpoint:
```json5
{
env: { CEREBRAS_API_KEY: "sk-..." },
agents: {
defaults: {
model: {
primary: "cerebras/zai-glm-4.7",
fallbacks: ["cerebras/zai-glm-4.6"]
},
models: {
"cerebras/zai-glm-4.7": { alias: "GLM 4.7 (Cerebras)" },
"cerebras/zai-glm-4.6": { alias: "GLM 4.6 (Cerebras)" }
}
}
},
models: {
mode: "merge",
providers: {
cerebras: {
baseUrl: "https://api.cerebras.ai/v1",
apiKey: "${CEREBRAS_API_KEY}",
api: "openai-completions",
models: [
{ id: "zai-glm-4.7", name: "GLM 4.7 (Cerebras)" },
{ id: "zai-glm-4.6", name: "GLM 4.6 (Cerebras)" }
]
}
}
}
}
```
Notes:
- Use `cerebras/zai-glm-4.7` for Cerebras; use `zai/glm-4.7` for Z.AI direct.
- Set `CEREBRAS_API_KEY` in the environment or config.
2025-12-23 02:48:57 +01:00
Notes:
- Supported APIs: `openai-completions` , `openai-responses` , `anthropic-messages` ,
`google-generative-ai`
- Use `authHeader: true` + `headers` for custom auth needs.
2026-01-04 14:32:47 +00:00
- Override the agent config root with `CLAWDBOT_AGENT_DIR` (or `PI_CODING_AGENT_DIR` )
2026-01-06 21:33:53 +00:00
if you want `models.json` stored elsewhere (default: `~/.clawdbot/agents/main/agent` ).
2025-12-23 02:48:57 +01:00
2025-12-24 00:22:57 +00:00
### `session`
2025-12-17 11:29:12 +01:00
2026-01-18 06:37:30 +00:00
Controls session scoping, reset policy, reset triggers, and where the session store is written.
2025-12-17 11:29:12 +01:00
```json5
{
2025-12-24 00:22:57 +00:00
session: {
scope: "per-sender",
2026-01-15 10:57:00 +00:00
dmScope: "main",
2026-01-16 14:23:22 -06:00
identityLinks: {
alice: ["telegram:123456789", "discord:987654321012345678"]
},
2026-01-18 06:37:30 +00:00
reset: {
mode: "daily",
atHour: 4,
idleMinutes: 60
},
resetByType: {
thread: { mode: "daily", atHour: 4 },
dm: { mode: "idle", idleMinutes: 240 },
group: { mode: "idle", idleMinutes: 120 }
},
2025-12-24 00:22:57 +00:00
resetTriggers: ["/new", "/reset"],
2026-01-06 18:25:52 +00:00
// Default is already per-agent under ~/.clawdbot/agents/< agentId > /sessions/sessions.json
// You can override with {agentId} templating:
store: "~/.clawdbot/agents/{agentId}/sessions/sessions.json",
// Direct chats collapse to agent:< agentId > :< mainKey > (default: "main").
mainKey: "main",
2026-01-04 03:37:44 +01:00
agentToAgent: {
// Max ping-pong reply turns between requester/target (0– 5).
maxPingPongTurns: 5
},
2026-01-18 06:37:30 +00:00
sendPolicy: {
rules: [
2026-01-13 07:15:57 +00:00
{ action: "deny", match: { channel: "discord", chatType: "group" } }
2026-01-18 06:37:30 +00:00
],
default: "allow"
}
2025-12-17 11:29:12 +01:00
}
}
```
2025-12-14 02:59:31 +00:00
2026-01-03 23:44:38 +01:00
Fields:
2026-01-06 18:25:52 +00:00
- `mainKey` : direct-chat bucket key (default: `"main"` ). Useful when you want to “rename” the primary DM thread without changing `agentId` .
2026-01-09 12:44:23 +00:00
- Sandbox note: `agents.defaults.sandbox.mode: "non-main"` uses this key to detect the main session. Any session key that does not match `mainKey` (groups/channels) is sandboxed.
2026-01-15 10:57:00 +00:00
- `dmScope` : how DM sessions are grouped (default: `"main"` ).
- `main` : all DMs share the main session for continuity.
- `per-peer` : isolate DMs by sender id across channels.
- `per-channel-peer` : isolate DMs per channel + sender (recommended for multi-user inboxes).
2026-01-16 14:23:22 -06:00
- `identityLinks` : map canonical ids to provider-prefixed peers so the same person shares a DM session across channels when using `per-peer` or `per-channel-peer` .
- Example: `alice: ["telegram:123456789", "discord:987654321012345678"]` .
2026-01-18 06:37:30 +00:00
- `reset` : primary reset policy. Defaults to daily resets at 4:00 AM local time on the gateway host.
- `mode` : `daily` or `idle` (default: `daily` when `reset` is present).
- `atHour` : local hour (0-23) for the daily reset boundary.
- `idleMinutes` : sliding idle window in minutes. When daily + idle are both configured, whichever expires first wins.
- `resetByType` : per-session overrides for `dm` , `group` , and `thread` .
- If you only set legacy `session.idleMinutes` without any `reset` /`resetByType` , Clawdbot stays in idle-only mode for backward compatibility.
- `heartbeatIdleMinutes` : optional idle override for heartbeat checks (daily reset still applies when enabled).
2026-01-04 03:37:44 +01:00
- `agentToAgent.maxPingPongTurns` : max reply-back turns between requester/target (0– 5, default 5).
2026-01-03 23:44:38 +01:00
- `sendPolicy.default` : `allow` or `deny` fallback when no rule matches.
2026-01-13 07:15:57 +00:00
- `sendPolicy.rules[]` : match by `channel` , `chatType` (`direct|group|room` ), or `keyPrefix` (e.g. `cron:` ). First deny wins; otherwise allow.
2026-01-03 23:44:38 +01:00
2026-01-01 10:07:31 +01:00
### `skills` (skills config)
2025-12-20 12:22:15 +01:00
2026-01-01 10:07:31 +01:00
Controls bundled allowlist, install preferences, extra skill folders, and per-skill
2026-01-04 14:32:47 +00:00
overrides. Applies to **bundled** skills and `~/.clawdbot/skills` (workspace skills
2026-01-01 10:07:31 +01:00
still win on name conflicts).
2025-12-20 12:22:15 +01:00
2026-01-01 10:07:31 +01:00
Fields:
- `allowBundled` : optional allowlist for **bundled** skills only. If set, only those
bundled skills are eligible (managed/workspace skills unaffected).
- `load.extraDirs` : additional skill directories to scan (lowest precedence).
- `install.preferBrew` : prefer brew installers when available (default: true).
- `install.nodeManager` : node installer preference (`npm` | `pnpm` | `yarn` , default: npm).
- `entries.<skillKey>` : per-skill config overrides.
Per-skill fields:
2025-12-20 12:23:53 +00:00
- `enabled` : set `false` to disable a skill even if it’ s bundled/installed.
2025-12-20 12:22:15 +01:00
- `env` : environment variables injected for the agent run (only if not already set).
- `apiKey` : optional convenience for skills that declare a primary env var (e.g. `nano-banana-pro` → `GEMINI_API_KEY` ).
Example:
```json5
{
skills: {
2026-01-15 04:07:29 +00:00
allowBundled: ["gemini", "peekaboo"],
2026-01-01 10:07:31 +01:00
load: {
extraDirs: [
"~/Projects/agent-scripts/skills",
"~/Projects/oss/some-skill-pack/skills"
]
2025-12-20 12:22:15 +01:00
},
2026-01-01 10:07:31 +01:00
install: {
preferBrew: true,
nodeManager: "npm"
},
entries: {
"nano-banana-pro": {
apiKey: "GEMINI_KEY_HERE",
env: {
GEMINI_API_KEY: "GEMINI_KEY_HERE"
}
},
peekaboo: { enabled: true },
sag: { enabled: false }
}
2025-12-20 12:26:58 +00:00
}
}
```
2026-01-11 12:11:12 +00:00
### `plugins` (extensions)
Controls plugin discovery, allow/deny, and per-plugin config. Plugins are loaded
from `~/.clawdbot/extensions` , `<workspace>/.clawdbot/extensions` , plus any
`plugins.load.paths` entries. **Config changes require a gateway restart.**
See [/plugin ](/plugin ) for full usage.
Fields:
- `enabled` : master toggle for plugin loading (default: true).
- `allow` : optional allowlist of plugin ids; when set, only listed plugins load.
- `deny` : optional denylist of plugin ids (deny wins).
- `load.paths` : extra plugin files or directories to load (absolute or `~` ).
- `entries.<pluginId>` : per-plugin overrides.
- `enabled` : set `false` to disable.
- `config` : plugin-specific config object (validated by the plugin if provided).
Example:
```json5
{
plugins: {
enabled: true,
allow: ["voice-call"],
load: {
paths: ["~/Projects/oss/voice-call-extension"]
},
entries: {
"voice-call": {
enabled: true,
config: {
provider: "twilio"
}
}
}
}
}
```
2026-01-16 03:24:53 +00:00
### `browser` (clawd-managed browser)
2025-12-13 15:15:09 +00:00
2026-01-16 03:24:53 +00:00
Clawdbot can start a **dedicated, isolated** Chrome/Brave/Edge/Chromium instance for clawd and expose a small loopback control server.
Profiles can point at a **remote** Chromium-based browser via `profiles.<name>.cdpUrl` . Remote
2026-01-04 03:32:40 +00:00
profiles are attach-only (start/stop/reset are disabled).
`browser.cdpUrl` remains for legacy single-profile configs and as the base
scheme/host for profiles that only set `cdpPort` .
2025-12-13 15:15:09 +00:00
Defaults:
- enabled: `true`
2025-12-13 15:29:39 +00:00
- control URL: `http://127.0.0.1:18791` (CDP uses `18792` )
2026-01-04 03:32:40 +00:00
- CDP URL: `http://127.0.0.1:18792` (control URL + 1, legacy single-profile)
2025-12-13 15:15:09 +00:00
- profile color: `#FF4500` (lobster-orange)
2026-01-04 14:32:47 +00:00
- Note: the control server is started by the running gateway (Clawdbot.app menubar, or `clawdbot gateway` ).
2026-01-16 05:37:37 +00:00
- Auto-detect order: default browser if Chromium-based; otherwise Chrome → Brave → Edge → Chromium → Chrome Canary.
2025-12-13 15:15:09 +00:00
```json5
{
browser: {
enabled: true,
2025-12-13 15:29:39 +00:00
controlUrl: "http://127.0.0.1:18791",
2026-01-04 03:32:40 +00:00
// cdpUrl: "http://127.0.0.1:18792", // legacy single-profile override
2026-01-15 08:50:08 +00:00
defaultProfile: "chrome",
2026-01-04 03:32:40 +00:00
profiles: {
clawd: { cdpPort: 18800, color: "#FF4500 " },
work: { cdpPort: 18801, color: "#0066CC " },
remote: { cdpUrl: "http://10.0.0.42:9222", color: "#00AA00 " }
},
2025-12-13 15:15:09 +00:00
color: "#FF4500 ",
// Advanced:
// headless: false,
2026-01-01 22:44:52 +01:00
// noSandbox: false,
2026-01-16 03:24:53 +00:00
// executablePath: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
2026-01-01 22:44:52 +01:00
// attachOnly: false, // set true when tunneling a remote CDP to localhost
2025-12-13 15:15:09 +00:00
}
}
```
2025-12-30 04:14:36 +01:00
### `ui` (Appearance)
Optional accent color used by the native apps for UI chrome (e.g. Talk Mode bubble tint).
If unset, clients fall back to a muted light-blue.
```json5
{
ui: {
seamColor: "#FF4500 " // hex (RRGGBB or #RRGGBB )
}
}
```
2025-12-20 02:08:04 +00:00
### `gateway` (Gateway server mode + bind)
Use `gateway.mode` to explicitly declare whether this machine should run the Gateway.
Defaults:
- mode: **unset** (treated as “do not auto-start”)
- bind: `loopback`
2026-01-03 11:46:58 +01:00
- port: `18789` (single port for WS + HTTP)
2025-12-20 02:08:04 +00:00
```json5
{
gateway: {
mode: "local", // or "remote"
2026-01-03 11:46:58 +01:00
port: 18789, // WS + HTTP multiplex
2025-12-20 02:08:04 +00:00
bind: "loopback",
2026-01-04 14:32:47 +00:00
// controlUi: { enabled: true, basePath: "/clawdbot" }
2026-01-11 01:51:07 +01:00
// auth: { mode: "token", token: "your-token" } // token gates WS + Control UI access
2025-12-21 00:44:39 +00:00
// tailscale: { mode: "off" | "serve" | "funnel" }
2025-12-20 02:08:04 +00:00
}
}
```
2026-01-03 17:54:52 +01:00
Control UI base path:
- `gateway.controlUi.basePath` sets the URL prefix where the Control UI is served.
2026-01-04 14:32:47 +00:00
- Examples: `"/ui"` , `"/clawdbot"` , `"/apps/clawdbot"` .
2026-01-03 17:54:52 +01:00
- Default: root (`/` ) (unchanged).
2026-01-07 01:19:31 +01:00
Related docs:
- [Control UI ](/web/control-ui )
- [Web overview ](/web )
- [Tailscale ](/gateway/tailscale )
- [Remote access ](/gateway/remote )
2025-12-20 02:08:04 +00:00
Notes:
2026-01-04 14:32:47 +00:00
- `clawdbot gateway` refuses to start unless `gateway.mode` is set to `local` (or you pass the override flag).
2026-01-03 11:46:58 +01:00
- `gateway.port` controls the single multiplexed port used for WebSocket + HTTP (control UI, hooks, A2UI).
2026-01-10 21:55:54 +00:00
- OpenAI Chat Completions endpoint: **disabled by default** ; enable with `gateway.http.endpoints.chatCompletions.enabled: true` .
2026-01-04 14:32:47 +00:00
- Precedence: `--port` > `CLAWDBOT_GATEWAY_PORT` > `gateway.port` > default `18789` .
2026-01-08 07:42:50 +01:00
- Non-loopback binds (`lan` /`tailnet` /`auto` ) require auth. Use `gateway.auth.token` (or `CLAWDBOT_GATEWAY_TOKEN` ).
2026-01-11 01:51:07 +01:00
- The onboarding wizard generates a gateway token by default (even on loopback).
2026-01-08 07:42:50 +01:00
- `gateway.remote.token` is **only** for remote CLI calls; it does not enable local gateway auth. `gateway.token` is ignored.
2025-12-20 02:08:04 +00:00
2025-12-21 00:44:39 +00:00
Auth and Tailscale:
2025-12-23 13:13:09 +00:00
- `gateway.auth.mode` sets the handshake requirements (`token` or `password` ).
2026-01-02 13:46:48 +01:00
- `gateway.auth.token` stores the shared token for token auth (used by the CLI on the same machine).
2025-12-21 00:44:39 +00:00
- When `gateway.auth.mode` is set, only that method is accepted (plus optional Tailscale headers).
2026-01-04 14:32:47 +00:00
- `gateway.auth.password` can be set here, or via `CLAWDBOT_GATEWAY_PASSWORD` (recommended).
2026-01-13 04:37:04 +00:00
- `gateway.auth.allowTailscale` allows Tailscale Serve identity headers
(`tailscale-user-login` ) to satisfy auth when the request arrives on loopback
with `x-forwarded-for` , `x-forwarded-proto` , and `x-forwarded-host` . When
`true` , Serve requests do not need a token/password; set `false` to require
explicit credentials. Defaults to `true` when `tailscale.mode = "serve"` and
auth mode is not `password` .
2025-12-21 00:44:39 +00:00
- `gateway.tailscale.mode: "serve"` uses Tailscale Serve (tailnet only, loopback bind).
- `gateway.tailscale.mode: "funnel"` exposes the dashboard publicly; requires auth.
- `gateway.tailscale.resetOnExit` resets Serve/Funnel config on shutdown.
2026-01-01 20:10:50 +01:00
Remote client defaults (CLI):
- `gateway.remote.url` sets the default Gateway WebSocket URL for CLI calls when `gateway.mode = "remote"` .
- `gateway.remote.token` supplies the token for remote calls (leave unset for no auth).
2026-01-02 00:17:54 +01:00
- `gateway.remote.password` supplies the password for remote calls (leave unset for no auth).
2026-01-01 20:10:50 +01:00
2026-01-03 23:34:18 +01:00
macOS app behavior:
2026-01-04 14:32:47 +00:00
- Clawdbot.app watches `~/.clawdbot/clawdbot.json` and switches modes live when `gateway.mode` or `gateway.remote.url` changes.
2026-01-03 23:34:18 +01:00
- If `gateway.mode` is unset but `gateway.remote.url` is set, the macOS app treats it as remote mode.
- When you change connection mode in the macOS app, it writes `gateway.mode` (and `gateway.remote.url` in remote mode) back to the config file.
2026-01-01 20:10:50 +01:00
```json5
{
gateway: {
mode: "remote",
remote: {
url: "ws://gateway.tailnet:18789",
2026-01-02 00:17:54 +01:00
token: "your-token",
password: "your-password"
2026-01-01 20:10:50 +01:00
}
}
}
```
2026-01-03 19:52:24 +00:00
### `gateway.reload` (Config hot reload)
2026-01-04 14:32:47 +00:00
The Gateway watches `~/.clawdbot/clawdbot.json` (or `CLAWDBOT_CONFIG_PATH` ) and applies changes automatically.
2026-01-03 19:52:24 +00:00
Modes:
- `hybrid` (default): hot-apply safe changes; restart the Gateway for critical changes.
- `hot` : only apply hot-safe changes; log when a restart is required.
- `restart` : restart the Gateway on any config change.
- `off` : disable hot reload.
```json5
{
gateway: {
reload: {
mode: "hybrid",
debounceMs: 300
}
}
}
```
#### Hot reload matrix (files + impact)
Files watched:
2026-01-04 14:32:47 +00:00
- `~/.clawdbot/clawdbot.json` (or `CLAWDBOT_CONFIG_PATH` )
2026-01-03 19:52:24 +00:00
Hot-applied (no full gateway restart):
- `hooks` (webhook auth/path/mappings) + `hooks.gmail` (Gmail watcher restarted)
- `browser` (browser control server restart)
- `cron` (cron service restart + concurrency update)
2026-01-09 12:44:23 +00:00
- `agents.defaults.heartbeat` (heartbeat runner restart)
2026-01-13 07:15:57 +00:00
- `web` (WhatsApp web channel restart)
- `telegram` , `discord` , `signal` , `imessage` (channel restarts)
2026-01-03 19:52:24 +00:00
- `agent` , `models` , `routing` , `messages` , `session` , `whatsapp` , `logging` , `skills` , `ui` , `talk` , `identity` , `wizard` (dynamic reads)
Requires full Gateway restart:
- `gateway` (port/bind/auth/control UI/tailscale)
- `bridge`
- `discovery`
- `canvasHost`
2026-01-11 12:11:12 +00:00
- `plugins`
2026-01-03 19:52:24 +00:00
- Any unknown/unsupported config path (defaults to restart for safety)
2026-01-03 11:46:58 +01:00
### Multi-instance isolation
2026-01-15 18:08:20 +01:00
To run multiple gateways on one host (for redundancy or a rescue bot), isolate per-instance state + config and use unique ports:
2026-01-04 14:32:47 +00:00
- `CLAWDBOT_CONFIG_PATH` (per-instance config)
2026-01-05 01:25:42 +01:00
- `CLAWDBOT_STATE_DIR` (sessions/creds)
2026-01-09 12:44:23 +00:00
- `agents.defaults.workspace` (memories)
2026-01-03 11:46:58 +01:00
- `gateway.port` (unique per instance)
2026-01-05 01:25:42 +01:00
Convenience flags (CLI):
- `clawdbot --dev …` → uses `~/.clawdbot-dev` + shifts ports from base `19001`
- `clawdbot --profile <name> …` → uses `~/.clawdbot-<name>` (port via config/env/flags)
2026-01-10 14:51:21 -06:00
See [Gateway runbook ](/gateway ) for the derived port mapping (gateway/bridge/browser/canvas).
2026-01-15 07:29:48 +00:00
See [Multiple gateways ](/gateway/multiple-gateways ) for browser/CDP port isolation details.
2026-01-05 02:03:10 +01:00
2026-01-03 11:46:58 +01:00
Example:
```bash
2026-01-04 14:32:47 +00:00
CLAWDBOT_CONFIG_PATH=~/.clawdbot/a.json \
CLAWDBOT_STATE_DIR=~/.clawdbot-a \
clawdbot gateway --port 19001
2026-01-03 11:46:58 +01:00
```
2025-12-24 14:32:55 +00:00
### `hooks` (Gateway webhooks)
2026-01-06 18:25:52 +00:00
Enable a simple HTTP webhook endpoint on the Gateway HTTP server.
2025-12-24 14:32:55 +00:00
Defaults:
- enabled: `false`
- path: `/hooks`
2025-12-24 19:39:36 +00:00
- maxBodyBytes: `262144` (256 KB)
2025-12-24 14:32:55 +00:00
```json5
{
hooks: {
enabled: true,
token: "shared-secret",
2025-12-24 19:39:36 +00:00
path: "/hooks",
presets: ["gmail"],
2026-01-04 14:32:47 +00:00
transformsDir: "~/.clawdbot/hooks",
2025-12-24 19:39:36 +00:00
mappings: [
{
match: { path: "gmail" },
action: "agent",
wakeMode: "now",
name: "Gmail",
sessionKey: "hook:gmail:{{messages[0].id}}",
messageTemplate:
"From: {{messages[0].from}}\nSubject: {{messages[0].subject}}\n{{messages[0].snippet}}",
2026-01-07 23:40:29 +00:00
deliver: true,
2026-01-13 07:15:57 +00:00
channel: "last",
2026-01-08 09:33:31 +00:00
model: "openai/gpt-5.2-mini",
2025-12-24 19:39:36 +00:00
},
],
2025-12-24 14:32:55 +00:00
}
}
```
Requests must include the hook token:
- `Authorization: Bearer <token>` **or**
2026-01-04 14:32:47 +00:00
- `x-clawdbot-token: <token>` **or**
2025-12-24 14:32:55 +00:00
- `?token=<token>`
Endpoints:
- `POST /hooks/wake` → `{ text, mode?: "now"|"next-heartbeat" }`
2026-01-13 07:15:57 +00:00
- `POST /hooks/agent` → `{ message, name?, sessionKey?, wakeMode?, deliver?, channel?, to?, model?, thinking?, timeoutSeconds? }`
2025-12-24 19:39:36 +00:00
- `POST /hooks/<name>` → resolved via `hooks.mappings`
2025-12-24 14:32:55 +00:00
`/hooks/agent` always posts a summary into the main session (and can optionally trigger an immediate heartbeat via `wakeMode: "now"` ).
2025-12-24 19:39:36 +00:00
Mapping notes:
- `match.path` matches the sub-path after `/hooks` (e.g. `/hooks/gmail` → `gmail` ).
- `match.source` matches a payload field (e.g. `{ source: "gmail" }` ) so you can use a generic `/hooks/ingest` path.
- Templates like `{{messages[0].subject}}` read from the payload.
- `transform` can point to a JS/TS module that returns a hook action.
2026-01-13 07:15:57 +00:00
- `deliver: true` sends the final reply to a channel; `channel` defaults to `last` (falls back to WhatsApp).
- If there is no prior delivery route, set `channel` + `to` explicitly (required for Telegram/Discord/Slack/Signal/iMessage/MS Teams).
2026-01-09 12:44:23 +00:00
- `model` overrides the LLM for this hook run (`provider/model` or alias; must be allowed if `agents.defaults.models` is set).
2025-12-24 19:39:36 +00:00
2026-01-17 07:08:04 +00:00
Gmail helper config (used by `clawdbot webhooks gmail setup` / `run` ):
2025-12-24 19:39:36 +00:00
```json5
{
hooks: {
gmail: {
account: "clawdbot@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,
2025-12-24 21:56:21 +00:00
serve: { bind: "127.0.0.1", port: 8788, path: "/" },
2025-12-24 19:39:36 +00:00
tailscale: { mode: "funnel", path: "/gmail-pubsub" },
2026-01-09 19:59:45 +01:00
// Optional: use a cheaper model for Gmail hook processing
// Falls back to agents.defaults.model.fallbacks, then primary, on auth/rate-limit/timeout
model: "openrouter/meta-llama/llama-3.3-70b-instruct:free",
// Optional: default thinking level for Gmail hooks
thinking: "off",
2025-12-24 19:39:36 +00:00
}
}
}
```
2026-01-09 19:59:45 +01:00
Model override for Gmail hooks:
- `hooks.gmail.model` specifies a model to use for Gmail hook processing (defaults to session primary).
- Accepts `provider/model` refs or aliases from `agents.defaults.models` .
- Falls back to `agents.defaults.model.fallbacks` , then `agents.defaults.model.primary` , on auth/rate-limit/timeouts.
- If `agents.defaults.models` is set, include the hooks model in the allowlist.
- At startup, warns if the configured model is not in the model catalog or allowlist.
- `hooks.gmail.thinking` sets the default thinking level for Gmail hooks and is overridden by per-hook `thinking` .
2026-01-03 03:03:49 +01:00
Gateway auto-start:
- If `hooks.enabled=true` and `hooks.gmail.account` is set, the Gateway starts
`gog gmail watch serve` on boot and auto-renews the watch.
2026-01-04 14:32:47 +00:00
- Set `CLAWDBOT_SKIP_GMAIL_WATCHER=1` to disable the auto-start (for manual runs).
2026-01-03 12:41:44 +00:00
- Avoid running a separate `gog gmail watch serve` alongside the Gateway; it will
fail with `listen tcp 127.0.0.1:8788: bind: address already in use` .
2026-01-03 03:03:49 +01:00
2026-01-04 14:32:47 +00:00
Note: when `tailscale.mode` is on, Clawdbot defaults `serve.path` to `/` so
2025-12-24 21:56:21 +00:00
Tailscale can proxy `/gmail-pubsub` correctly (it strips the set-path prefix).
2026-01-10 19:19:30 +01:00
If you need the backend to receive the prefixed path, set
`hooks.gmail.tailscale.target` to a full URL (and align `serve.path` ).
2025-12-24 21:56:21 +00:00
2025-12-20 17:31:09 +01:00
### `canvasHost` (LAN/tailnet Canvas file server + live reload)
2025-12-18 11:36:46 +01:00
2025-12-20 17:31:09 +01:00
The Gateway serves a directory of HTML/CSS/JS over HTTP so iOS/Android nodes can simply `canvas.navigate` to it.
2025-12-18 11:36:46 +01:00
Default root: `~/clawd/canvas`
2025-12-20 17:31:09 +01:00
Default port: `18793` (chosen to avoid the clawd browser CDP port `18792` )
2025-12-20 22:25:09 +01:00
The server listens on the **bridge bind host** (LAN or Tailnet) so nodes can reach it.
2025-12-18 11:36:46 +01:00
2025-12-18 23:32:48 +01:00
The server:
2025-12-18 11:36:46 +01:00
- serves files under `canvasHost.root`
- injects a tiny live-reload client into served HTML
2026-01-04 14:32:47 +00:00
- watches the directory and broadcasts reloads over a WebSocket endpoint at `/__clawdbot/ws`
2025-12-18 23:32:48 +01:00
- auto-creates a starter `index.html` when the directory is empty (so you see something immediately)
2026-01-04 14:32:47 +00:00
- also serves A2UI at `/__clawdbot__/a2ui/` and is advertised to nodes as `canvasHostUrl`
2025-12-20 22:25:09 +01:00
(always used by nodes for Canvas/A2UI)
2025-12-18 11:36:46 +01:00
2026-01-04 15:22:47 +01:00
Disable live reload (and file watching) if the directory is large or you hit `EMFILE` :
- config: `canvasHost: { liveReload: false }`
2025-12-18 11:36:46 +01:00
```json5
{
canvasHost: {
2025-12-20 17:31:09 +01:00
root: "~/clawd/canvas",
2026-01-04 15:22:47 +01:00
port: 18793,
liveReload: true
2025-12-18 11:36:46 +01:00
}
}
```
2026-01-04 15:45:42 +01:00
Changes to `canvasHost.*` require a gateway restart (config reload will restart).
2025-12-18 23:32:48 +01:00
Disable with:
- config: `canvasHost: { enabled: false }`
2026-01-04 14:32:47 +00:00
- env: `CLAWDBOT_SKIP_CANVAS_HOST=1`
2025-12-18 23:32:48 +01:00
2025-12-18 13:18:33 +01:00
### `bridge` (node bridge server)
2025-12-17 17:01:10 +01:00
2025-12-18 13:18:33 +01:00
The Gateway can expose a simple TCP bridge for nodes (iOS/Android), typically on port `18790` .
2025-12-17 17:01:10 +01:00
Defaults:
- enabled: `true`
- port: `18790`
- bind: `lan` (binds to `0.0.0.0` )
Bind modes:
- `lan` : `0.0.0.0` (reachable on any interface, including LAN/Wi‑ Fi and Tailscale)
- `tailnet` : bind only to the machine’ s Tailscale IP (recommended for Vienna ⇄ London)
- `loopback` : `127.0.0.1` (local only)
- `auto` : prefer tailnet IP if present, else `lan`
2026-01-16 05:28:33 +00:00
TLS:
- `bridge.tls.enabled` : enable TLS for bridge connections (TLS-only when enabled).
- `bridge.tls.autoGenerate` : generate a self-signed cert when no cert/key are present (default: true).
- `bridge.tls.certPath` / `bridge.tls.keyPath` : PEM paths for the bridge certificate + private key.
- `bridge.tls.caPath` : optional PEM CA bundle (custom roots or future mTLS).
When TLS is enabled, the Gateway advertises `bridgeTls=1` and `bridgeTlsSha256` in discovery TXT
records so nodes can pin the certificate. Manual connections use trust-on-first-use if no
fingerprint is stored yet.
Auto-generated certs require `openssl` on PATH; if generation fails, the bridge will not start.
2025-12-17 17:01:10 +01:00
```json5
{
bridge: {
enabled: true,
port: 18790,
2026-01-16 05:28:33 +00:00
bind: "tailnet",
tls: {
enabled: true,
// Uses ~/.clawdbot/bridge/tls/bridge-{cert,key}.pem when omitted.
// certPath: "~/.clawdbot/bridge/tls/bridge-cert.pem",
// keyPath: "~/.clawdbot/bridge/tls/bridge-key.pem"
}
2025-12-17 17:01:10 +01:00
}
}
```
### `discovery.wideArea` (Wide-Area Bonjour / unicast DNS‑ SD)
2026-01-04 14:32:47 +00:00
When enabled, the Gateway writes a unicast DNS-SD zone for `_clawdbot-bridge._tcp` under `~/.clawdbot/dns/` using the standard discovery domain `clawdbot.internal.`
2025-12-17 17:01:10 +01:00
To make iOS/Android discover across networks (Vienna ⇄ London), pair this with:
2026-01-04 14:32:47 +00:00
- a DNS server on the gateway host serving `clawdbot.internal.` (CoreDNS is recommended)
- Tailscale **split DNS** so clients resolve `clawdbot.internal` via that server
2025-12-17 17:01:10 +01:00
One-time setup helper (gateway host):
```bash
2026-01-04 14:32:47 +00:00
clawdbot dns setup --apply
2025-12-17 17:01:10 +01:00
```
```json5
{
discovery: { wideArea: { enabled: true } }
}
```
2025-12-13 13:25:49 +00:00
## Template variables
2025-12-03 15:45:32 +00:00
2026-01-17 04:38:20 +00:00
Template placeholders are expanded in `tools.media.*.models[].args` and `tools.media.models[].args` (and any future templated argument fields).
2025-12-03 15:45:32 +00:00
| Variable | Description |
|----------|-------------|
2025-12-13 13:25:49 +00:00
| `{{Body}}` | Full inbound message body |
2026-01-10 17:32:19 +01:00
| `{{RawBody}}` | Raw inbound message body (no history/sender wrappers; best for command parsing) |
2025-12-13 13:25:49 +00:00
| `{{BodyStripped}}` | Body with group mentions stripped (best default for agents) |
2026-01-13 07:15:57 +00:00
| `{{From}}` | Sender identifier (E.164 for WhatsApp; may differ per channel) |
2025-12-13 13:25:49 +00:00
| `{{To}}` | Destination identifier |
2026-01-13 07:15:57 +00:00
| `{{MessageSid}}` | Channel message id (when available) |
2025-12-03 15:45:32 +00:00
| `{{SessionId}}` | Current session UUID |
2025-12-13 13:25:49 +00:00
| `{{IsNewSession}}` | `"true"` when a new session was created |
| `{{MediaUrl}}` | Inbound media pseudo-URL (if present) |
| `{{MediaPath}}` | Local media path (if downloaded) |
| `{{MediaType}}` | Media type (image/audio/document/…) |
| `{{Transcript}}` | Audio transcript (when enabled) |
2026-01-17 03:52:37 +00:00
| `{{Prompt}}` | Resolved media prompt for CLI entries |
| `{{MaxChars}}` | Resolved max output chars for CLI entries |
2025-12-13 13:25:49 +00:00
| `{{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) |
2026-01-11 02:24:35 +00:00
| `{{Provider}}` | Provider hint (whatsapp|telegram|discord|slack|signal|imessage|msteams|webchat|…) |
2025-12-13 13:25:49 +00:00
## Cron (Gateway scheduler)
2026-01-07 02:04:02 +01:00
Cron is a Gateway-owned scheduler for wakeups and scheduled jobs. See [Cron jobs ](/automation/cron-jobs ) for the feature overview and CLI examples.
2025-12-03 15:45:32 +00:00
2025-12-13 13:25:49 +00:00
```json5
{
cron: {
enabled: true,
maxConcurrentRuns: 2
}
2025-12-03 15:45:32 +00:00
}
```
---
2026-01-07 02:04:02 +01:00
*Next: [Agent Runtime ](/concepts/agent )* 🦞