feat: wire multi-agent config and routing
Co-authored-by: Mark Pors <1078320+pors@users.noreply.github.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 per‑agent sandbox config).
|
||||
[`agents.defaults.sandbox`](/gateway/sandboxing) (and/or per‑agent 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`.
|
||||
|
||||
@@ -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 agent’s **only** working directory (`cwd`) for tools and context.
|
||||
CLAWDBOT uses a single agent workspace directory (`agents.defaults.workspace`) as the agent’s **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
|
||||
800–1200 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)
|
||||
|
||||
---
|
||||
|
||||
@@ -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).
|
||||
|
||||
## What’s implemented (2025-12-03)
|
||||
- Activation modes: `mention` (default) or `always`. `mention` requires a ping (real WhatsApp @-mentions via `mentionedJids`, regex patterns, or the bot’s 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 bot’s 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 hasn’t 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).
|
||||
|
||||
@@ -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).
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 can’t 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 can’t 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 isn’t 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 “didn’t 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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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 user’s 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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user