feat: wire multi-agent config and routing
Co-authored-by: Mark Pors <1078320+pors@users.noreply.github.com>
This commit is contained in:
@@ -32,9 +32,9 @@ Environment overrides:
|
||||
- `PI_BASH_JOB_TTL_MS`: TTL for finished sessions (ms, bounded to 1m–3h)
|
||||
|
||||
Config (preferred):
|
||||
- `agent.bash.backgroundMs` (default 10000)
|
||||
- `agent.bash.timeoutSec` (default 1800)
|
||||
- `agent.bash.cleanupMs` (default 1800000)
|
||||
- `tools.bash.backgroundMs` (default 10000)
|
||||
- `tools.bash.timeoutSec` (default 1800)
|
||||
- `tools.bash.cleanupMs` (default 1800000)
|
||||
|
||||
## process tool
|
||||
|
||||
|
||||
@@ -189,52 +189,71 @@ Save to `~/.clawdbot/clawdbot.json` and you can DM the bot from that number.
|
||||
},
|
||||
|
||||
// Agent runtime
|
||||
agent: {
|
||||
workspace: "~/clawd",
|
||||
userTimezone: "America/Chicago",
|
||||
model: {
|
||||
primary: "anthropic/claude-sonnet-4-5",
|
||||
fallbacks: ["anthropic/claude-opus-4-5", "openai/gpt-5.2"]
|
||||
},
|
||||
imageModel: {
|
||||
primary: "openrouter/anthropic/claude-sonnet-4-5"
|
||||
},
|
||||
models: {
|
||||
"anthropic/claude-opus-4-5": { alias: "opus" },
|
||||
"anthropic/claude-sonnet-4-5": { alias: "sonnet" },
|
||||
"openai/gpt-5.2": { alias: "gpt" }
|
||||
},
|
||||
thinkingDefault: "low",
|
||||
verboseDefault: "off",
|
||||
elevatedDefault: "on",
|
||||
blockStreamingDefault: "on",
|
||||
blockStreamingBreak: "text_end",
|
||||
blockStreamingChunk: {
|
||||
minChars: 800,
|
||||
maxChars: 1200,
|
||||
breakPreference: "paragraph"
|
||||
},
|
||||
timeoutSeconds: 600,
|
||||
mediaMaxMb: 5,
|
||||
typingIntervalSeconds: 5,
|
||||
maxConcurrent: 3,
|
||||
tools: {
|
||||
allow: ["bash", "process", "read", "write", "edit"],
|
||||
deny: ["browser", "canvas"]
|
||||
},
|
||||
agents: {
|
||||
defaults: {
|
||||
workspace: "~/clawd",
|
||||
userTimezone: "America/Chicago",
|
||||
model: {
|
||||
primary: "anthropic/claude-sonnet-4-5",
|
||||
fallbacks: ["anthropic/claude-opus-4-5", "openai/gpt-5.2"]
|
||||
},
|
||||
imageModel: {
|
||||
primary: "openrouter/anthropic/claude-sonnet-4-5"
|
||||
},
|
||||
models: {
|
||||
"anthropic/claude-opus-4-5": { alias: "opus" },
|
||||
"anthropic/claude-sonnet-4-5": { alias: "sonnet" },
|
||||
"openai/gpt-5.2": { alias: "gpt" }
|
||||
},
|
||||
thinkingDefault: "low",
|
||||
verboseDefault: "off",
|
||||
elevatedDefault: "on",
|
||||
blockStreamingDefault: "on",
|
||||
blockStreamingBreak: "text_end",
|
||||
blockStreamingChunk: {
|
||||
minChars: 800,
|
||||
maxChars: 1200,
|
||||
breakPreference: "paragraph"
|
||||
},
|
||||
timeoutSeconds: 600,
|
||||
mediaMaxMb: 5,
|
||||
typingIntervalSeconds: 5,
|
||||
maxConcurrent: 3,
|
||||
heartbeat: {
|
||||
every: "30m",
|
||||
model: "anthropic/claude-sonnet-4-5",
|
||||
target: "last",
|
||||
to: "+15555550123",
|
||||
prompt: "HEARTBEAT",
|
||||
ackMaxChars: 30
|
||||
},
|
||||
sandbox: {
|
||||
mode: "non-main",
|
||||
perSession: true,
|
||||
workspaceRoot: "~/.clawdbot/sandboxes",
|
||||
docker: {
|
||||
image: "clawdbot-sandbox:bookworm-slim",
|
||||
workdir: "/workspace",
|
||||
readOnlyRoot: true,
|
||||
tmpfs: ["/tmp", "/var/tmp", "/run"],
|
||||
network: "none",
|
||||
user: "1000:1000"
|
||||
},
|
||||
browser: {
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
tools: {
|
||||
allow: ["bash", "process", "read", "write", "edit"],
|
||||
deny: ["browser", "canvas"],
|
||||
bash: {
|
||||
backgroundMs: 10000,
|
||||
timeoutSec: 1800,
|
||||
cleanupMs: 1800000
|
||||
},
|
||||
heartbeat: {
|
||||
every: "30m",
|
||||
model: "anthropic/claude-sonnet-4-5",
|
||||
target: "last",
|
||||
to: "+15555550123",
|
||||
prompt: "HEARTBEAT",
|
||||
ackMaxChars: 30
|
||||
},
|
||||
elevated: {
|
||||
enabled: true,
|
||||
allowFrom: {
|
||||
@@ -246,22 +265,6 @@ Save to `~/.clawdbot/clawdbot.json` and you can DM the bot from that number.
|
||||
imessage: ["user@example.com"],
|
||||
webchat: ["session:demo"]
|
||||
}
|
||||
},
|
||||
sandbox: {
|
||||
mode: "non-main",
|
||||
perSession: true,
|
||||
workspaceRoot: "~/.clawdbot/sandboxes",
|
||||
docker: {
|
||||
image: "clawdbot-sandbox:bookworm-slim",
|
||||
workdir: "/workspace",
|
||||
readOnlyRoot: true,
|
||||
tmpfs: ["/tmp", "/var/tmp", "/run"],
|
||||
network: "none",
|
||||
user: "1000:1000"
|
||||
},
|
||||
browser: {
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -9,11 +9,11 @@ CLAWDBOT reads an optional **JSON5** config from `~/.clawdbot/clawdbot.json` (co
|
||||
|
||||
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:
|
||||
- restrict who can trigger the bot (`whatsapp.allowFrom`, `telegram.allowFrom`, etc.)
|
||||
- control group allowlists + mention behavior (`whatsapp.groups`, `telegram.groups`, `discord.guilds`, `routing.groupChat`)
|
||||
- control group allowlists + mention behavior (`whatsapp.groups`, `telegram.groups`, `discord.guilds`, `agents.list[].groupChat`)
|
||||
- customize message prefixes (`messages`)
|
||||
- set the agent's workspace (`agent.workspace`)
|
||||
- tune the embedded agent (`agent`) and session behavior (`session`)
|
||||
- set the agent's identity (`identity`)
|
||||
- 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`)
|
||||
|
||||
> **New to configuration?** Check out the [Configuration Examples](/gateway/configuration-examples) guide for complete examples with detailed explanations!
|
||||
|
||||
@@ -39,7 +39,7 @@ Example (via `gateway call`):
|
||||
|
||||
```bash
|
||||
clawdbot gateway call config.apply --params '{
|
||||
"raw": "{\\n agent: { workspace: \\"~/clawd\\" }\\n}\\n",
|
||||
"raw": "{\\n agents: { defaults: { workspace: \\"~/clawd\\" } }\\n}\\n",
|
||||
"sessionKey": "agent:main:whatsapp:dm:+15555550123",
|
||||
"restartDelayMs": 1000
|
||||
}'
|
||||
@@ -49,7 +49,7 @@ clawdbot gateway call config.apply --params '{
|
||||
|
||||
```json5
|
||||
{
|
||||
agent: { workspace: "~/clawd" },
|
||||
agents: { defaults: { workspace: "~/clawd" } },
|
||||
whatsapp: { allowFrom: ["+15555550123"] }
|
||||
}
|
||||
```
|
||||
@@ -65,16 +65,19 @@ To prevent the bot from responding to WhatsApp @-mentions in groups (only respon
|
||||
|
||||
```json5
|
||||
{
|
||||
agent: { workspace: "~/clawd" },
|
||||
agents: {
|
||||
defaults: { workspace: "~/clawd" },
|
||||
list: [
|
||||
{
|
||||
id: "main",
|
||||
groupChat: { mentionPatterns: ["@clawd", "reisponde"] }
|
||||
}
|
||||
]
|
||||
},
|
||||
whatsapp: {
|
||||
// Allowlist is DMs only; including your own number enables self-chat mode.
|
||||
allowFrom: ["+15555550123"],
|
||||
groups: { "*": { requireMention: true } }
|
||||
},
|
||||
routing: {
|
||||
groupChat: {
|
||||
mentionPatterns: ["@clawd", "reisponde"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -175,17 +178,21 @@ rotation order used for failover.
|
||||
}
|
||||
```
|
||||
|
||||
### `identity`
|
||||
### `agents.list[].identity`
|
||||
|
||||
Optional agent identity used for defaults and UX. This is written by the macOS onboarding assistant.
|
||||
Optional per-agent identity used for defaults and UX. This is written by the macOS onboarding assistant.
|
||||
|
||||
If set, CLAWDBOT derives defaults (only when you haven’t set them explicitly):
|
||||
- `messages.ackReaction` from `identity.emoji` (falls back to 👀)
|
||||
- `routing.groupChat.mentionPatterns` from `identity.name` (so “@Samantha” works in groups across Telegram/Slack/Discord/iMessage/WhatsApp)
|
||||
- `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)
|
||||
|
||||
```json5
|
||||
{
|
||||
identity: { name: "Samantha", theme: "helpful sloth", emoji: "🦥" }
|
||||
agents: {
|
||||
list: [
|
||||
{ id: "main", identity: { name: "Samantha", theme: "helpful sloth", emoji: "🦥" } }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -311,25 +318,26 @@ Notes:
|
||||
- `default` is used when `accountId` is omitted (CLI + routing).
|
||||
- Env tokens only apply to the **default** account.
|
||||
- Base provider settings (group policy, mention gating, etc.) apply to all accounts unless overridden per account.
|
||||
- Use `routing.bindings[].match.accountId` to route each account to a different agent.
|
||||
- Use `bindings[].match.accountId` to route each account to a different agents.defaults.
|
||||
|
||||
### `routing.groupChat`
|
||||
### Group chat mention gating (`agents.list[].groupChat` + `messages.groupChat`)
|
||||
|
||||
Group messages default to **require mention** (either metadata mention or regex patterns). Applies to WhatsApp, Telegram, Discord, and iMessage group chats.
|
||||
|
||||
**Mention types:**
|
||||
- **Metadata mentions**: Native platform @-mentions (e.g., WhatsApp tap-to-mention). Ignored in WhatsApp self-chat mode (see `whatsapp.allowFrom`).
|
||||
- **Text patterns**: Regex patterns defined in `mentionPatterns`. Always checked regardless of self-chat mode.
|
||||
- **Text patterns**: Regex patterns defined in `agents.list[].groupChat.mentionPatterns`. Always checked regardless of self-chat mode.
|
||||
- Mention gating is enforced only when mention detection is possible (native mentions or at least one `mentionPattern`).
|
||||
- Per-agent override: `routing.agents.<agentId>.mentionPatterns` (useful when multiple agents share a group).
|
||||
|
||||
```json5
|
||||
{
|
||||
routing: {
|
||||
groupChat: {
|
||||
mentionPatterns: ["@clawd", "clawdbot", "clawd"],
|
||||
historyLimit: 50
|
||||
}
|
||||
messages: {
|
||||
groupChat: { historyLimit: 50 }
|
||||
},
|
||||
agents: {
|
||||
list: [
|
||||
{ id: "main", groupChat: { mentionPatterns: ["@clawd", "clawdbot", "clawd"] } }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -337,11 +345,11 @@ Group messages default to **require mention** (either metadata mention or regex
|
||||
Per-agent override (takes precedence when set, even `[]`):
|
||||
```json5
|
||||
{
|
||||
routing: {
|
||||
agents: {
|
||||
work: { mentionPatterns: ["@workbot", "\\+15555550123"] },
|
||||
personal: { mentionPatterns: ["@homebot", "\\+15555550999"] }
|
||||
}
|
||||
agents: {
|
||||
list: [
|
||||
{ id: "work", groupChat: { mentionPatterns: ["@workbot", "\\+15555550123"] } },
|
||||
{ id: "personal", groupChat: { mentionPatterns: ["@homebot", "\\+15555550999"] } }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -356,11 +364,16 @@ To respond **only** to specific text triggers (ignoring native @-mentions):
|
||||
allowFrom: ["+15555550123"],
|
||||
groups: { "*": { requireMention: true } }
|
||||
},
|
||||
routing: {
|
||||
groupChat: {
|
||||
// Only these text patterns will trigger responses
|
||||
mentionPatterns: ["reisponde", "@clawd"]
|
||||
}
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "main",
|
||||
groupChat: {
|
||||
// Only these text patterns will trigger responses
|
||||
mentionPatterns: ["reisponde", "@clawd"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -410,17 +423,22 @@ Notes:
|
||||
- Discord/Slack use channel allowlists (`discord.guilds.*.channels`, `slack.channels`).
|
||||
- Group DMs (Discord/Slack) are still controlled by `dm.groupEnabled` + `dm.groupChannels`.
|
||||
|
||||
### Multi-agent routing (`routing.agents` + `routing.bindings`)
|
||||
### Multi-agent routing (`agents.list` + `bindings`)
|
||||
|
||||
Run multiple isolated agents (separate workspace, `agentDir`, sessions) inside one Gateway. Inbound messages are routed to an agent via bindings.
|
||||
Run multiple isolated agents (separate workspace, `agentDir`, sessions) inside one Gateway.
|
||||
Inbound messages are routed to an agent via bindings.
|
||||
|
||||
- `routing.defaultAgentId`: fallback when no binding matches (default: `main`).
|
||||
- `routing.agents.<agentId>`: per-agent overrides.
|
||||
- `agents.list[]`: per-agent overrides.
|
||||
- `id`: stable agent id (required).
|
||||
- `default`: optional; when multiple are set, the first wins and a warning is logged.
|
||||
If none are set, the **first entry** in the list is the default agent.
|
||||
- `name`: display name for the agent.
|
||||
- `workspace`: default `~/clawd-<agentId>` (for `main`, falls back to legacy `agent.workspace`).
|
||||
- `workspace`: default `~/clawd-<agentId>` (for `main`, falls back to `agents.defaults.workspace`).
|
||||
- `agentDir`: default `~/.clawdbot/agents/<agentId>/agent`.
|
||||
- `model`: per-agent default model (provider/model), overrides `agent.model` for that agent.
|
||||
- `sandbox`: per-agent sandbox config (overrides `agent.sandbox`).
|
||||
- `model`: per-agent default model (provider/model), overrides `agents.defaults.model` for that agent.
|
||||
- `identity`: per-agent name/theme/emoji (used for mention patterns + ack reactions).
|
||||
- `groupChat`: per-agent mention-gating (`mentionPatterns`).
|
||||
- `sandbox`: per-agent sandbox config (overrides `agents.defaults.sandbox`).
|
||||
- `mode`: `"off"` | `"non-main"` | `"all"`
|
||||
- `workspaceAccess`: `"none"` | `"ro"` | `"rw"`
|
||||
- `scope`: `"session"` | `"agent"` | `"shared"`
|
||||
@@ -428,13 +446,13 @@ Run multiple isolated agents (separate workspace, `agentDir`, sessions) inside o
|
||||
- `docker`: per-agent docker overrides (e.g. `image`, `network`, `env`, `setupCommand`, limits; ignored when `scope: "shared"`)
|
||||
- `browser`: per-agent sandboxed browser overrides (ignored when `scope: "shared"`)
|
||||
- `prune`: per-agent sandbox pruning overrides (ignored when `scope: "shared"`)
|
||||
- `tools`: per-agent sandbox tool policy (deny wins; overrides `agent.sandbox.tools`)
|
||||
- `subagents`: per-agent sub-agent defaults.
|
||||
- `allowAgents`: allowlist of agent ids for `sessions_spawn` from this agent (`["*"]` = allow any; default: only same agent)
|
||||
- `tools`: per-agent tool restrictions (overrides `agent.tools`; applied before sandbox tool policy).
|
||||
- `tools`: per-agent tool restrictions (applied before sandbox tool policy).
|
||||
- `allow`: array of allowed tool names
|
||||
- `deny`: array of denied tool names (deny wins)
|
||||
- `routing.bindings[]`: routes inbound messages to an `agentId`.
|
||||
- `agents.defaults`: shared agent defaults (model, workspace, sandbox, etc.).
|
||||
- `bindings[]`: routes inbound messages to an `agentId`.
|
||||
- `match.provider` (required)
|
||||
- `match.accountId` (optional; `*` = any account; omitted = default account)
|
||||
- `match.peer` (optional; `{ kind: dm|group|channel, id }`)
|
||||
@@ -446,9 +464,9 @@ Deterministic match order:
|
||||
3) `match.teamId`
|
||||
4) `match.accountId` (exact, no peer/guild/team)
|
||||
5) `match.accountId: "*"` (provider-wide, no peer/guild/team)
|
||||
6) `routing.defaultAgentId`
|
||||
6) default agent (`agents.list[].default`, else first list entry, else `"main"`)
|
||||
|
||||
Within each match tier, the first matching entry in `routing.bindings` wins.
|
||||
Within each match tier, the first matching entry in `bindings` wins.
|
||||
|
||||
#### Per-agent access profiles (multi-agent)
|
||||
|
||||
@@ -464,13 +482,14 @@ additional examples.
|
||||
Full access (no sandbox):
|
||||
```json5
|
||||
{
|
||||
routing: {
|
||||
agents: {
|
||||
personal: {
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "personal",
|
||||
workspace: "~/clawd-personal",
|
||||
sandbox: { mode: "off" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -478,9 +497,10 @@ Full access (no sandbox):
|
||||
Read-only tools + read-only workspace:
|
||||
```json5
|
||||
{
|
||||
routing: {
|
||||
agents: {
|
||||
family: {
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "family",
|
||||
workspace: "~/clawd-family",
|
||||
sandbox: {
|
||||
mode: "all",
|
||||
@@ -492,7 +512,7 @@ Read-only tools + read-only workspace:
|
||||
deny: ["write", "edit", "bash", "process", "browser"]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -500,9 +520,10 @@ Read-only tools + read-only workspace:
|
||||
No filesystem access (messaging/session tools enabled):
|
||||
```json5
|
||||
{
|
||||
routing: {
|
||||
agents: {
|
||||
public: {
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "public",
|
||||
workspace: "~/clawd-public",
|
||||
sandbox: {
|
||||
mode: "all",
|
||||
@@ -514,7 +535,7 @@ No filesystem access (messaging/session tools enabled):
|
||||
deny: ["read", "write", "edit", "bash", "process", "browser", "canvas", "nodes", "cron", "gateway", "image"]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -523,17 +544,16 @@ Example: two WhatsApp accounts → two agents:
|
||||
|
||||
```json5
|
||||
{
|
||||
routing: {
|
||||
defaultAgentId: "home",
|
||||
agents: {
|
||||
home: { workspace: "~/clawd-home" },
|
||||
work: { workspace: "~/clawd-work" },
|
||||
},
|
||||
bindings: [
|
||||
{ agentId: "home", match: { provider: "whatsapp", accountId: "personal" } },
|
||||
{ agentId: "work", match: { provider: "whatsapp", accountId: "biz" } },
|
||||
],
|
||||
agents: {
|
||||
list: [
|
||||
{ id: "home", default: true, workspace: "~/clawd-home" },
|
||||
{ id: "work", workspace: "~/clawd-work" }
|
||||
]
|
||||
},
|
||||
bindings: [
|
||||
{ agentId: "home", match: { provider: "whatsapp", accountId: "personal" } },
|
||||
{ agentId: "work", match: { provider: "whatsapp", accountId: "biz" } }
|
||||
],
|
||||
whatsapp: {
|
||||
accounts: {
|
||||
personal: {},
|
||||
@@ -543,13 +563,13 @@ Example: two WhatsApp accounts → two agents:
|
||||
}
|
||||
```
|
||||
|
||||
### `routing.agentToAgent` (optional)
|
||||
### `tools.agentToAgent` (optional)
|
||||
|
||||
Agent-to-agent messaging is opt-in:
|
||||
|
||||
```json5
|
||||
{
|
||||
routing: {
|
||||
tools: {
|
||||
agentToAgent: {
|
||||
enabled: false,
|
||||
allow: ["home", "work"]
|
||||
@@ -558,13 +578,13 @@ Agent-to-agent messaging is opt-in:
|
||||
}
|
||||
```
|
||||
|
||||
### `routing.queue`
|
||||
### `messages.queue`
|
||||
|
||||
Controls how inbound messages behave when an agent run is already active.
|
||||
|
||||
```json5
|
||||
{
|
||||
routing: {
|
||||
messages: {
|
||||
queue: {
|
||||
mode: "collect", // steer | followup | collect | steer-backlog (steer+backlog ok) | interrupt (queue=steer legacy)
|
||||
debounceMs: 1000,
|
||||
@@ -859,7 +879,7 @@ Example wrapper:
|
||||
exec ssh -T mac-mini "imsg rpc"
|
||||
```
|
||||
|
||||
### `agent.workspace`
|
||||
### `agents.defaults.workspace`
|
||||
|
||||
Sets the **single global workspace directory** used by the agent for file operations.
|
||||
|
||||
@@ -867,14 +887,14 @@ Default: `~/clawd`.
|
||||
|
||||
```json5
|
||||
{
|
||||
agent: { workspace: "~/clawd" }
|
||||
agents: { defaults: { workspace: "~/clawd" } }
|
||||
}
|
||||
```
|
||||
|
||||
If `agent.sandbox` is enabled, non-main sessions can override this with their
|
||||
own per-scope workspaces under `agent.sandbox.workspaceRoot`.
|
||||
If `agents.defaults.sandbox` is enabled, non-main sessions can override this with their
|
||||
own per-scope workspaces under `agents.defaults.sandbox.workspaceRoot`.
|
||||
|
||||
### `agent.skipBootstrap`
|
||||
### `agents.defaults.skipBootstrap`
|
||||
|
||||
Disables automatic creation of the workspace bootstrap files (`AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, and `BOOTSTRAP.md`).
|
||||
|
||||
@@ -882,18 +902,18 @@ Use this for pre-seeded deployments where your workspace files come from a repo.
|
||||
|
||||
```json5
|
||||
{
|
||||
agent: { skipBootstrap: true }
|
||||
agents: { defaults: { skipBootstrap: true } }
|
||||
}
|
||||
```
|
||||
|
||||
### `agent.userTimezone`
|
||||
### `agents.defaults.userTimezone`
|
||||
|
||||
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
|
||||
{
|
||||
agent: { userTimezone: "America/Chicago" }
|
||||
agents: { defaults: { userTimezone: "America/Chicago" } }
|
||||
}
|
||||
```
|
||||
|
||||
@@ -917,7 +937,7 @@ streaming, final replies) across providers unless already present.
|
||||
|
||||
`ackReaction` sends a best-effort emoji reaction to acknowledge inbound messages
|
||||
on providers that support reactions (Slack/Discord/Telegram). Defaults to the
|
||||
configured `identity.emoji` when set, otherwise `"👀"`. Set it to `""` to disable.
|
||||
active agent’s `identity.emoji` when set, otherwise `"👀"`. Set it to `""` to disable.
|
||||
|
||||
`ackReactionScope` controls when reactions fire:
|
||||
- `group-mentions` (default): only when a group/room requires mentions **and** the bot was mentioned
|
||||
@@ -947,22 +967,22 @@ Defaults for Talk mode (macOS/iOS/Android). Voice IDs fall back to `ELEVENLABS_V
|
||||
}
|
||||
```
|
||||
|
||||
### `agent`
|
||||
### `agents.defaults`
|
||||
|
||||
Controls the embedded agent runtime (model/thinking/verbose/timeouts).
|
||||
`agent.models` defines the configured model catalog (and acts as the allowlist for `/model`).
|
||||
`agent.model.primary` sets the default model; `agent.model.fallbacks` are global failovers.
|
||||
`agent.imageModel` is optional and is **only used if the primary model lacks image input**.
|
||||
Each `agent.models` entry can include:
|
||||
`agents.defaults.models` defines the configured model catalog (and acts as the allowlist for `/model`).
|
||||
`agents.defaults.model.primary` sets the default model; `agents.defaults.model.fallbacks` are global failovers.
|
||||
`agents.defaults.imageModel` is optional and is **only used if the primary model lacks image input**.
|
||||
Each `agents.defaults.models` entry can include:
|
||||
- `alias` (optional model shortcut, e.g. `/opus`).
|
||||
- `params` (optional provider-specific API params passed through to the model request).
|
||||
|
||||
Z.AI GLM-4.x models automatically enable thinking mode unless you:
|
||||
- set `--thinking off`, or
|
||||
- define `agent.models["zai/<model>"].params.thinking` yourself.
|
||||
- define `agents.defaults.models["zai/<model>"].params.thinking` yourself.
|
||||
|
||||
Clawdbot also ships a few built-in alias shorthands. Defaults only apply when the model
|
||||
is already present in `agent.models`:
|
||||
is already present in `agents.defaults.models`:
|
||||
|
||||
- `opus` -> `anthropic/claude-opus-4-5`
|
||||
- `sonnet` -> `anthropic/claude-sonnet-4-5`
|
||||
@@ -975,61 +995,63 @@ If you configure the same alias name (case-insensitive) yourself, your value win
|
||||
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
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: {
|
||||
maxConcurrent: 1,
|
||||
archiveAfterMinutes: 60
|
||||
},
|
||||
bash: {
|
||||
backgroundMs: 10000,
|
||||
timeoutSec: 1800,
|
||||
cleanupMs: 1800000
|
||||
},
|
||||
contextTokens: 200000
|
||||
},
|
||||
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: {
|
||||
maxConcurrent: 1,
|
||||
archiveAfterMinutes: 60
|
||||
},
|
||||
bash: {
|
||||
backgroundMs: 10000,
|
||||
timeoutSec: 1800,
|
||||
cleanupMs: 1800000
|
||||
},
|
||||
contextTokens: 200000
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `agent.contextPruning` (tool-result pruning)
|
||||
#### `agents.defaults.contextPruning` (tool-result pruning)
|
||||
|
||||
`agent.contextPruning` prunes **old tool results** from the in-memory context right before a request is sent to the LLM.
|
||||
`agents.defaults.contextPruning` prunes **old tool results** from the in-memory context right before a request is sent to the LLM.
|
||||
It does **not** modify the session history on disk (`*.jsonl` remains complete).
|
||||
|
||||
This is intended to reduce token usage for chatty agents that accumulate large tool outputs over time.
|
||||
@@ -1061,22 +1083,14 @@ Notes / current limitations:
|
||||
Default (adaptive):
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
contextPruning: {
|
||||
mode: "adaptive"
|
||||
}
|
||||
}
|
||||
agents: { defaults: { contextPruning: { mode: "adaptive" } } }
|
||||
}
|
||||
```
|
||||
|
||||
To disable:
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
contextPruning: {
|
||||
mode: "off"
|
||||
}
|
||||
}
|
||||
agents: { defaults: { contextPruning: { mode: "off" } } }
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1091,28 +1105,26 @@ Defaults (when `mode` is `"adaptive"` or `"aggressive"`):
|
||||
Example (aggressive, minimal):
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
contextPruning: {
|
||||
mode: "aggressive"
|
||||
}
|
||||
}
|
||||
agents: { defaults: { contextPruning: { mode: "aggressive" } } }
|
||||
}
|
||||
```
|
||||
|
||||
Example (adaptive tuned):
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
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"] },
|
||||
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"] },
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1121,36 +1133,34 @@ Example (adaptive tuned):
|
||||
See [/concepts/session-pruning](/concepts/session-pruning) for behavior details.
|
||||
|
||||
Block streaming:
|
||||
- `agent.blockStreamingDefault`: `"on"`/`"off"` (default on).
|
||||
- `agent.blockStreamingBreak`: `"text_end"` or `"message_end"` (default: text_end).
|
||||
- `agent.blockStreamingChunk`: soft chunking for streamed blocks. Defaults to
|
||||
- `agents.defaults.blockStreamingDefault`: `"on"`/`"off"` (default on).
|
||||
- `agents.defaults.blockStreamingBreak`: `"text_end"` or `"message_end"` (default: text_end).
|
||||
- `agents.defaults.blockStreamingChunk`: soft chunking for streamed blocks. Defaults to
|
||||
800–1200 chars, prefers paragraph breaks (`\n\n`), then newlines, then sentences.
|
||||
Example:
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
blockStreamingChunk: { minChars: 800, maxChars: 1200 }
|
||||
}
|
||||
agents: { defaults: { blockStreamingChunk: { minChars: 800, maxChars: 1200 } } }
|
||||
}
|
||||
```
|
||||
See [/concepts/streaming](/concepts/streaming) for behavior + chunking details.
|
||||
|
||||
Typing indicators:
|
||||
- `agent.typingMode`: `"never" | "instant" | "thinking" | "message"`. Defaults to
|
||||
- `agents.defaults.typingMode`: `"never" | "instant" | "thinking" | "message"`. Defaults to
|
||||
`instant` for direct chats / mentions and `message` for unmentioned group chats.
|
||||
- `session.typingMode`: per-session override for the mode.
|
||||
- `agent.typingIntervalSeconds`: how often the typing signal is refreshed (default: 6s).
|
||||
- `agents.defaults.typingIntervalSeconds`: how often the typing signal is refreshed (default: 6s).
|
||||
- `session.typingIntervalSeconds`: per-session override for the refresh interval.
|
||||
See [/concepts/typing-indicators](/concepts/typing-indicators) for behavior details.
|
||||
|
||||
`agent.model.primary` should be set as `provider/model` (e.g. `anthropic/claude-opus-4-5`).
|
||||
Aliases come from `agent.models.*.alias` (e.g. `Opus`).
|
||||
`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`).
|
||||
If you omit the provider, CLAWDBOT currently assumes `anthropic` as a temporary
|
||||
deprecation fallback.
|
||||
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.
|
||||
|
||||
`agent.heartbeat` configures periodic heartbeat runs:
|
||||
`agents.defaults.heartbeat` configures periodic heartbeat runs:
|
||||
- `every`: duration string (`ms`, `s`, `m`, `h`); default unit minutes. Default:
|
||||
`30m`. Set `0m` to disable.
|
||||
- `model`: optional override model for heartbeat runs (`provider/model`).
|
||||
@@ -1162,31 +1172,27 @@ Z.AI models are available as `zai/<model>` (e.g. `zai/glm-4.7`) and require
|
||||
Heartbeats run full agent turns. Shorter intervals burn more tokens; be mindful
|
||||
of `every`, keep `HEARTBEAT.md` tiny, and/or choose a cheaper `model`.
|
||||
|
||||
`agent.bash` configures background bash defaults:
|
||||
`tools.bash` configures background bash defaults:
|
||||
- `backgroundMs`: time before auto-background (ms, default 10000)
|
||||
- `timeoutSec`: auto-kill after this runtime (seconds, default 1800)
|
||||
- `cleanupMs`: how long to keep finished sessions in memory (ms, default 1800000)
|
||||
|
||||
`agent.subagents` configures sub-agent defaults:
|
||||
`agents.defaults.subagents` configures sub-agent defaults:
|
||||
- `maxConcurrent`: max concurrent sub-agent runs (default 1)
|
||||
- `archiveAfterMinutes`: auto-archive sub-agent sessions after N minutes (default 60; set `0` to disable)
|
||||
- `tools.allow` / `tools.deny`: per-subagent tool allow/deny policy (deny wins)
|
||||
- Per-subagent tool policy: `tools.subagents.tools.allow` / `tools.subagents.tools.deny` (deny wins)
|
||||
|
||||
`agent.tools` configures a global tool allow/deny policy (deny wins).
|
||||
`tools.allow` / `tools.deny` configure a global tool allow/deny policy (deny wins).
|
||||
This is applied even when the Docker sandbox is **off**.
|
||||
|
||||
Example (disable browser/canvas everywhere):
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
tools: {
|
||||
deny: ["browser", "canvas"]
|
||||
}
|
||||
}
|
||||
tools: { deny: ["browser", "canvas"] }
|
||||
}
|
||||
```
|
||||
|
||||
`agent.elevated` controls elevated (host) bash access:
|
||||
`tools.elevated` controls elevated (host) bash access:
|
||||
- `enabled`: allow elevated mode (default true)
|
||||
- `allowFrom`: per-provider allowlists (empty = disabled)
|
||||
- `whatsapp`: E.164 numbers
|
||||
@@ -1199,7 +1205,7 @@ Example (disable browser/canvas everywhere):
|
||||
Example:
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
tools: {
|
||||
elevated: {
|
||||
enabled: true,
|
||||
allowFrom: {
|
||||
@@ -1212,16 +1218,16 @@ Example:
|
||||
```
|
||||
|
||||
Notes:
|
||||
- `agent.elevated` is **global** (not per-agent). Availability is based on sender allowlists.
|
||||
- `tools.elevated` is **global** (not per-agent). Availability is based on sender allowlists.
|
||||
- `/elevated on|off` stores state per session key; inline directives apply to a single message.
|
||||
- Elevated `bash` runs on the host and bypasses sandboxing.
|
||||
- Tool policy still applies; if `bash` is denied, elevated cannot be used.
|
||||
|
||||
`agent.maxConcurrent` sets the maximum number of embedded agent runs that can
|
||||
`agents.defaults.maxConcurrent` sets the maximum number of embedded agent runs that can
|
||||
execute in parallel across sessions. Each session is still serialized (one run
|
||||
per session key at a time). Default: 1.
|
||||
|
||||
### `agent.sandbox`
|
||||
### `agents.defaults.sandbox`
|
||||
|
||||
Optional **Docker sandboxing** for the embedded agent. Intended for non-main
|
||||
sessions so they cannot access your host system.
|
||||
@@ -1236,7 +1242,8 @@ Defaults (if enabled):
|
||||
- `"ro"`: keep the sandbox workspace at `/workspace`, and mount the agent workspace read-only at `/agent` (disables `write`/`edit`)
|
||||
- `"rw"`: mount the agent workspace read/write at `/workspace`
|
||||
- auto-prune: idle > 24h OR age > 7d
|
||||
- tools: allow only `bash`, `process`, `read`, `write`, `edit`, `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn` (deny wins)
|
||||
- tool policy: allow only `bash`, `process`, `read`, `write`, `edit`, `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn` (deny wins)
|
||||
- configure via `tools.sandbox.tools`, override per-agent via `agents.list[].tools.sandbox.tools`
|
||||
- optional sandboxed browser (Chromium + CDP, noVNC observer)
|
||||
- hardening knobs: `network`, `user`, `pidsLimit`, `memory`, `cpus`, `ulimits`, `seccompProfile`, `apparmorProfile`
|
||||
|
||||
@@ -1248,54 +1255,60 @@ Legacy: `perSession` is still supported (`true` → `scope: "session"`,
|
||||
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
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): routing.agents.<agentId>.sandbox.docker.*
|
||||
pidsLimit: 256,
|
||||
memory: "1g",
|
||||
memorySwap: "2g",
|
||||
cpus: 1,
|
||||
ulimits: {
|
||||
nofile: { soft: 1024, hard: 2048 },
|
||||
nproc: 256
|
||||
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"],
|
||||
extraHosts: ["internal.service:10.0.0.5"]
|
||||
},
|
||||
seccompProfile: "/path/to/seccomp.json",
|
||||
apparmorProfile: "clawdbot-sandbox",
|
||||
dns: ["1.1.1.1", "8.8.8.8"],
|
||||
extraHosts: ["internal.service:10.0.0.5"]
|
||||
},
|
||||
browser: {
|
||||
enabled: false,
|
||||
image: "clawdbot-sandbox-browser:bookworm-slim",
|
||||
containerPrefix: "clawdbot-sbx-browser-",
|
||||
cdpPort: 9222,
|
||||
vncPort: 5900,
|
||||
noVncPort: 6080,
|
||||
headless: false,
|
||||
enableNoVnc: true
|
||||
},
|
||||
browser: {
|
||||
enabled: false,
|
||||
image: "clawdbot-sandbox-browser:bookworm-slim",
|
||||
containerPrefix: "clawdbot-sbx-browser-",
|
||||
cdpPort: 9222,
|
||||
vncPort: 5900,
|
||||
noVncPort: 6080,
|
||||
headless: false,
|
||||
enableNoVnc: true
|
||||
},
|
||||
prune: {
|
||||
idleHours: 24, // 0 disables idle pruning
|
||||
maxAgeDays: 7 // 0 disables max-age pruning
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
tools: {
|
||||
sandbox: {
|
||||
tools: {
|
||||
allow: ["bash", "process", "read", "write", "edit", "sessions_list", "sessions_history", "sessions_send", "sessions_spawn"],
|
||||
deny: ["browser", "canvas", "nodes", "cron", "discord", "gateway"]
|
||||
},
|
||||
prune: {
|
||||
idleHours: 24, // 0 disables idle pruning
|
||||
maxAgeDays: 7 // 0 disables max-age pruning
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1307,7 +1320,7 @@ Build the default sandbox image once with:
|
||||
scripts/sandbox-setup.sh
|
||||
```
|
||||
|
||||
Note: sandbox containers default to `network: "none"`; set `agent.sandbox.docker.network`
|
||||
Note: sandbox containers default to `network: "none"`; set `agents.defaults.sandbox.docker.network`
|
||||
to `"bridge"` (or your custom network) if the agent needs outbound access.
|
||||
|
||||
Note: inbound attachments are staged into the active workspace at `media/inbound/*`. With `workspaceAccess: "rw"`, that means files are written into the agent workspace.
|
||||
@@ -1317,7 +1330,7 @@ Build the optional browser image with:
|
||||
scripts/sandbox-browser-setup.sh
|
||||
```
|
||||
|
||||
When `agent.sandbox.browser.enabled=true`, the browser tool uses a sandboxed
|
||||
When `agents.defaults.sandbox.browser.enabled=true`, the browser tool uses a sandboxed
|
||||
Chromium instance (CDP). If noVNC is enabled (default when headless=false),
|
||||
the noVNC URL is injected into the system prompt so the agent can reference it.
|
||||
This does not require `browser.enabled` in the main config; the sandbox control
|
||||
@@ -1335,14 +1348,16 @@ When `models.providers` is present, Clawdbot writes/merges a `models.json` into
|
||||
- default behavior: **merge** (keeps existing providers, overrides on name)
|
||||
- set `models.mode: "replace"` to overwrite the file contents
|
||||
|
||||
Select the model via `agent.model.primary` (provider/model).
|
||||
Select the model via `agents.defaults.model.primary` (provider/model).
|
||||
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
model: { primary: "custom-proxy/llama-3.1-8b" },
|
||||
models: {
|
||||
"custom-proxy/llama-3.1-8b": {}
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "custom-proxy/llama-3.1-8b" },
|
||||
models: {
|
||||
"custom-proxy/llama-3.1-8b": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
models: {
|
||||
@@ -1376,9 +1391,11 @@ in your environment and reference the model by provider/model.
|
||||
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
model: "zai/glm-4.7",
|
||||
allowedModels: ["zai/glm-4.7"]
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "zai/glm-4.7" },
|
||||
models: { "zai/glm-4.7": {} }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1401,11 +1418,13 @@ via **LM Studio** using the **Responses API**.
|
||||
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
model: { primary: "lmstudio/minimax-m2.1-gs32" },
|
||||
models: {
|
||||
"anthropic/claude-opus-4-5": { alias: "Opus" },
|
||||
"lmstudio/minimax-m2.1-gs32": { alias: "Minimax" }
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "lmstudio/minimax-m2.1-gs32" },
|
||||
models: {
|
||||
"anthropic/claude-opus-4-5": { alias: "Opus" },
|
||||
"lmstudio/minimax-m2.1-gs32": { alias: "Minimax" }
|
||||
}
|
||||
}
|
||||
},
|
||||
models: {
|
||||
@@ -1475,7 +1494,7 @@ Controls session scoping, idle expiry, reset triggers, and where the session sto
|
||||
|
||||
Fields:
|
||||
- `mainKey`: direct-chat bucket key (default: `"main"`). Useful when you want to “rename” the primary DM thread without changing `agentId`.
|
||||
- Sandbox note: `agent.sandbox.mode: "non-main"` uses this key to detect the main session. Any session key that does not match `mainKey` (groups/channels) is sandboxed.
|
||||
- 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.
|
||||
- `agentToAgent.maxPingPongTurns`: max reply-back turns between requester/target (0–5, default 5).
|
||||
- `sendPolicy.default`: `allow` or `deny` fallback when no rule matches.
|
||||
- `sendPolicy.rules[]`: match by `provider`, `chatType` (`direct|group|room`), or `keyPrefix` (e.g. `cron:`). First deny wins; otherwise allow.
|
||||
@@ -1684,7 +1703,7 @@ 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)
|
||||
- `agent.heartbeat` (heartbeat runner restart)
|
||||
- `agents.defaults.heartbeat` (heartbeat runner restart)
|
||||
- `web` (WhatsApp web provider restart)
|
||||
- `telegram`, `discord`, `signal`, `imessage` (provider restarts)
|
||||
- `agent`, `models`, `routing`, `messages`, `session`, `whatsapp`, `logging`, `skills`, `ui`, `talk`, `identity`, `wizard` (dynamic reads)
|
||||
@@ -1701,7 +1720,7 @@ Requires full Gateway restart:
|
||||
To run multiple gateways on one host, isolate per-instance state + config and use unique ports:
|
||||
- `CLAWDBOT_CONFIG_PATH` (per-instance config)
|
||||
- `CLAWDBOT_STATE_DIR` (sessions/creds)
|
||||
- `agent.workspace` (memories)
|
||||
- `agents.defaults.workspace` (memories)
|
||||
- `gateway.port` (unique per instance)
|
||||
|
||||
Convenience flags (CLI):
|
||||
@@ -1771,7 +1790,7 @@ Mapping notes:
|
||||
- `transform` can point to a JS/TS module that returns a hook action.
|
||||
- `deliver: true` sends the final reply to a provider; `provider` defaults to `last` (falls back to WhatsApp).
|
||||
- If there is no prior delivery route, set `provider` + `to` explicitly (required for Telegram/Discord/Slack/Signal/iMessage).
|
||||
- `model` overrides the LLM for this hook run (`provider/model` or alias; must be allowed if `agent.models` is set).
|
||||
- `model` overrides the LLM for this hook run (`provider/model` or alias; must be allowed if `agents.defaults.models` is set).
|
||||
|
||||
Gmail helper config (used by `clawdbot hooks gmail setup` / `run`):
|
||||
|
||||
@@ -1886,7 +1905,7 @@ clawdbot dns setup --apply
|
||||
|
||||
## Template variables
|
||||
|
||||
Template placeholders are expanded in `routing.transcribeAudio.command` (and any future templated command fields).
|
||||
Template placeholders are expanded in `audio.transcription.command` (and any future templated command fields).
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
|
||||
@@ -94,8 +94,18 @@ legacy config format, so stale configs are repaired without manual intervention.
|
||||
|
||||
Current migrations:
|
||||
- `routing.allowFrom` → `whatsapp.allowFrom`
|
||||
- `routing.groupChat.requireMention` → `whatsapp/telegram/imessage.groups."*".requireMention`
|
||||
- `routing.groupChat.historyLimit` → `messages.groupChat.historyLimit`
|
||||
- `routing.groupChat.mentionPatterns` → `messages.groupChat.mentionPatterns`
|
||||
- `routing.queue` → `messages.queue`
|
||||
- `routing.bindings` → top-level `bindings`
|
||||
- `routing.agents`/`routing.defaultAgentId` → `agents.list` + `agents.list[].default`
|
||||
- `routing.agentToAgent` → `tools.agentToAgent`
|
||||
- `routing.transcribeAudio` → `audio.transcription`
|
||||
- `identity` → `agents.list[].identity`
|
||||
- `agent.*` → `agents.defaults` + `tools.*` (tools/elevated/bash/sandbox/subagents)
|
||||
- `agent.model`/`allowedModels`/`modelAliases`/`modelFallbacks`/`imageModelFallbacks`
|
||||
→ `agent.models` + `agent.model.primary/fallbacks` + `agent.imageModel.primary/fallbacks`
|
||||
→ `agents.defaults.models` + `agents.defaults.model.primary/fallbacks` + `agents.defaults.imageModel.primary/fallbacks`
|
||||
|
||||
### 3) Legacy state migrations (disk layout)
|
||||
Doctor can migrate older on-disk layouts into the current structure:
|
||||
|
||||
@@ -22,7 +22,7 @@ Short guide to verify the WhatsApp Web / Baileys stack without guessing.
|
||||
## When something fails
|
||||
- `logged out` or status 409–515 → relink with `clawdbot providers logout` then `clawdbot providers login`.
|
||||
- Gateway unreachable → start it: `clawdbot gateway --port 18789` (use `--force` if the port is busy).
|
||||
- No inbound messages → confirm linked phone is online and the sender is allowed (`whatsapp.allowFrom`); for group chats, ensure allowlist + mention rules match (`whatsapp.groups`, `routing.groupChat.mentionPatterns`).
|
||||
- No inbound messages → confirm linked phone is online and the sender is allowed (`whatsapp.allowFrom`); for group chats, ensure allowlist + mention rules match (`whatsapp.groups`, `agents.list[].groupChat.mentionPatterns`).
|
||||
|
||||
## Dedicated "health" command
|
||||
`clawdbot health --json` asks the running Gateway for its health snapshot (no direct Baileys socket from the CLI). It reports linked creds, auth age, Baileys connect result/status code, session-store summary, and a probe duration. It exits non-zero if the Gateway is unreachable or the probe fails/timeouts. Use `--timeout <ms>` to override the 10s default.
|
||||
|
||||
@@ -10,8 +10,8 @@ surface anything that needs attention without spamming you.
|
||||
|
||||
## Defaults
|
||||
|
||||
- Interval: `30m` (set `agent.heartbeat.every`; use `0m` to disable).
|
||||
- Prompt body (configurable via `agent.heartbeat.prompt`):
|
||||
- Interval: `30m` (set `agents.defaults.heartbeat.every`; use `0m` to disable).
|
||||
- Prompt body (configurable via `agents.defaults.heartbeat.prompt`):
|
||||
`Read HEARTBEAT.md if exists. Consider outstanding tasks. Checkup sometimes on your human during (user local) day time.`
|
||||
- The heartbeat prompt is sent **verbatim** as the user message. The system
|
||||
prompt includes a “Heartbeat” section and the run is flagged internally.
|
||||
@@ -33,14 +33,16 @@ and logged; a message that is only `HEARTBEAT_OK` is dropped.
|
||||
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
heartbeat: {
|
||||
every: "30m", // default: 30m (0m disables)
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
target: "last", // last | whatsapp | telegram | discord | slack | signal | imessage | none
|
||||
to: "+15551234567", // optional provider-specific override
|
||||
prompt: "Read HEARTBEAT.md if exists. Consider outstanding tasks. Checkup sometimes on your human during (user local) day time.",
|
||||
ackMaxChars: 30 // max chars allowed after HEARTBEAT_OK
|
||||
agents: {
|
||||
defaults: {
|
||||
heartbeat: {
|
||||
every: "30m", // default: 30m (0m disables)
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
target: "last", // last | whatsapp | telegram | discord | slack | signal | imessage | none
|
||||
to: "+15551234567", // optional provider-specific override
|
||||
prompt: "Read HEARTBEAT.md if exists. Consider outstanding tasks. Checkup sometimes on your human during (user local) day time.",
|
||||
ackMaxChars: 30 // max chars allowed after HEARTBEAT_OK
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ Defaults (can be overridden via env/flags/config):
|
||||
- `bridge.port=19002` (derived: `gateway.port+1`)
|
||||
- `browser.controlUrl=http://127.0.0.1:19003` (derived: `gateway.port+2`)
|
||||
- `canvasHost.port=19005` (derived: `gateway.port+4`)
|
||||
- `agent.workspace` default becomes `~/clawd-dev` when you run `setup`/`onboard` under `--dev`.
|
||||
- `agents.defaults.workspace` default becomes `~/clawd-dev` when you run `setup`/`onboard` under `--dev`.
|
||||
|
||||
Derived ports (rules of thumb):
|
||||
- Base port = `gateway.port` (or `CLAWDBOT_GATEWAY_PORT` / `--port`)
|
||||
@@ -81,7 +81,7 @@ Checklist per instance:
|
||||
- unique `gateway.port`
|
||||
- unique `CLAWDBOT_CONFIG_PATH`
|
||||
- unique `CLAWDBOT_STATE_DIR`
|
||||
- unique `agent.workspace`
|
||||
- unique `agents.defaults.workspace`
|
||||
- separate WhatsApp numbers (if using WA)
|
||||
|
||||
Example:
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
---
|
||||
summary: "How Clawdbot sandboxing works: modes, scopes, workspace access, and images"
|
||||
title: Sandboxing
|
||||
read_when: "You want a dedicated explanation of sandboxing or need to tune agent.sandbox."
|
||||
read_when: "You want a dedicated explanation of sandboxing or need to tune agents.defaults.sandbox."
|
||||
status: active
|
||||
---
|
||||
|
||||
# Sandboxing
|
||||
|
||||
Clawdbot can run **tools inside Docker containers** to reduce blast radius.
|
||||
This is **optional** and controlled by configuration (`agent.sandbox` or
|
||||
`routing.agents[id].sandbox`). If sandboxing is off, tools run on the host.
|
||||
This is **optional** and controlled by configuration (`agents.defaults.sandbox` or
|
||||
`agents.list[].sandbox`). If sandboxing is off, tools run on the host.
|
||||
The Gateway stays on the host; tool execution runs in an isolated sandbox
|
||||
when enabled.
|
||||
|
||||
@@ -18,16 +18,16 @@ and process access when the model does something dumb.
|
||||
|
||||
## What gets sandboxed
|
||||
- Tool execution (`bash`, `read`, `write`, `edit`, `process`, etc.).
|
||||
- Optional sandboxed browser (`agent.sandbox.browser`).
|
||||
- Optional sandboxed browser (`agents.defaults.sandbox.browser`).
|
||||
|
||||
Not sandboxed:
|
||||
- The Gateway process itself.
|
||||
- Any tool explicitly allowed to run on the host (e.g. `agent.elevated`).
|
||||
- Any tool explicitly allowed to run on the host (e.g. `tools.elevated`).
|
||||
- **Elevated bash runs on the host and bypasses sandboxing.**
|
||||
- If sandboxing is off, `agent.elevated` does not change execution (already on host). See [Elevated Mode](/tools/elevated).
|
||||
- If sandboxing is off, `tools.elevated` does not change execution (already on host). See [Elevated Mode](/tools/elevated).
|
||||
|
||||
## Modes
|
||||
`agent.sandbox.mode` controls **when** sandboxing is used:
|
||||
`agents.defaults.sandbox.mode` controls **when** sandboxing is used:
|
||||
- `"off"`: no sandboxing.
|
||||
- `"non-main"`: sandbox only **non-main** sessions (default if you want normal chats on host).
|
||||
- `"all"`: every session runs in a sandbox.
|
||||
@@ -35,13 +35,13 @@ Note: `"non-main"` is based on `session.mainKey` (default `"main"`), not agent i
|
||||
Group/channel sessions use their own keys, so they count as non-main and will be sandboxed.
|
||||
|
||||
## Scope
|
||||
`agent.sandbox.scope` controls **how many containers** are created:
|
||||
`agents.defaults.sandbox.scope` controls **how many containers** are created:
|
||||
- `"session"` (default): one container per session.
|
||||
- `"agent"`: one container per agent.
|
||||
- `"shared"`: one container shared by all sandboxed sessions.
|
||||
|
||||
## Workspace access
|
||||
`agent.sandbox.workspaceAccess` controls **what the sandbox can see**:
|
||||
`agents.defaults.sandbox.workspaceAccess` controls **what the sandbox can see**:
|
||||
- `"none"` (default): tools see a sandbox workspace under `~/.clawdbot/sandboxes`.
|
||||
- `"ro"`: mounts the agent workspace read-only at `/agent` (disables `write`/`edit`).
|
||||
- `"rw"`: mounts the agent workspace read/write at `/workspace`.
|
||||
@@ -66,7 +66,7 @@ scripts/sandbox-browser-setup.sh
|
||||
```
|
||||
|
||||
By default, sandbox containers run with **no network**.
|
||||
Override with `agent.sandbox.docker.network`.
|
||||
Override with `agents.defaults.sandbox.docker.network`.
|
||||
|
||||
Docker installs and the containerized gateway live here:
|
||||
[Docker](/install/docker)
|
||||
@@ -75,28 +75,30 @@ Docker installs and the containerized gateway live here:
|
||||
Tool allow/deny policies still apply before sandbox rules. If a tool is denied
|
||||
globally or per-agent, sandboxing doesn’t bring it back.
|
||||
|
||||
`agent.elevated` is an explicit escape hatch that runs `bash` on the host.
|
||||
`tools.elevated` is an explicit escape hatch that runs `bash` on the host.
|
||||
Keep it locked down.
|
||||
|
||||
## Multi-agent overrides
|
||||
Each agent can override sandbox + tools:
|
||||
`routing.agents[id].sandbox` and `routing.agents[id].tools`.
|
||||
`agents.list[].sandbox` and `agents.list[].tools` (plus `agents.list[].tools.sandbox.tools` for sandbox tool policy).
|
||||
See [Multi-Agent Sandbox & Tools](/multi-agent-sandbox-tools) for precedence.
|
||||
|
||||
## Minimal enable example
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
sandbox: {
|
||||
mode: "non-main",
|
||||
scope: "session",
|
||||
workspaceAccess: "none"
|
||||
agents: {
|
||||
defaults: {
|
||||
sandbox: {
|
||||
mode: "non-main",
|
||||
scope: "session",
|
||||
workspaceAccess: "none"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Related docs
|
||||
- [Sandbox Configuration](/gateway/configuration#agent-sandbox)
|
||||
- [Sandbox Configuration](/gateway/configuration#agentsdefaults-sandbox)
|
||||
- [Multi-Agent Sandbox & Tools](/multi-agent-sandbox-tools)
|
||||
- [Security](/gateway/security)
|
||||
|
||||
@@ -127,10 +127,13 @@ Keep config + state private on the gateway host:
|
||||
"*": { "requireMention": true }
|
||||
}
|
||||
},
|
||||
"routing": {
|
||||
"groupChat": {
|
||||
"mentionPatterns": ["@clawd", "@mybot"]
|
||||
}
|
||||
"agents": {
|
||||
"list": [
|
||||
{
|
||||
"id": "main",
|
||||
"groupChat": { "mentionPatterns": ["@clawd", "@mybot"] }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -146,7 +149,7 @@ Consider running your AI on a separate phone number from your personal one:
|
||||
### 4. Read-Only Mode (Today, via sandbox + tools)
|
||||
|
||||
You can already build a read-only profile by combining:
|
||||
- `sandbox.workspaceAccess: "ro"` (or `"none"` for no workspace access)
|
||||
- `agents.defaults.sandbox.workspaceAccess: "ro"` (or `"none"` for no workspace access)
|
||||
- tool allow/deny lists that block `write`, `edit`, `bash`, `process`, etc.
|
||||
|
||||
We may add a single `readOnlyMode` flag later to simplify this configuration.
|
||||
@@ -158,18 +161,18 @@ Dedicated doc: [Sandboxing](/gateway/sandboxing)
|
||||
Two complementary approaches:
|
||||
|
||||
- **Run the full Gateway in Docker** (container boundary): [Docker](/install/docker)
|
||||
- **Tool sandbox** (`agent.sandbox`, host gateway + Docker-isolated tools): [Sandboxing](/gateway/sandboxing)
|
||||
- **Tool sandbox** (`agents.defaults.sandbox`, host gateway + Docker-isolated tools): [Sandboxing](/gateway/sandboxing)
|
||||
|
||||
Note: to prevent cross-agent access, keep `sandbox.scope` at `"agent"` (default)
|
||||
Note: to prevent cross-agent access, keep `agents.defaults.sandbox.scope` at `"agent"` (default)
|
||||
or `"session"` for stricter per-session isolation. `scope: "shared"` uses a
|
||||
single container/workspace.
|
||||
|
||||
Also consider agent workspace access inside the sandbox:
|
||||
- `agent.sandbox.workspaceAccess: "none"` (default) keeps the agent workspace off-limits; tools run against a sandbox workspace under `~/.clawdbot/sandboxes`
|
||||
- `workspaceAccess: "ro"` mounts the agent workspace read-only at `/agent` (disables `write`/`edit`)
|
||||
- `workspaceAccess: "rw"` mounts the agent workspace read/write at `/workspace`
|
||||
- `agents.defaults.sandbox.workspaceAccess: "none"` (default) keeps the agent workspace off-limits; tools run against a sandbox workspace under `~/.clawdbot/sandboxes`
|
||||
- `agents.defaults.sandbox.workspaceAccess: "ro"` mounts the agent workspace read-only at `/agent` (disables `write`/`edit`)
|
||||
- `agents.defaults.sandbox.workspaceAccess: "rw"` mounts the agent workspace read/write at `/workspace`
|
||||
|
||||
Important: `agent.elevated` is a **global**, sender-based escape hatch that runs bash on the host. Keep `agent.elevated.allowFrom` tight and don’t enable it for strangers. See [Elevated Mode](/tools/elevated).
|
||||
Important: `tools.elevated` is a **global**, sender-based escape hatch that runs bash on the host. Keep `tools.elevated.allowFrom` tight and don’t enable it for strangers. See [Elevated Mode](/tools/elevated).
|
||||
|
||||
## Per-agent access profiles (multi-agent)
|
||||
|
||||
@@ -187,13 +190,14 @@ Common use cases:
|
||||
|
||||
```json5
|
||||
{
|
||||
routing: {
|
||||
agents: {
|
||||
personal: {
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "personal",
|
||||
workspace: "~/clawd-personal",
|
||||
sandbox: { mode: "off" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -202,9 +206,10 @@ Common use cases:
|
||||
|
||||
```json5
|
||||
{
|
||||
routing: {
|
||||
agents: {
|
||||
family: {
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "family",
|
||||
workspace: "~/clawd-family",
|
||||
sandbox: {
|
||||
mode: "all",
|
||||
@@ -216,7 +221,7 @@ Common use cases:
|
||||
deny: ["write", "edit", "bash", "process", "browser"]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -225,9 +230,10 @@ Common use cases:
|
||||
|
||||
```json5
|
||||
{
|
||||
routing: {
|
||||
agents: {
|
||||
public: {
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "public",
|
||||
workspace: "~/clawd-public",
|
||||
sandbox: {
|
||||
mode: "all",
|
||||
@@ -239,7 +245,7 @@ Common use cases:
|
||||
deny: ["read", "write", "edit", "bash", "process", "browser", "canvas", "nodes", "cron", "gateway", "image"]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -127,12 +127,12 @@ or state drift because only one workspace is active.
|
||||
Symptoms: `pwd` or file tools show `~/.clawdbot/sandboxes/...` even though you
|
||||
expected the host workspace.
|
||||
|
||||
**Why:** `agent.sandbox.mode: "non-main"` keys off `session.mainKey` (default `"main"`).
|
||||
**Why:** `agents.defaults.sandbox.mode: "non-main"` keys off `session.mainKey` (default `"main"`).
|
||||
Group/channel sessions use their own keys, so they are treated as non-main and
|
||||
get sandbox workspaces.
|
||||
|
||||
**Fix options:**
|
||||
- If you want host workspaces for an agent: set `routing.agents.<id>.sandbox.mode: "off"`.
|
||||
- If you want host workspaces for an agent: set `agents.list[].sandbox.mode: "off"`.
|
||||
- If you want host workspace access inside sandbox: set `workspaceAccess: "rw"` for that agent.
|
||||
|
||||
### "Agent was aborted"
|
||||
@@ -157,8 +157,8 @@ Look for `AllowFrom: ...` in the output.
|
||||
**Check 2:** For group chats, is mention required?
|
||||
```bash
|
||||
# The message must match mentionPatterns or explicit mentions; defaults live in provider groups/guilds.
|
||||
# Multi-agent: `routing.agents.<agentId>.mentionPatterns` overrides global patterns.
|
||||
grep -n "routing\\|groupChat\\|mentionPatterns\\|whatsapp\\.groups\\|telegram\\.groups\\|imessage\\.groups\\|discord\\.guilds" \
|
||||
# Multi-agent: `agents.list[].groupChat.mentionPatterns` overrides global patterns.
|
||||
grep -n "agents\\|groupChat\\|mentionPatterns\\|whatsapp\\.groups\\|telegram\\.groups\\|imessage\\.groups\\|discord\\.guilds" \
|
||||
"${CLAWDBOT_CONFIG_PATH:-$HOME/.clawdbot/clawdbot.json}"
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user