feat: wire multi-agent config and routing

Co-authored-by: Mark Pors <1078320+pors@users.noreply.github.com>
This commit is contained in:
Peter Steinberger
2026-01-09 12:44:23 +00:00
parent 81beda0772
commit 7b81d97ec2
189 changed files with 4340 additions and 2903 deletions

View File

@@ -63,7 +63,7 @@ If you want a fixed channel, set `provider` + `to`. Otherwise `provider: "last"`
uses the last delivery route (falls back to WhatsApp).
To force a cheaper model for Gmail runs, set `model` in the mapping
(`provider/model` or alias). If you enforce `agent.models`, include it there.
(`provider/model` or alias). If you enforce `agents.defaults.models`, include it there.
To customize payload handling further, add `hooks.mappings` or a JS/TS transform module
under `hooks.transformsDir` (see [`docs/webhook.md`](https://docs.clawd.bot/automation/webhook)).

View File

@@ -134,7 +134,7 @@ curl -X POST http://127.0.0.1:18789/hooks/agent \
-d '{"message":"Summarize inbox","name":"Email","model":"openai/gpt-5.2-mini"}'
```
If you enforce `agent.models`, make sure the override model is included there.
If you enforce `agents.defaults.models`, make sure the override model is included there.
```bash
curl -X POST http://127.0.0.1:18789/hooks/gmail \

View File

@@ -488,10 +488,10 @@ Options:
Always includes the auth overview and OAuth expiry status for profiles in the auth store.
### `models set <model>`
Set `agent.model.primary`.
Set `agents.defaults.model.primary`.
### `models set-image <model>`
Set `agent.imageModel.primary`.
Set `agents.defaults.imageModel.primary`.
### `models aliases list|add|remove`
Options:

View File

@@ -42,7 +42,7 @@ Short, exact flow of one agent run.
## Timeouts
- `agent.wait` default: 30s (just the wait). `timeoutMs` param overrides.
- Agent runtime: `agent.timeoutSeconds` default 600s; enforced in `runEmbeddedPiAgent` abort timer.
- Agent runtime: `agents.defaults.timeoutSeconds` default 600s; enforced in `runEmbeddedPiAgent` abort timer.
## Where things can end early
- Agent timeout (abort)

View File

@@ -15,7 +15,7 @@ sessions.
**Important:** the workspace is the **default cwd**, not a hard sandbox. Tools
resolve relative paths against the workspace, but absolute paths can still reach
elsewhere on the host unless sandboxing is enabled. If you need isolation, use
[`agent.sandbox`](/gateway/sandboxing) (and/or peragent sandbox config).
[`agents.defaults.sandbox`](/gateway/sandboxing) (and/or peragent sandbox config).
When sandboxing is enabled and `workspaceAccess` is not `"rw"`, tools operate
inside a sandbox workspace under `~/.clawdbot/sandboxes`, not your host workspace.
@@ -53,7 +53,7 @@ only one workspace is active at a time.
**Recommendation:** keep a single active workspace. If you no longer use the
legacy folders, archive or move them to Trash (for example `trash ~/clawdis`).
If you intentionally keep multiple workspaces, make sure
`agent.workspace` points to the active one.
`agents.defaults.workspace` points to the active one.
`clawdbot doctor` warns when it detects legacy workspace directories.
@@ -207,7 +207,7 @@ Suggested `.gitignore` starter:
## Moving the workspace to a new machine
1. Clone the repo to the desired path (default `~/clawd`).
2. Set `agent.workspace` to that path in `~/.clawdbot/clawdbot.json`.
2. Set `agents.defaults.workspace` to that path in `~/.clawdbot/clawdbot.json`.
3. Run `clawdbot setup --workspace <path>` to seed any missing files.
4. If you need sessions, copy `~/.clawdbot/agents/<agentId>/sessions/` from the
old machine separately.
@@ -216,5 +216,5 @@ Suggested `.gitignore` starter:
- Multi-agent routing can use different workspaces per agent. See
`docs/provider-routing.md` for routing configuration.
- If `agent.sandbox` is enabled, non-main sessions can use per-session sandbox
workspaces under `agent.sandbox.workspaceRoot`.
- If `agents.defaults.sandbox` is enabled, non-main sessions can use per-session sandbox
workspaces under `agents.defaults.sandbox.workspaceRoot`.

View File

@@ -9,19 +9,19 @@ CLAWDBOT runs a single embedded agent runtime derived from **p-mono**.
## Workspace (required)
CLAWDBOT uses a single agent workspace directory (`agent.workspace`) as the agents **only** working directory (`cwd`) for tools and context.
CLAWDBOT uses a single agent workspace directory (`agents.defaults.workspace`) as the agents **only** working directory (`cwd`) for tools and context.
Recommended: use `clawdbot setup` to create `~/.clawdbot/clawdbot.json` if missing and initialize the workspace files.
Full workspace layout + backup guide: [`docs/agent-workspace.md`](/concepts/agent-workspace)
If `agent.sandbox` is enabled, non-main sessions can override this with
per-session workspaces under `agent.sandbox.workspaceRoot` (see
If `agents.defaults.sandbox` is enabled, non-main sessions can override this with
per-session workspaces under `agents.defaults.sandbox.workspaceRoot` (see
[`docs/configuration.md`](/gateway/configuration)).
## Bootstrap files (injected)
Inside `agent.workspace`, CLAWDBOT expects these user-editable files:
Inside `agents.defaults.workspace`, CLAWDBOT expects these user-editable files:
- `AGENTS.md` — operating instructions + “memory”
- `SOUL.md` — persona, boundaries, tone
- `TOOLS.md` — user-maintained tool notes (e.g. `imsg`, `sag`, conventions)
@@ -84,9 +84,9 @@ current turn ends, then a new agent turn starts with the queued payloads. See
[`docs/queue.md`](/concepts/queue) for mode + debounce/cap behavior.
Block streaming sends completed assistant blocks as soon as they finish; disable
via `agent.blockStreamingDefault: "off"` if you only want the final response.
Tune the boundary via `agent.blockStreamingBreak` (`text_end` vs `message_end`; defaults to text_end).
Control soft block chunking with `agent.blockStreamingChunk` (defaults to
via `agents.defaults.blockStreamingDefault: "off"` if you only want the final response.
Tune the boundary via `agents.defaults.blockStreamingBreak` (`text_end` vs `message_end`; defaults to text_end).
Control soft block chunking with `agents.defaults.blockStreamingChunk` (defaults to
8001200 chars; prefers paragraph breaks, then newlines; sentences last).
Verbose tool summaries are emitted at tool start (no debounce); Control UI
streams tool output via agent events when available.
@@ -95,7 +95,7 @@ More details: [Streaming + chunking](/concepts/streaming).
## Configuration (minimal)
At minimum, set:
- `agent.workspace`
- `agents.defaults.workspace`
- `whatsapp.allowFrom` (strongly recommended)
---

View File

@@ -7,7 +7,7 @@ read_when:
Goal: let Clawd sit in WhatsApp groups, wake up only when pinged, and keep that thread separate from the personal DM session.
Note: `routing.groupChat.mentionPatterns` is now used by Telegram/Discord/Slack/iMessage as well; this doc focuses on WhatsApp-specific behavior. For multi-agent setups, you can override per agent with `routing.agents.<agentId>.mentionPatterns`.
Note: `agents.list[].groupChat.mentionPatterns` is now used by Telegram/Discord/Slack/iMessage as well; this doc focuses on WhatsApp-specific behavior. For multi-agent setups, set `agents.list[].groupChat.mentionPatterns` per agent (or use `messages.groupChat.mentionPatterns` as a global fallback).
## Whats implemented (2025-12-03)
- Activation modes: `mention` (default) or `always`. `mention` requires a ping (real WhatsApp @-mentions via `mentionedJids`, regex patterns, or the bots E.164 anywhere in the text). `always` wakes the agent on every message but it should reply only when it can add meaningful value; otherwise it returns the silent token `NO_REPLY`. Defaults can be set in config (`whatsapp.groups`) and overridden per group via `/activation`. When `whatsapp.groups` is set, it also acts as a group allowlist (include `"*"` to allow all).
@@ -28,16 +28,21 @@ Add a `groupChat` block to `~/.clawdbot/clawdbot.json` so display-name pings wor
"*": { "requireMention": true }
}
},
"routing": {
"groupChat": {
"historyLimit": 50,
"mentionPatterns": [
"@?clawd",
"@?clawd\\s*uk",
"@?clawdbot",
"\\+?447700900123"
]
}
"agents": {
"list": [
{
"id": "main",
"groupChat": {
"historyLimit": 50,
"mentionPatterns": [
"@?clawd",
"@?clawd\\s*uk",
"@?clawdbot",
"\\+?447700900123"
]
}
}
]
}
}
```
@@ -70,4 +75,4 @@ Only the owner number (from `whatsapp.allowFrom`, or the bots own E.164 when
- Heartbeats are intentionally skipped for groups to avoid noisy broadcasts.
- Echo suppression uses the combined batch string; if you send identical text twice without mentions, only the first will get a response.
- Session store entries will appear as `agent:<agentId>:whatsapp:group:<jid>` in the session store (`~/.clawdbot/agents/<agentId>/sessions/sessions.json` by default); a missing entry just means the group hasnt triggered a run yet.
- Typing indicators in groups follow `agent.typingMode` (default: `message` when unmentioned).
- Typing indicators in groups follow `agents.defaults.typingMode` (default: `message` when unmentioned).

View File

@@ -88,11 +88,16 @@ Group messages require a mention unless overridden per group. Defaults live per
"123": { requireMention: false }
}
},
routing: {
groupChat: {
mentionPatterns: ["@clawd", "clawdbot", "\\+15555550123"],
historyLimit: 50
}
agents: {
list: [
{
id: "main",
groupChat: {
mentionPatterns: ["@clawd", "clawdbot", "\\+15555550123"],
historyLimit: 50
}
}
]
}
}
```
@@ -100,7 +105,7 @@ Group messages require a mention unless overridden per group. Defaults live per
Notes:
- `mentionPatterns` are case-insensitive regexes.
- Surfaces that provide explicit mentions still pass; patterns are a fallback.
- Per-agent override: `routing.agents.<agentId>.mentionPatterns` (useful when multiple agents share a group).
- Per-agent override: `agents.list[].groupChat.mentionPatterns` (useful when multiple agents share a group).
- Mention gating is only enforced when mention detection is possible (native mentions or `mentionPatterns` are configured).
- Discord defaults live in `discord.guilds."*"` (overridable per guild/channel).

View File

@@ -9,7 +9,7 @@ read_when:
Clawdbot handles failures in two stages:
1) **Auth profile rotation** within the current provider.
2) **Model fallback** to the next model in `agent.model.fallbacks`.
2) **Model fallback** to the next model in `agents.defaults.model.fallbacks`.
This doc explains the runtime rules and the data that backs them.
@@ -82,14 +82,14 @@ State is stored in `auth-profiles.json` under `usageStats`:
## Model fallback
If all profiles for a provider fail, Clawdbot moves to the next model in
`agent.model.fallbacks`. This applies to auth failures, rate limits, and
`agents.defaults.model.fallbacks`. This applies to auth failures, rate limits, and
timeouts that exhausted profile rotation.
## Related config
See [`docs/configuration.md`](/gateway/configuration) for:
- `auth.profiles` / `auth.order`
- `agent.model.primary` / `agent.model.fallbacks`
- `agent.imageModel` routing
- `agents.defaults.model.primary` / `agents.defaults.model.fallbacks`
- `agents.defaults.imageModel` routing
See [`docs/models.md`](/concepts/models) for the broader model selection and fallback overview.

View File

@@ -14,20 +14,20 @@ rotation, cooldowns, and how that interacts with fallbacks.
Clawdbot selects models in this order:
1) **Primary** model (`agent.model.primary` or `agent.model`).
2) **Fallbacks** in `agent.model.fallbacks` (in order).
1) **Primary** model (`agents.defaults.model.primary` or `agents.defaults.model`).
2) **Fallbacks** in `agents.defaults.model.fallbacks` (in order).
3) **Provider auth failover** happens inside a provider before moving to the
next model.
Related:
- `agent.models` is the allowlist/catalog of models Clawdbot can use (plus aliases).
- `agent.imageModel` is used **only when** the primary model cant accept images.
- `agents.defaults.models` is the allowlist/catalog of models Clawdbot can use (plus aliases).
- `agents.defaults.imageModel` is used **only when** the primary model cant accept images.
## Config keys (overview)
- `agent.model.primary` and `agent.model.fallbacks`
- `agent.imageModel.primary` and `agent.imageModel.fallbacks`
- `agent.models` (allowlist + aliases + provider params)
- `agents.defaults.model.primary` and `agents.defaults.model.fallbacks`
- `agents.defaults.imageModel.primary` and `agents.defaults.imageModel.fallbacks`
- `agents.defaults.models` (allowlist + aliases + provider params)
- `models.providers` (custom providers written into `models.json`)
Model refs are normalized to lowercase. Provider aliases like `z.ai/*` normalize
@@ -35,7 +35,7 @@ to `zai/*`.
## “Model is not allowed” (and why replies stop)
If `agent.models` is set, it becomes the **allowlist** for `/model` and for
If `agents.defaults.models` is set, it becomes the **allowlist** for `/model` and for
session overrides. When a user selects a model that isnt in that allowlist,
Clawdbot returns:
@@ -46,8 +46,8 @@ Model "provider/model" is not allowed. Use /model to list available models.
This happens **before** a normal reply is generated, so the message can feel
like it “didnt respond.” The fix is to either:
- Add the model to `agent.models`, or
- Clear the allowlist (remove `agent.models`), or
- Add the model to `agents.defaults.models`, or
- Clear the allowlist (remove `agents.defaults.models`), or
- Pick a model from `/model list`.
Example allowlist config:
@@ -123,8 +123,8 @@ Key flags:
- `--max-age-days <days>`: skip older models
- `--provider <name>`: provider prefix filter
- `--max-candidates <n>`: fallback list size
- `--set-default`: set `agent.model.primary` to the first selection
- `--set-image`: set `agent.imageModel.primary` to the first image selection
- `--set-default`: set `agents.defaults.model.primary` to the first selection
- `--set-image`: set `agents.defaults.imageModel.primary` to the first image selection
Probing requires an OpenRouter API key (from auth profiles or
`OPENROUTER_API_KEY`). Without a key, use `--no-probe` to list candidates only.

View File

@@ -32,7 +32,7 @@ reach other host locations unless sandboxing is enabled. See
- Config: `~/.clawdbot/clawdbot.json` (or `CLAWDBOT_CONFIG_PATH`)
- State dir: `~/.clawdbot` (or `CLAWDBOT_STATE_DIR`)
- Workspace: `~/clawd` (or `~/clawd-<agentId>`)
- Agent dir: `~/.clawdbot/agents/<agentId>/agent` (or `routing.agents.<agentId>.agentDir`)
- Agent dir: `~/.clawdbot/agents/<agentId>/agent` (or `agents.list[].agentDir`)
- Sessions: `~/.clawdbot/agents/<agentId>/sessions`
### Single-agent mode (default)
@@ -52,7 +52,7 @@ Use the agent wizard to add a new isolated agent:
clawdbot agents add work
```
Then add `routing.bindings` (or let the wizard do it) to route inbound messages.
Then add `bindings` (or let the wizard do it) to route inbound messages.
Verify with:
@@ -79,7 +79,7 @@ Bindings are **deterministic** and **most-specific wins**:
3. `teamId` (Slack)
4. `accountId` match for a provider
5. provider-level match (`accountId: "*"`)
6. fallback to `routing.defaultAgentId` (default: `main`)
6. fallback to default agent (`agents.list[].default`, else first list entry, default: `main`)
## Multiple accounts / phone numbers
@@ -100,39 +100,42 @@ multiple phone numbers without mixing sessions.
```js
{
routing: {
defaultAgentId: "home",
agents: {
home: {
agents: {
list: [
{
id: "home",
default: true,
name: "Home",
workspace: "~/clawd-home",
agentDir: "~/.clawdbot/agents/home/agent",
},
work: {
{
id: "work",
name: "Work",
workspace: "~/clawd-work",
agentDir: "~/.clawdbot/agents/work/agent",
},
},
// Deterministic routing: first match wins (most-specific first).
bindings: [
{ agentId: "home", match: { provider: "whatsapp", accountId: "personal" } },
{ agentId: "work", match: { provider: "whatsapp", accountId: "biz" } },
// Optional per-peer override (example: send a specific group to work agent).
{
agentId: "work",
match: {
provider: "whatsapp",
accountId: "personal",
peer: { kind: "group", id: "1203630...@g.us" },
},
},
],
},
// Off by default: agent-to-agent messaging must be explicitly enabled + allowlisted.
// Deterministic routing: first match wins (most-specific first).
bindings: [
{ agentId: "home", match: { provider: "whatsapp", accountId: "personal" } },
{ agentId: "work", match: { provider: "whatsapp", accountId: "biz" } },
// Optional per-peer override (example: send a specific group to work agent).
{
agentId: "work",
match: {
provider: "whatsapp",
accountId: "personal",
peer: { kind: "group", id: "1203630...@g.us" },
},
},
],
// Off by default: agent-to-agent messaging must be explicitly enabled + allowlisted.
tools: {
agentToAgent: {
enabled: false,
allow: ["home", "work"],
@@ -160,16 +163,18 @@ Starting with v2026.1.6, each agent can have its own sandbox and tool restrictio
```js
{
routing: {
agents: {
personal: {
agents: {
list: [
{
id: "personal",
workspace: "~/clawd-personal",
sandbox: {
mode: "off", // No sandbox for personal agent
},
// No tool restrictions - all tools available
},
family: {
{
id: "family",
workspace: "~/clawd-family",
sandbox: {
mode: "all", // Always sandboxed
@@ -184,7 +189,7 @@ Starting with v2026.1.6, each agent can have its own sandbox and tool restrictio
deny: ["bash", "write", "edit"], // Deny others
},
},
},
],
},
}
```
@@ -194,8 +199,8 @@ Starting with v2026.1.6, each agent can have its own sandbox and tool restrictio
- **Resource control**: Sandbox specific agents while keeping others on host
- **Flexible policies**: Different permissions per agent
Note: `agent.elevated` is **global** and sender-based; it is not configurable per agent.
If you need per-agent boundaries, use `routing.agents[id].tools` to deny `bash`.
For group targeting, you can set `routing.agents[id].mentionPatterns` so @mentions map cleanly to the intended agent.
Note: `tools.elevated` is **global** and sender-based; it is not configurable per agent.
If you need per-agent boundaries, use `agents.list[].tools` to deny `bash`.
For group targeting, use `agents.list[].groupChat.mentionPatterns` so @mentions map cleanly to the intended agent.
See [Multi-Agent Sandbox & Tools](/multi-agent-sandbox-tools) for detailed examples.

View File

@@ -42,35 +42,33 @@ Examples:
Routing picks **one agent** for each inbound message:
1. **Exact peer match** (`routing.bindings` with `peer.kind` + `peer.id`).
1. **Exact peer match** (`bindings` with `peer.kind` + `peer.id`).
2. **Guild match** (Discord) via `guildId`.
3. **Team match** (Slack) via `teamId`.
4. **Account match** (`accountId` on the provider).
5. **Provider match** (any account on that provider).
6. **Default agent** (`routing.defaultAgentId`, fallback to `main`).
6. **Default agent** (`agents.list[].default`, else first list entry, fallback to `main`).
The matched agent determines which workspace and session store are used.
## Config overview
- `routing.defaultAgentId`: default agent when no binding matches.
- `routing.agents`: named agent definitions (workspace, model, etc.).
- `routing.bindings`: map inbound providers/accounts/peers to agents.
- `agents.list`: named agent definitions (workspace, model, etc.).
- `bindings`: map inbound providers/accounts/peers to agents.
Example:
```json5
{
routing: {
defaultAgentId: "main",
agents: {
support: { name: "Support", workspace: "~/clawd-support" }
},
bindings: [
{ match: { provider: "slack", teamId: "T123" }, agentId: "support" },
{ match: { provider: "telegram", peer: { kind: "group", id: "-100123" } }, agentId: "support" }
agents: {
list: [
{ id: "support", name: "Support", workspace: "~/clawd-support" }
]
}
},
bindings: [
{ match: { provider: "slack", teamId: "T123" }, agentId: "support" },
{ match: { provider: "telegram", peer: { kind: "group", id: "-100123" } }, agentId: "support" }
]
}
```

View File

@@ -14,7 +14,7 @@ We now serialize command-based auto-replies (WhatsApp Web listener) through a ti
## How it works
- A lane-aware FIFO queue drains each lane synchronously.
- `runEmbeddedPiAgent` enqueues by **session key** (lane `session:<key>`) to guarantee only one active run per session.
- Each session run is then queued into a **global lane** (`main` by default) so overall parallelism is capped by `agent.maxConcurrent`.
- Each session run is then queued into a **global lane** (`main` by default) so overall parallelism is capped by `agents.defaults.maxConcurrent`.
- When verbose logging is enabled, queued commands emit a short notice if they waited more than ~2s before starting.
- Typing indicators (`onReplyStart`) still fire immediately on enqueue so user experience is unchanged while we wait our turn.
@@ -30,16 +30,16 @@ Inbound messages can steer the current run, wait for a followup turn, or do both
Steer-backlog means you can get a followup response after the steered run, so
streaming surfaces can look like duplicates. Prefer `collect`/`steer` if you want
one response per inbound message.
Send `/queue collect` as a standalone command (per-session) or set `routing.queue.byProvider.discord: "collect"`.
Send `/queue collect` as a standalone command (per-session) or set `messages.queue.byProvider.discord: "collect"`.
Defaults (when unset in config):
- All surfaces → `collect`
Configure globally or per provider via `routing.queue`:
Configure globally or per provider via `messages.queue`:
```json5
{
routing: {
messages: {
queue: {
mode: "collect",
debounceMs: 1000,
@@ -67,7 +67,7 @@ Defaults: `debounceMs: 1000`, `cap: 20`, `drop: summarize`.
## Scope and guarantees
- Applies only to config-driven command replies; plain text replies are unaffected.
- Default lane (`main`) is process-wide for inbound + main heartbeats; set `agent.maxConcurrent` to allow multiple sessions in parallel.
- Default lane (`main`) is process-wide for inbound + main heartbeats; set `agents.defaults.maxConcurrent` to allow multiple sessions in parallel.
- Additional lanes may exist (e.g. `cron`) so background jobs can run in parallel without blocking inbound replies.
- Per-session lanes guarantee that only one agent run touches a given session at a time.
- No external dependencies or background worker threads; pure TypeScript + promises.

View File

@@ -2,7 +2,7 @@
summary: "Session pruning: tool-result trimming to reduce context bloat"
read_when:
- You want to reduce LLM context growth from tool outputs
- You are tuning agent.contextPruning
- You are tuning agents.defaults.contextPruning
---
# Session Pruning
@@ -23,7 +23,7 @@ Session pruning trims **old tool results** from the in-memory context right befo
Pruning uses an estimated context window (chars ≈ tokens × 4). The window size is resolved in this order:
1) Model definition `contextWindow` (from the model registry).
2) `models.providers.*.models[].contextWindow` override.
3) `agent.contextTokens`.
3) `agents.defaults.contextTokens`.
4) Default `200000` tokens.
## Modes

View File

@@ -132,19 +132,19 @@ Parameters:
- `cleanup?` (`delete|keep`, default `keep`)
Allowlist:
- `routing.agents.<agentId>.subagents.allowAgents`: list of agent ids allowed via `agentId` (`["*"]` to allow any). Default: only the requester agent.
- `agents.list[].subagents.allowAgents`: list of agent ids allowed via `agentId` (`["*"]` to allow any). Default: only the requester agent.
Discovery:
- Use `agents_list` to discover which agent ids are allowed for `sessions_spawn`.
Behavior:
- Starts a new `agent:<agentId>:subagent:<uuid>` session with `deliver: false`.
- Sub-agents default to the full tool set **minus session tools** (configurable via `agent.subagents.tools`).
- Sub-agents default to the full tool set **minus session tools** (configurable via `tools.subagents.tools`).
- Sub-agents are not allowed to call `sessions_spawn` (no sub-agent → sub-agent spawning).
- Always non-blocking: returns `{ status: "accepted", runId, childSessionKey }` immediately.
- After completion, Clawdbot runs a sub-agent **announce step** and posts the result to the requester chat provider.
- Reply exactly `ANNOUNCE_SKIP` during the announce step to stay silent.
- Sub-agent sessions are auto-archived after `agent.subagents.archiveAfterMinutes` (default: 60).
- Sub-agent sessions are auto-archived after `agents.defaults.subagents.archiveAfterMinutes` (default: 60).
- Announce replies include a stats line (runtime, tokens, sessionKey/sessionId, transcript path, and optional cost).
## Sandbox Session Visibility
@@ -155,10 +155,12 @@ Config:
```json5
{
agent: {
sandbox: {
// default: "spawned"
sessionToolsVisibility: "spawned" // or "all"
agents: {
defaults: {
sandbox: {
// default: "spawned"
sessionToolsVisibility: "spawned" // or "all"
}
}
}
}

View File

@@ -32,9 +32,9 @@ Legend:
- `provider send`: actual outbound messages (block replies).
**Controls:**
- `agent.blockStreamingDefault`: `"on"`/`"off"` (default on).
- `agent.blockStreamingBreak`: `"text_end"` or `"message_end"`.
- `agent.blockStreamingChunk`: `{ minChars, maxChars, breakPreference? }`.
- `agents.defaults.blockStreamingDefault`: `"on"`/`"off"` (default on).
- `agents.defaults.blockStreamingBreak`: `"text_end"` or `"message_end"`.
- `agents.defaults.blockStreamingChunk`: `{ minChars, maxChars, breakPreference? }`.
- Provider hard cap: `*.textChunkLimit` (e.g., `whatsapp.textChunkLimit`).
- Discord soft cap: `discord.maxLinesPerMessage` (default 17) splits tall replies to avoid UI clipping.

View File

@@ -17,7 +17,7 @@ The prompt is intentionally compact and uses fixed sections:
- **Tooling**: current tool list + short descriptions.
- **Skills**: tells the model how to load skill instructions on demand.
- **Clawdbot Self-Update**: how to run `config.apply` and `update.run`.
- **Workspace**: working directory (`agent.workspace`).
- **Workspace**: working directory (`agents.defaults.workspace`).
- **Workspace Files (injected)**: indicates bootstrap files are included below.
- **Time**: UTC default + the users local time (already converted).
- **Reply Tags**: optional reply tag syntax for supported providers.
@@ -43,9 +43,9 @@ Large files are truncated with a marker. Missing files inject a short missing-fi
The Time line is compact and explicit:
- Assume timestamps are **UTC** unless stated.
- The listed **user time** is already converted to `agent.userTimezone` (if set).
- The listed **user time** is already converted to `agents.defaults.userTimezone` (if set).
Use `agent.userTimezone` in `~/.clawdbot/clawdbot.json` to change the user time zone.
Use `agents.defaults.userTimezone` in `~/.clawdbot/clawdbot.json` to change the user time zone.
## Skills

View File

@@ -26,7 +26,7 @@ These are typically UTC ISO strings (Discord) or UTC epoch strings (Slack). We d
## User timezone for the system prompt
Set `agent.userTimezone` to tell the model the user's local time zone. If it is
Set `agents.defaults.userTimezone` to tell the model the user's local time zone. If it is
unset, Clawdbot resolves the **host timezone at runtime** (no config write).
```json5

View File

@@ -6,18 +6,18 @@ read_when:
# Typing indicators
Typing indicators are sent to the chat provider while a run is active. Use
`agent.typingMode` to control **when** typing starts and `typingIntervalSeconds`
`agents.defaults.typingMode` to control **when** typing starts and `typingIntervalSeconds`
to control **how often** it refreshes.
## Defaults
When `agent.typingMode` is **unset**, Clawdbot keeps the legacy behavior:
When `agents.defaults.typingMode` is **unset**, Clawdbot keeps the legacy behavior:
- **Direct chats**: typing starts immediately once the model loop begins.
- **Group chats with a mention**: typing starts immediately.
- **Group chats without a mention**: typing starts only when message text begins streaming.
- **Heartbeat runs**: typing is disabled.
## Modes
Set `agent.typingMode` to one of:
Set `agents.defaults.typingMode` to one of:
- `never` — no typing indicator, ever.
- `instant` — start typing **as soon as the model loop begins**, even if the run
later returns only the silent reply token.

View File

@@ -8,7 +8,7 @@ read_when:
# Workspace Memory v2 (offline): research notes
Target: Clawd-style workspace (`agent.workspace`, default `~/clawd`) where “memory” is stored as one Markdown file per day (`memory/YYYY-MM-DD.md`) plus a small set of stable files (e.g. `memory.md`, `SOUL.md`).
Target: Clawd-style workspace (`agents.defaults.workspace`, default `~/clawd`) where “memory” is stored as one Markdown file per day (`memory/YYYY-MM-DD.md`) plus a small set of stable files (e.g. `memory.md`, `SOUL.md`).
This doc proposes an **offline-first** memory architecture that keeps Markdown as the canonical, reviewable source of truth, but adds **structured recall** (search, entity summaries, confidence updates) via a derived index.
@@ -159,7 +159,7 @@ Recommendation: **deep integration in Clawdbot**, but keep a separable core libr
### Why integrate into Clawdbot?
- Clawdbot already knows:
- the workspace path (`agent.workspace`)
- the workspace path (`agents.defaults.workspace`)
- the session model + heartbeats
- logging + troubleshooting patterns
- You want the agent itself to call the tools:

View File

@@ -32,9 +32,9 @@ Environment overrides:
- `PI_BASH_JOB_TTL_MS`: TTL for finished sessions (ms, bounded to 1m3h)
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

View File

@@ -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
}
}
},

View File

@@ -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 havent 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 agents `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 users 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 agents `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
8001200 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 (05, 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 |
|----------|-------------|

View File

@@ -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:

View File

@@ -22,7 +22,7 @@ Short guide to verify the WhatsApp Web / Baileys stack without guessing.
## When something fails
- `logged out` or status 409515 → 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.

View File

@@ -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
}
}
}
}

View File

@@ -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:

View File

@@ -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 doesnt 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)

View File

@@ -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 dont 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 dont 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"]
}
}
}
]
}
}
```

View File

@@ -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}"
```

View File

@@ -109,12 +109,12 @@ Deep dive: [Sandboxing](/gateway/sandboxing)
### What it does
When `agent.sandbox` is enabled, **non-main sessions** run tools inside a Docker
When `agents.defaults.sandbox` is enabled, **non-main sessions** run tools inside a Docker
container. The gateway stays on your host, but the tool execution is isolated:
- scope: `"agent"` by default (one container + workspace per agent)
- scope: `"session"` for per-session isolation
- per-scope workspace folder mounted at `/workspace`
- optional agent workspace access (`agent.sandbox.workspaceAccess`)
- optional agent workspace access (`agents.defaults.sandbox.workspaceAccess`)
- allow/deny tool policy (deny wins)
- inbound media is copied into the active sandbox workspace (`media/inbound/*`) so tools can read it (with `workspaceAccess: "rw"`, this lands in the agent workspace)
@@ -124,7 +124,7 @@ one container and one workspace.
### Per-agent sandbox profiles (multi-agent)
If you use multi-agent routing, each agent can override sandbox + tool settings:
`routing.agents[id].sandbox` and `routing.agents[id].tools`. This lets you run
`agents.list[].sandbox` and `agents.list[].tools` (plus `agents.list[].tools.sandbox.tools`). This lets you run
mixed access levels in one gateway:
- Full access (personal agent)
- Read-only tools + read-only workspace (family/work agent)
@@ -149,54 +149,60 @@ precedence, and troubleshooting.
```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",
workdir: "/workspace",
readOnlyRoot: true,
tmpfs: ["/tmp", "/var/tmp", "/run"],
network: "none",
user: "1000:1000",
capDrop: ["ALL"],
env: { LANG: "C.UTF-8" },
setupCommand: "apt-get update && apt-get install -y git curl jq",
pidsLimit: 256,
memory: "1g",
memorySwap: "2g",
cpus: 1,
ulimits: {
nofile: { soft: 1024, hard: 2048 },
nproc: 256
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",
workdir: "/workspace",
readOnlyRoot: true,
tmpfs: ["/tmp", "/var/tmp", "/run"],
network: "none",
user: "1000:1000",
capDrop: ["ALL"],
env: { LANG: "C.UTF-8" },
setupCommand: "apt-get update && apt-get install -y git curl jq",
pidsLimit: 256,
memory: "1g",
memorySwap: "2g",
cpus: 1,
ulimits: {
nofile: { soft: 1024, hard: 2048 },
nproc: 256
},
seccompProfile: "/path/to/seccomp.json",
apparmorProfile: "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"]
},
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
}
}
}
}
```
Hardening knobs live under `agent.sandbox.docker`:
Hardening knobs live under `agents.defaults.sandbox.docker`:
`network`, `user`, `pidsLimit`, `memory`, `memorySwap`, `cpus`, `ulimits`,
`seccompProfile`, `apparmorProfile`, `dns`, `extraHosts`.
Multi-agent: override `agent.sandbox.{docker,browser,prune}.*` per agent via `routing.agents.<agentId>.sandbox.{docker,browser,prune}.*`
(ignored when `agent.sandbox.scope` / `routing.agents.<agentId>.sandbox.scope` is `"shared"`).
Multi-agent: override `agents.defaults.sandbox.{docker,browser,prune}.*` per agent via `agents.list[].sandbox.{docker,browser,prune}.*`
(ignored when `agents.defaults.sandbox.scope` / `agents.list[].sandbox.scope` is `"shared"`).
### Build the default sandbox image
@@ -217,7 +223,7 @@ This builds `clawdbot-sandbox-common:bookworm-slim`. To use it:
```json5
{
agent: { sandbox: { docker: { image: "clawdbot-sandbox-common:bookworm-slim" } } }
agents: { defaults: { sandbox: { docker: { image: "clawdbot-sandbox-common:bookworm-slim" } } } }
}
```
@@ -235,16 +241,18 @@ an optional noVNC observer (headful via Xvfb).
Notes:
- Headful (Xvfb) reduces bot blocking vs headless.
- Headless can still be used by setting `agent.sandbox.browser.headless=true`.
- Headless can still be used by setting `agents.defaults.sandbox.browser.headless=true`.
- No full desktop environment (GNOME) is needed; Xvfb provides the display.
Use config:
```json5
{
agent: {
sandbox: {
browser: { enabled: true }
agents: {
defaults: {
sandbox: {
browser: { enabled: true }
}
}
}
}
@@ -254,8 +262,10 @@ Custom browser image:
```json5
{
agent: {
sandbox: { browser: { image: "my-clawdbot-browser" } }
agents: {
defaults: {
sandbox: { browser: { image: "my-clawdbot-browser" } }
}
}
}
```
@@ -266,7 +276,7 @@ When enabled, the agent receives:
Remember: if you use an allowlist for tools, add `browser` (and remove it from
deny) or the tool remains blocked.
Prune rules (`agent.sandbox.prune`) apply to browser containers too.
Prune rules (`agents.defaults.sandbox.prune`) apply to browser containers too.
### Custom sandbox image
@@ -278,8 +288,10 @@ docker build -t my-clawdbot-sbx -f Dockerfile.sandbox .
```json5
{
agent: {
sandbox: { docker: { image: "my-clawdbot-sbx" } }
agents: {
defaults: {
sandbox: { docker: { image: "my-clawdbot-sbx" } }
}
}
}
```
@@ -310,7 +322,7 @@ Example:
## Troubleshooting
- Image missing: build with [`scripts/sandbox-setup.sh`](https://github.com/clawdbot/clawdbot/blob/main/scripts/sandbox-setup.sh) or set `agent.sandbox.docker.image`.
- Image missing: build with [`scripts/sandbox-setup.sh`](https://github.com/clawdbot/clawdbot/blob/main/scripts/sandbox-setup.sh) or set `agents.defaults.sandbox.docker.image`.
- Container not running: it will auto-create per session on demand.
- Permission errors in sandbox: set `docker.user` to a UID:GID that matches your
mounted workspace ownership (or chown the workspace folder).

View File

@@ -10,8 +10,8 @@ status: active
## Overview
Each agent in a multi-agent setup can now have its own:
- **Sandbox configuration** (`mode`, `scope`, `workspaceRoot`, `workspaceAccess`, `tools`)
- **Tool restrictions** (`allow`, `deny`)
- **Sandbox configuration** (`agents.list[].sandbox` overrides `agents.defaults.sandbox`)
- **Tool restrictions** (`tools.allow` / `tools.deny`, plus `agents.list[].tools`)
This allows you to run multiple agents with different security profiles:
- Personal assistant with full access
@@ -28,18 +28,17 @@ For how sandboxing behaves at runtime, see [Sandboxing](/gateway/sandboxing).
```json
{
"routing": {
"defaultAgentId": "main",
"agents": {
"main": {
"agents": {
"list": [
{
"id": "main",
"default": true,
"name": "Personal Assistant",
"workspace": "~/clawd",
"sandbox": {
"mode": "off"
}
// No tool restrictions - all tools available
"sandbox": { "mode": "off" }
},
"family": {
{
"id": "family",
"name": "Family Bot",
"workspace": "~/clawd-family",
"sandbox": {
@@ -51,21 +50,21 @@ For how sandboxing behaves at runtime, see [Sandboxing](/gateway/sandboxing).
"deny": ["bash", "write", "edit", "process", "browser"]
}
}
},
"bindings": [
{
"agentId": "family",
"match": {
"provider": "whatsapp",
"accountId": "*",
"peer": {
"kind": "group",
"id": "120363424282127706@g.us"
}
]
},
"bindings": [
{
"agentId": "family",
"match": {
"provider": "whatsapp",
"accountId": "*",
"peer": {
"kind": "group",
"id": "120363424282127706@g.us"
}
}
]
}
}
]
}
```
@@ -79,13 +78,15 @@ For how sandboxing behaves at runtime, see [Sandboxing](/gateway/sandboxing).
```json
{
"routing": {
"agents": {
"personal": {
"agents": {
"list": [
{
"id": "personal",
"workspace": "~/clawd-personal",
"sandbox": { "mode": "off" }
},
"work": {
{
"id": "work",
"workspace": "~/clawd-work",
"sandbox": {
"mode": "all",
@@ -97,7 +98,7 @@ For how sandboxing behaves at runtime, see [Sandboxing](/gateway/sandboxing).
"deny": ["browser", "gateway", "discord"]
}
}
}
]
}
}
```
@@ -108,21 +109,23 @@ For how sandboxing behaves at runtime, see [Sandboxing](/gateway/sandboxing).
```json
{
"agent": {
"sandbox": {
"mode": "non-main", // Global default
"scope": "session"
}
},
"routing": {
"agents": {
"main": {
"agents": {
"defaults": {
"sandbox": {
"mode": "non-main", // Global default
"scope": "session"
}
},
"list": [
{
"id": "main",
"workspace": "~/clawd",
"sandbox": {
"mode": "off" // Override: main never sandboxed
}
},
"public": {
{
"id": "public",
"workspace": "~/clawd-public",
"sandbox": {
"mode": "all", // Override: public always sandboxed
@@ -133,7 +136,7 @@ For how sandboxing behaves at runtime, see [Sandboxing](/gateway/sandboxing).
"deny": ["bash", "write", "edit"]
}
}
}
]
}
}
```
@@ -142,40 +145,40 @@ For how sandboxing behaves at runtime, see [Sandboxing](/gateway/sandboxing).
## Configuration Precedence
When both global (`agent.*`) and agent-specific (`routing.agents[id].*`) configs exist:
When both global (`agents.defaults.*`) and agent-specific (`agents.list[].*`) configs exist:
### Sandbox Config
Agent-specific settings override global:
```
routing.agents[id].sandbox.mode > agent.sandbox.mode
routing.agents[id].sandbox.scope > agent.sandbox.scope
routing.agents[id].sandbox.workspaceRoot > agent.sandbox.workspaceRoot
routing.agents[id].sandbox.workspaceAccess > agent.sandbox.workspaceAccess
routing.agents[id].sandbox.docker.* > agent.sandbox.docker.*
routing.agents[id].sandbox.browser.* > agent.sandbox.browser.*
routing.agents[id].sandbox.prune.* > agent.sandbox.prune.*
agents.list[].sandbox.mode > agents.defaults.sandbox.mode
agents.list[].sandbox.scope > agents.defaults.sandbox.scope
agents.list[].sandbox.workspaceRoot > agents.defaults.sandbox.workspaceRoot
agents.list[].sandbox.workspaceAccess > agents.defaults.sandbox.workspaceAccess
agents.list[].sandbox.docker.* > agents.defaults.sandbox.docker.*
agents.list[].sandbox.browser.* > agents.defaults.sandbox.browser.*
agents.list[].sandbox.prune.* > agents.defaults.sandbox.prune.*
```
**Notes:**
- `routing.agents[id].sandbox.{docker,browser,prune}.*` overrides `agent.sandbox.{docker,browser,prune}.*` for that agent (ignored when sandbox scope resolves to `"shared"`).
- `agents.list[].sandbox.{docker,browser,prune}.*` overrides `agents.defaults.sandbox.{docker,browser,prune}.*` for that agent (ignored when sandbox scope resolves to `"shared"`).
### Tool Restrictions
The filtering order is:
1. **Global tool policy** (`agent.tools`)
2. **Agent-specific tool policy** (`routing.agents[id].tools`)
3. **Sandbox tool policy** (`agent.sandbox.tools` or `routing.agents[id].sandbox.tools`)
4. **Subagent tool policy** (if applicable)
1. **Global tool policy** (`tools.allow` / `tools.deny`)
2. **Agent-specific tool policy** (`agents.list[].tools`)
3. **Sandbox tool policy** (`tools.sandbox.tools` or `agents.list[].tools.sandbox.tools`)
4. **Subagent tool policy** (`tools.subagents.tools`, if applicable)
Each level can further restrict tools, but cannot grant back denied tools from earlier levels.
If `routing.agents[id].sandbox.tools` is set, it replaces `agent.sandbox.tools` for that agent.
If `agents.list[].tools.sandbox.tools` is set, it replaces `tools.sandbox.tools` for that agent.
### Elevated Mode (global)
`agent.elevated` is **global** and **sender-based** (per-provider allowlist). It is **not** configurable per agent.
`tools.elevated` is **global** and **sender-based** (per-provider allowlist). It is **not** configurable per agent.
Mitigation patterns:
- Deny `bash` for untrusted agents (`routing.agents[id].tools.deny: ["bash"]`)
- Deny `bash` for untrusted agents (`agents.list[].tools.deny: ["bash"]`)
- Avoid allowlisting senders that route to restricted agents
- Disable elevated globally (`agent.elevated.enabled: false`) if you only want sandboxed execution
- Disable elevated globally (`tools.elevated.enabled: false`) if you only want sandboxed execution
---
@@ -184,10 +187,16 @@ Mitigation patterns:
**Before (single agent):**
```json
{
"agent": {
"workspace": "~/clawd",
"agents": {
"defaults": {
"workspace": "~/clawd",
"sandbox": {
"mode": "non-main"
}
}
},
"tools": {
"sandbox": {
"mode": "non-main",
"tools": {
"allow": ["read", "write", "bash"],
"deny": []
@@ -200,21 +209,20 @@ Mitigation patterns:
**After (multi-agent with different profiles):**
```json
{
"routing": {
"defaultAgentId": "main",
"agents": {
"main": {
"agents": {
"list": [
{
"id": "main",
"default": true,
"workspace": "~/clawd",
"sandbox": {
"mode": "off"
}
"sandbox": { "mode": "off" }
}
}
]
}
}
```
The global `agent.workspace` and `agent.sandbox` are still supported for backward compatibility, but we recommend using `routing.agents` for clarity in multi-agent setups.
Legacy `agent.*` configs are migrated by `clawdbot doctor`; prefer `agents.defaults` + `agents.list` going forward.
---
@@ -254,10 +262,10 @@ The global `agent.workspace` and `agent.sandbox` are still supported for backwar
## Common Pitfall: "non-main"
`sandbox.mode: "non-main"` is based on `session.mainKey` (default `"main"`),
`agents.defaults.sandbox.mode: "non-main"` is based on `session.mainKey` (default `"main"`),
not the agent id. Group/channel sessions always get their own keys, so they
are treated as non-main and will be sandboxed. If you want an agent to never
sandbox, set `routing.agents.<id>.sandbox.mode: "off"`.
sandbox, set `agents.list[].sandbox.mode: "off"`.
---
@@ -289,8 +297,8 @@ After configuring multi-agent sandbox and tools:
## Troubleshooting
### Agent not sandboxed despite `mode: "all"`
- Check if there's a global `agent.sandbox.mode` that overrides it
- Agent-specific config takes precedence, so set `routing.agents[id].sandbox.mode: "all"`
- Check if there's a global `agents.defaults.sandbox.mode` that overrides it
- Agent-specific config takes precedence, so set `agents.list[].sandbox.mode: "all"`
### Tools still available despite deny list
- Check tool filtering order: global → agent → sandbox → subagent
@@ -306,5 +314,5 @@ After configuring multi-agent sandbox and tools:
## See Also
- [Multi-Agent Routing](/concepts/multi-agent)
- [Sandbox Configuration](/gateway/configuration#agent-sandbox)
- [Sandbox Configuration](/gateway/configuration#agentsdefaults-sandbox)
- [Session Management](/concepts/session)

View File

@@ -6,7 +6,7 @@ read_when:
# Audio / Voice Notes — 2025-12-05
## What works
- **Optional transcription**: If `routing.transcribeAudio.command` is set in `~/.clawdbot/clawdbot.json`, CLAWDBOT will:
- **Optional transcription**: If `audio.transcription.command` is set in `~/.clawdbot/clawdbot.json`, CLAWDBOT will:
1) Download inbound audio to a temp path when WhatsApp only provides a URL.
2) Run the configured CLI (templated with `{{MediaPath}}`), expecting transcript on stdout.
3) Replace `Body` with the transcript, set `{{Transcript}}`, and prepend the original media path plus a `Transcript:` section in the command prompt so models see both.
@@ -17,8 +17,8 @@ read_when:
Requires `OPENAI_API_KEY` in env and `openai` CLI installed:
```json5
{
routing: {
transcribeAudio: {
audio: {
transcription: {
command: [
"openai",
"api",

View File

@@ -20,7 +20,7 @@ CLAWDBOT is now **web-only** (Baileys). This document captures the current media
## Web Provider Behavior
- Input: local file path **or** HTTP(S) URL.
- Flow: load into a Buffer, detect media kind, and build the correct payload:
- **Images:** resize & recompress to JPEG (max side 2048px) targeting `agent.mediaMaxMb` (default 5MB), capped at 6MB.
- **Images:** resize & recompress to JPEG (max side 2048px) targeting `agents.defaults.mediaMaxMb` (default 5MB), capped at 6MB.
- **Audio/Voice/Video:** pass-through up to 16MB; audio is sent as a voice note (`ptt: true`).
- **Documents:** anything else, up to 100MB, with filename preserved when available.
- WhatsApp GIF-style playback: send an MP4 with `gifPlayback: true` (CLI: `--gif-playback`) so mobile clients loop inline.

View File

@@ -136,8 +136,8 @@ Example “single server, only allow me, only allow #help”:
Notes:
- `requireMention: true` means the bot only replies when mentioned (recommended for shared channels).
- `routing.groupChat.mentionPatterns` also count as mentions for guild messages.
- Multi-agent override: `routing.agents.<agentId>.mentionPatterns` takes precedence.
- `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) also count as mentions for guild messages.
- Multi-agent override: set per-agent patterns on `agents.list[].groupChat.mentionPatterns`.
- If `channels` is present, any channel not listed is denied by default.
### 6) Verify it works

View File

@@ -66,8 +66,8 @@ DMs:
Groups:
- `imessage.groupPolicy = open | allowlist | disabled`.
- `imessage.groupAllowFrom` controls who can trigger in groups when `allowlist` is set.
- Mention gating uses `routing.groupChat.mentionPatterns` (iMessage has no native mention metadata).
- Multi-agent override: `routing.agents.<agentId>.mentionPatterns` takes precedence.
- Mention gating uses `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) because iMessage has no native mention metadata.
- Multi-agent override: set per-agent patterns on `agents.list[].groupChat.mentionPatterns`.
## How it works (behavior)
- `imsg` streams message events; the gateway normalizes them into the shared provider envelope.
@@ -112,5 +112,5 @@ Provider options:
- `imessage.textChunkLimit`: outbound chunk size (chars).
Related global options:
- `routing.groupChat.mentionPatterns`.
- `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`).
- `messages.responsePrefix`.

View File

@@ -92,6 +92,6 @@ Provider options:
- `signal.mediaMaxMb`: inbound/outbound media cap (MB).
Related global options:
- `routing.groupChat.mentionPatterns` (Signal does not support native mentions).
- Multi-agent override: `routing.agents.<agentId>.mentionPatterns` takes precedence.
- `agents.list[].groupChat.mentionPatterns` (Signal does not support native mentions).
- `messages.groupChat.mentionPatterns` (global fallback).
- `messages.responsePrefix`.

View File

@@ -248,8 +248,8 @@ Slack tool actions can be gated with `slack.actions.*`:
| emojiList | enabled | Custom emoji list |
## Notes
- Mention gating is controlled via `slack.channels` (set `requireMention` to `true`); `routing.groupChat.mentionPatterns` also count as mentions.
- Multi-agent override: `routing.agents.<agentId>.mentionPatterns` takes precedence.
- Mention gating is controlled via `slack.channels` (set `requireMention` to `true`); `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) also count as mentions.
- Multi-agent override: set per-agent patterns on `agents.list[].groupChat.mentionPatterns`.
- Reaction notifications follow `slack.reactionNotifications` (use `reactionAllowlist` with mode `allowlist`).
- Bot-authored messages are ignored by default; enable via `slack.allowBots` or `slack.channels.<id>.allowBots`.
- For the Slack tool, reaction removal semantics are in [/tools/reactions](/tools/reactions).

View File

@@ -64,10 +64,10 @@ group messages, so use admin if you need full visibility.
## How it works (behavior)
- Inbound messages are normalized into the shared provider envelope with reply context and media placeholders.
- Group replies require a mention by default (native @mention or `routing.groupChat.mentionPatterns`).
- Multi-agent override: `routing.agents.<agentId>.mentionPatterns` takes precedence.
- Group replies require a mention by default (native @mention or `agents.list[].groupChat.mentionPatterns` / `messages.groupChat.mentionPatterns`).
- Multi-agent override: set per-agent patterns on `agents.list[].groupChat.mentionPatterns`.
- Replies always route back to the same Telegram chat.
- Long-polling uses grammY runner with per-chat sequencing; overall concurrency is capped by `agent.maxConcurrent`.
- Long-polling uses grammY runner with per-chat sequencing; overall concurrency is capped by `agents.defaults.maxConcurrent`.
## Formatting (Telegram HTML)
- Outbound Telegram text uses `parse_mode: "HTML"` (Telegrams supported tag subset).
@@ -81,7 +81,7 @@ group messages, so use admin if you need full visibility.
## Group activation modes
By default, the bot only responds to mentions in groups (`@botname` or patterns in `routing.groupChat.mentionPatterns`). To change this behavior:
By default, the bot only responds to mentions in groups (`@botname` or patterns in `agents.list[].groupChat.mentionPatterns`). To change this behavior:
### Via config (recommended)
@@ -280,7 +280,7 @@ Provider options:
- `telegram.actions.sendMessage`: gate Telegram tool message sends.
Related global options:
- `routing.groupChat.mentionPatterns` (mention gating patterns).
- `routing.agents.<agentId>.mentionPatterns` overrides for multi-agent setups.
- `agents.list[].groupChat.mentionPatterns` (mention gating patterns).
- `messages.groupChat.mentionPatterns` (global fallback).
- `commands.native`, `commands.text`, `commands.useAccessGroups` (command behavior).
- `messages.responsePrefix`, `messages.ackReaction`, `messages.ackReactionScope`.

View File

@@ -148,7 +148,7 @@ Behavior:
## Limits
- Outbound text is chunked to `whatsapp.textChunkLimit` (default 4000).
- Media items are capped by `agent.mediaMaxMb` (default 5 MB).
- Media items are capped by `agents.defaults.mediaMaxMb` (default 5 MB).
## Outbound send (text + media)
- Uses active web listener; error if gateway not running.
@@ -164,13 +164,13 @@ Behavior:
## Media limits + optimization
- Default cap: 5 MB (per media item).
- Override: `agent.mediaMaxMb`.
- Override: `agents.defaults.mediaMaxMb`.
- Images are auto-optimized to JPEG under cap (resize + quality sweep).
- Oversize media => error; media reply falls back to text warning.
## Heartbeats
- **Gateway heartbeat** logs connection health (`web.heartbeatSeconds`, default 60s).
- **Agent heartbeat** is global (`agent.heartbeat.*`) and runs in the main session.
- **Agent heartbeat** is global (`agents.defaults.heartbeat.*`) and runs in the main session.
- Uses the configured heartbeat prompt (default: `Read HEARTBEAT.md if exists. Consider outstanding tasks. Checkup sometimes on your human during (user local) day time.`) + `HEARTBEAT_OK` skip behavior.
- Delivery defaults to the last used provider (or configured target).
@@ -189,16 +189,15 @@ Behavior:
- `whatsapp.groupPolicy` (group policy).
- `whatsapp.groups` (group allowlist + mention gating defaults; use `"*"` to allow all)
- `whatsapp.actions.reactions` (gate WhatsApp tool reactions).
- `routing.groupChat.mentionPatterns`
- Multi-agent override: `routing.agents.<agentId>.mentionPatterns` takes precedence.
- `routing.groupChat.historyLimit`
- `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`)
- `messages.groupChat.historyLimit`
- `messages.messagePrefix` (inbound prefix)
- `messages.responsePrefix` (outbound prefix)
- `agent.mediaMaxMb`
- `agent.heartbeat.every`
- `agent.heartbeat.model` (optional override)
- `agent.heartbeat.target`
- `agent.heartbeat.to`
- `agents.defaults.mediaMaxMb`
- `agents.defaults.heartbeat.every`
- `agents.defaults.heartbeat.model` (optional override)
- `agents.defaults.heartbeat.target`
- `agents.defaults.heartbeat.to`
- `session.*` (scope, idle, store, mainKey)
- `web.enabled` (disable provider startup when false)
- `web.heartbeatSeconds`

View File

@@ -8,7 +8,7 @@ read_when:
## First run (recommended)
Clawdbot uses a dedicated workspace directory for the agent. Default: `~/clawd` (configurable via `agent.workspace`).
Clawdbot uses a dedicated workspace directory for the agent. Default: `~/clawd` (configurable via `agents.defaults.workspace`).
1) Create the workspace (if it doesnt already exist):
@@ -30,13 +30,11 @@ cp docs/reference/templates/TOOLS.md ~/clawd/TOOLS.md
cp docs/reference/AGENTS.default.md ~/clawd/AGENTS.md
```
4) Optional: choose a different workspace by setting `agent.workspace` (supports `~`):
4) Optional: choose a different workspace by setting `agents.defaults.workspace` (supports `~`):
```json5
{
agent: {
workspace: "~/clawd"
}
agents: { defaults: { workspace: "~/clawd" } }
}
```

View File

@@ -18,7 +18,7 @@ Youre putting an agent in a position to:
Start conservative:
- Always set `whatsapp.allowFrom` (never run open-to-the-world on your personal Mac).
- Use a dedicated WhatsApp number for the assistant.
- Heartbeats now default to every 30 minutes. Disable until you trust the setup by setting `agent.heartbeat.every: "0m"`.
- Heartbeats now default to every 30 minutes. Disable until you trust the setup by setting `agents.defaults.heartbeat.every: "0m"`.
## Prerequisites
@@ -103,7 +103,7 @@ clawdbot setup
Full workspace layout + backup guide: [`docs/agent-workspace.md`](/concepts/agent-workspace)
Optional: choose a different workspace with `agent.workspace` (supports `~`).
Optional: choose a different workspace with `agents.defaults.workspace` (supports `~`).
```json5
{
@@ -173,9 +173,9 @@ Example:
By default, CLAWDBOT runs a heartbeat every 30 minutes with the prompt:
`Read HEARTBEAT.md if exists. Consider outstanding tasks. Checkup sometimes on your human during (user local) day time.`
Set `agent.heartbeat.every: "0m"` to disable.
Set `agents.defaults.heartbeat.every: "0m"` to disable.
- If the agent replies with `HEARTBEAT_OK` (optionally with short padding; see `agent.heartbeat.ackMaxChars`), CLAWDBOT suppresses outbound delivery for that heartbeat.
- If the agent replies with `HEARTBEAT_OK` (optionally with short padding; see `agents.defaults.heartbeat.ackMaxChars`), CLAWDBOT suppresses outbound delivery for that heartbeat.
- Heartbeats run full agent turns — shorter intervals burn more tokens.
```json5

View File

@@ -115,14 +115,14 @@ Everything lives under `$CLAWDBOT_STATE_DIR` (default: `~/.clawdbot`):
Legacy singleagent path: `~/.clawdbot/agent/*` (migrated by `clawdbot doctor`).
Your **workspace** (AGENTS.md, memory files, skills, etc.) is separate and configured via `agent.workspace` (default: `~/clawd`).
Your **workspace** (AGENTS.md, memory files, skills, etc.) is separate and configured via `agents.defaults.workspace` (default: `~/clawd`).
### Can agents work outside the workspace?
Yes. The workspace is the **default cwd** and memory anchor, not a hard sandbox.
Relative paths resolve inside the workspace, but absolute paths can access other
host locations unless sandboxing is enabled. If you need isolation, use
[`agent.sandbox`](/gateway/sandboxing) or peragent sandbox settings. If you
[`agents.defaults.sandbox`](/gateway/sandboxing) or peragent sandbox settings. If you
want a repo to be the default working directory, point that agents
`workspace` to the repo root. The Clawdbot repo is just source code; keep the
workspace separate unless you intentionally want the agent to work inside it.
@@ -259,7 +259,7 @@ Direct chats collapse to the main session by default. Groups/channels have their
Clawdbots default model is whatever you set as:
```
agent.model.primary
agents.defaults.model.primary
```
Models are referenced as `provider/model` (example: `anthropic/claude-opus-4-5`). If you omit the provider, Clawdbot currently assumes `anthropic` as a temporary deprecation fallback — but you should still **explicitly** set `provider/model`.
@@ -282,7 +282,7 @@ You can list available models with `/model`, `/model list`, or `/model status`.
### Why do I see “Model … is not allowed” and then no reply?
If `agent.models` is set, it becomes the **allowlist** for `/model` and any
If `agents.defaults.models` is set, it becomes the **allowlist** for `/model` and any
session overrides. Choosing a model that isnt in that list returns:
```
@@ -290,11 +290,11 @@ Model "provider/model" is not allowed. Use /model to list available models.
```
That error is returned **instead of** a normal reply. Fix: add the model to
`agent.models`, remove the allowlist, or pick a model from `/model list`.
`agents.defaults.models`, remove the allowlist, or pick a model from `/model list`.
### Are opus / sonnet / gpt builtin shortcuts?
Yes. Clawdbot ships a few default shorthands (only applied when the model exists in `agent.models`):
Yes. Clawdbot ships a few default shorthands (only applied when the model exists in `agents.defaults.models`):
- `opus` → `anthropic/claude-opus-4-5`
- `sonnet` → `anthropic/claude-sonnet-4-5`
@@ -307,7 +307,7 @@ If you set your own alias with the same name, your value wins.
### How do I define/override model shortcuts (aliases)?
Aliases come from `agent.models.<modelId>.alias`. Example:
Aliases come from `agents.defaults.models.<modelId>.alias`. Example:
```json5
{
@@ -359,7 +359,7 @@ If you reference a provider/model but the required provider key is missing, you
Failover happens in two stages:
1) **Auth profile rotation** within the same provider.
2) **Model fallback** to the next model in `agent.model.fallbacks`.
2) **Model fallback** to the next model in `agents.defaults.model.fallbacks`.
Cooldowns apply to failing profiles (exponential backoff), so Clawdbot can keep responding even when a provider is ratelimited or temporarily failing.
@@ -387,7 +387,7 @@ It means the system attempted to use the auth profile ID `anthropic:default`, bu
If your model config includes Google Gemini as a fallback (or you switched to a Gemini shorthand), Clawdbot will try it during model fallback. If you havent configured Google credentials, youll see `No API key found for provider "google"`.
Fix: either provide Google auth, or remove/avoid Google models in `agent.model.fallbacks` / aliases so fallback doesnt route there.
Fix: either provide Google auth, or remove/avoid Google models in `agents.defaults.model.fallbacks` / aliases so fallback doesnt route there.
## Auth profiles: what they are and how to manage them
@@ -506,7 +506,7 @@ Yes, but you must isolate:
- `CLAWDBOT_CONFIG_PATH` (perinstance config)
- `CLAWDBOT_STATE_DIR` (perinstance state)
- `agent.workspace` (workspace isolation)
- `agents.defaults.workspace` (workspace isolation)
- `gateway.port` (unique ports)
There are convenience CLI flags like `--dev` and `--profile <name>` that shift state dirs and ports.
@@ -619,7 +619,7 @@ You can add options like `debounce:2s cap:25 drop:summarize` for followup modes.
### “All models failed” — what should I check first?
- **Credentials** present for the provider(s) being tried (auth profiles + env vars).
- **Model routing**: confirm `agent.model.primary` and fallbacks are models you can access.
- **Model routing**: confirm `agents.defaults.model.primary` and fallbacks are models you can access.
- **Gateway logs** in `/tmp/clawdbot/…` for the exact provider error.
- **`/model status`** to see current configured models + shorthands.
@@ -658,7 +658,7 @@ clawdbot providers login
**Q: “Whats the default model for Anthropic with an API key?”**
**A:** In Clawdbot, credentials and model selection are separate. Setting `ANTHROPIC_API_KEY` (or storing an Anthropic API key in auth profiles) enables authentication, but the actual default model is whatever you configure in `agent.model.primary` (for example, `anthropic/claude-sonnet-4-5` or `anthropic/claude-opus-4-5`). If you see `No credentials found for profile "anthropic:default"`, it means the Gateway couldnt find Anthropic credentials in the expected `auth-profiles.json` for the agent thats running.
**A:** In Clawdbot, credentials and model selection are separate. Setting `ANTHROPIC_API_KEY` (or storing an Anthropic API key in auth profiles) enables authentication, but the actual default model is whatever you configure in `agents.defaults.model.primary` (for example, `anthropic/claude-sonnet-4-5` or `anthropic/claude-opus-4-5`). If you see `No credentials found for profile "anthropic:default"`, it means the Gateway couldnt find Anthropic credentials in the expected `auth-profiles.json` for the agent thats running.
---

View File

@@ -19,7 +19,7 @@ Recommended path: use the **CLI onboarding wizard** (`clawdbot onboard`). It set
If you want the deeper reference pages, jump to: [Wizard](/start/wizard), [Setup](/start/setup), [Pairing](/start/pairing), [Security](/gateway/security).
Sandboxing note: `agent.sandbox.mode: "non-main"` uses `session.mainKey` (default `"main"`),
Sandboxing note: `agents.defaults.sandbox.mode: "non-main"` uses `session.mainKey` (default `"main"`),
so group/channel sessions are sandboxed. If you want the main agent to always
run on host, set an explicit per-agent override:

View File

@@ -71,12 +71,12 @@ Tip: `--json` does **not** imply non-interactive mode. Use `--non-interactive` (
2) **Model/Auth**
- **Anthropic OAuth (Claude CLI)**: on macOS the wizard checks Keychain item "Claude Code-credentials" (choose "Always Allow" so launchd starts don't block); on Linux/Windows it reuses `~/.claude/.credentials.json` if present.
- **Anthropic token (paste setup-token)**: run `claude setup-token` in your terminal, then paste the token (you can name it; blank = default).
- **OpenAI Codex OAuth (Codex CLI)**: if `~/.codex/auth.json` exists, the wizard can reuse it.
- **OpenAI Codex OAuth**: browser flow; paste the `code#state`.
- Sets `agent.model` to `openai-codex/gpt-5.2` when model is unset or `openai/*`.
- **OpenAI API key**: uses `OPENAI_API_KEY` if present or prompts for a key, then saves it to `~/.clawdbot/.env` so launchd can read it.
- **API key**: stores the key for you.
- **Anthropic token (paste setup-token)**: run `claude setup-token` in your terminal, then paste the token (you can name it; blank = default).
- **OpenAI Codex OAuth (Codex CLI)**: if `~/.codex/auth.json` exists, the wizard can reuse it.
- **OpenAI Codex OAuth**: browser flow; paste the `code#state`.
- Sets `agents.defaults.model` to `openai-codex/gpt-5.2` when model is unset or `openai/*`.
- **OpenAI API key**: uses `OPENAI_API_KEY` if present or prompts for a key, then saves it to `~/.clawdbot/.env` so launchd can read it.
- **API key**: stores the key for you.
- **Minimax M2.1 (LM Studio)**: config is autowritten for the LM Studio endpoint.
- **Skip**: no auth configured yet.
- Wizard runs a model check and warns if the configured model is unknown or missing auth.
@@ -144,14 +144,14 @@ Use `clawdbot agents add <name>` to create a separate agent with its own workspa
sessions, and auth profiles. Running without `--workspace` launches the wizard.
What it sets:
- `routing.agents.<agentId>.name`
- `routing.agents.<agentId>.workspace`
- `routing.agents.<agentId>.agentDir`
- `agents.list[].name`
- `agents.list[].workspace`
- `agents.list[].agentDir`
Notes:
- Default workspaces follow `~/clawd-<agentId>`.
- Add `routing.bindings` to route inbound messages (the wizard can do this).
- Non-interactive flags: `--model`, `--agent-dir`, `--bind`, `--non-interactive`.
- Add `bindings` to route inbound messages (the wizard can do this).
- Non-interactive flags: `--model`, `--agent-dir`, `--bind`, `--non-interactive`.
## Noninteractive mode
@@ -213,8 +213,8 @@ Notes:
## What the wizard writes
Typical fields in `~/.clawdbot/clawdbot.json`:
- `agent.workspace`
- `agent.model` / `models.providers` (if Minimax chosen)
- `agents.defaults.workspace`
- `agents.defaults.model` / `models.providers` (if Minimax chosen)
- `gateway.*` (mode, bind, auth, tailscale)
- `telegram.botToken`, `discord.token`, `signal.*`, `imessage.*`
- `skills.install.nodeManager`
@@ -224,7 +224,7 @@ Typical fields in `~/.clawdbot/clawdbot.json`:
- `wizard.lastRunCommand`
- `wizard.lastRunMode`
`clawdbot agents add` writes `routing.agents.<agentId>` and optional `routing.bindings`.
`clawdbot agents add` writes `agents.list[]` and optional `bindings`.
WhatsApp credentials go under `~/.clawdbot/credentials/whatsapp/<accountId>/`.
Sessions are stored under `~/.clawdbot/agents/<agentId>/sessions/`.

View File

@@ -12,7 +12,7 @@ read_when:
- Only `on|off` are accepted; anything else returns a hint and does not change state.
## What it controls (and what it doesnt)
- **Global availability gate**: `agent.elevated` is global (not per-agent). If disabled or sender not allowlisted, elevated is unavailable everywhere.
- **Global availability gate**: `tools.elevated` is global (not per-agent). If disabled or sender not allowlisted, elevated is unavailable everywhere.
- **Per-session state**: `/elevated on|off` sets the elevated level for the current session key.
- **Inline directive**: `/elevated on` inside a message applies to that message only.
- **Groups**: In group chats, elevated directives are only honored when the agent is mentioned. Command-only messages that bypass mention requirements are treated as mentioned.
@@ -31,7 +31,7 @@ Note:
## Resolution order
1. Inline directive on the message (applies only to that message).
2. Session override (set by sending a directive-only message).
3. Global default (`agent.elevatedDefault` in config).
3. Global default (`agents.defaults.elevatedDefault` in config).
## Setting a session default
- Send a message that is **only** the directive (whitespace allowed), e.g. `/elevated on`.
@@ -40,10 +40,10 @@ Note:
- Send `/elevated` (or `/elevated:`) with no argument to see the current elevated level.
## Availability + allowlists
- Feature gate: `agent.elevated.enabled` (default can be off via config even if the code supports it).
- Sender allowlist: `agent.elevated.allowFrom` with per-provider allowlists (e.g. `discord`, `whatsapp`).
- Feature gate: `tools.elevated.enabled` (default can be off via config even if the code supports it).
- Sender allowlist: `tools.elevated.allowFrom` with per-provider allowlists (e.g. `discord`, `whatsapp`).
- Both must pass; otherwise elevated is treated as unavailable.
- Discord fallback: if `agent.elevated.allowFrom.discord` is omitted, the `discord.dm.allowFrom` list is used as a fallback. Set `agent.elevated.allowFrom.discord` (even `[]`) to override.
- Discord fallback: if `tools.elevated.allowFrom.discord` is omitted, the `discord.dm.allowFrom` list is used as a fallback. Set `tools.elevated.allowFrom.discord` (even `[]`) to override.
## Logging + status
- Elevated bash calls are logged at info level.

View File

@@ -13,16 +13,12 @@ and the agent should rely on them directly.
## Disabling tools
You can globally allow/deny tools via `agent.tools` in `clawdbot.json`
You can globally allow/deny tools via `tools.allow` / `tools.deny` in `clawdbot.json`
(deny wins). This prevents disallowed tools from being sent to providers.
```json5
{
agent: {
tools: {
deny: ["browser"]
}
}
tools: { deny: ["browser"] }
}
```
@@ -43,7 +39,7 @@ Notes:
- Returns `status: "running"` with a `sessionId` when backgrounded.
- Use `process` to poll/log/write/kill/clear background sessions.
- If `process` is disallowed, `bash` runs synchronously and ignores `yieldMs`/`background`.
- `elevated` is gated by `agent.elevated` (global sender allowlist) and runs on the host.
- `elevated` is gated by `tools.elevated` (global sender allowlist) and runs on the host.
- `elevated` only changes behavior when the agent is sandboxed (otherwise its a no-op).
### `process`
@@ -145,7 +141,7 @@ Core parameters:
- `maxBytesMb` (optional size cap)
Notes:
- Only available when `agent.imageModel` is configured (primary or fallbacks).
- Only available when `agents.defaults.imageModel` is configured (primary or fallbacks).
- Uses the image model directly (independent of the main chat model).
### `message`
@@ -219,7 +215,7 @@ Notes:
List agent ids that the current session may target with `sessions_spawn`.
Notes:
- Result is restricted to per-agent allowlists (`routing.agents.<agentId>.subagents.allowAgents`).
- Result is restricted to per-agent allowlists (`agents.list[].subagents.allowAgents`).
- When `["*"]` is configured, the tool includes all configured agents and marks `allowAny: true`.
## Parameters (common)

View File

@@ -46,7 +46,7 @@ Text + native (when enabled):
- `/verbose on|off` (alias: `/v`)
- `/reasoning on|off|stream` (alias: `/reason`; `stream` = Telegram draft only)
- `/elevated on|off` (alias: `/elev`)
- `/model <name>` (or `/<alias>` from `agent.models.*.alias`)
- `/model <name>` (or `/<alias>` from `agents.defaults.models.*.alias`)
- `/queue <mode>` (plus options like `debounce:2s cap:25 drop:summarize`; send `/queue` to see current settings)
Text-only:

View File

@@ -30,13 +30,13 @@ Tool params:
- `cleanup?` (`delete|keep`, default `keep`)
Allowlist:
- `routing.agents.<agentId>.subagents.allowAgents`: list of agent ids that can be targeted via `agentId` (`["*"]` to allow any). Default: only the requester agent.
- `agents.list[].subagents.allowAgents`: list of agent ids that can be targeted via `agentId` (`["*"]` to allow any). Default: only the requester agent.
Discovery:
- Use `agents_list` to see which agent ids are currently allowed for `sessions_spawn`.
Auto-archive:
- Sub-agent sessions are automatically archived after `agent.subagents.archiveAfterMinutes` (default: 60).
- Sub-agent sessions are automatically archived after `agents.defaults.subagents.archiveAfterMinutes` (default: 60).
- Archive uses `sessions.delete` and renames the transcript to `*.deleted.<timestamp>` (same folder).
- `cleanup: "delete"` archives immediately after announce (still keeps the transcript via rename).
- Auto-archive is best-effort; pending timers are lost if the gateway restarts.
@@ -67,9 +67,15 @@ Override via config:
```json5
{
agent: {
agents: {
defaults: {
subagents: {
maxConcurrent: 1
}
}
},
tools: {
subagents: {
maxConcurrent: 1,
tools: {
// deny wins
deny: ["gateway", "cron"],
@@ -85,7 +91,7 @@ Override via config:
Sub-agents use a dedicated in-process queue lane:
- Lane name: `subagent`
- Concurrency: `agent.subagents.maxConcurrent` (default `1`)
- Concurrency: `agents.defaults.subagents.maxConcurrent` (default `1`)
## Limitations

View File

@@ -17,7 +17,7 @@ read_when:
## Resolution order
1. Inline directive on the message (applies only to that message).
2. Session override (set by sending a directive-only message).
3. Global default (`agent.thinkingDefault` in config).
3. Global default (`agents.defaults.thinkingDefault` in config).
4. Fallback: low for reasoning-capable models; off otherwise.
## Setting a session default