42 KiB
summary, read_when
| summary | read_when | |
|---|---|---|
| All configuration options for ~/.clawdbot/clawdbot.json with examples |
|
Configuration 🔧
CLAWDBOT reads an optional JSON5 config from ~/.clawdbot/clawdbot.json (comments + trailing commas allowed).
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) - 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)
Schema + UI hints
The Gateway exposes a JSON Schema representation of the config via config.schema for UI editors.
The Control UI renders a form from this schema, with a Raw JSON editor as an escape hatch.
Hints (labels, grouping, sensitive fields) ship alongside the schema so clients can render better forms without hard-coding config knowledge.
Minimal config (recommended starting point)
{
agent: { workspace: "~/clawd" },
whatsapp: { allowFrom: ["+15555550123"] }
}
Build the default image once with:
scripts/sandbox-setup.sh
Self-chat mode (recommended for group control)
To prevent the bot from responding to WhatsApp @-mentions in groups (only respond to specific text triggers):
{
agent: { workspace: "~/clawd" },
whatsapp: {
// Allowlist is DMs only; including your own number enables self-chat mode.
allowFrom: ["+15555550123"],
groups: { "*": { requireMention: true } }
},
routing: {
groupChat: {
mentionPatterns: ["@clawd", "reisponde"]
}
}
}
Common options
Env vars + .env
CLAWDBOT reads env vars from the parent process (shell, launchd/systemd, CI, etc.).
Additionally, it loads:
.envfrom the current working directory (if present)- a global fallback
.envfrom~/.clawdbot/.env(aka$CLAWDBOT_STATE_DIR/.env)
Neither .env file overrides existing env vars.
env.shellEnv (optional)
Opt-in convenience: if enabled and none of the expected keys are set yet, CLAWDBOT runs your login shell and imports only the missing expected keys (never overrides). This effectively sources your shell profile.
{
env: {
shellEnv: {
enabled: true,
timeoutMs: 15000
}
}
}
Env var equivalent:
CLAWDBOT_LOAD_SHELL_ENV=1CLAWDBOT_SHELL_ENV_TIMEOUT_MS=15000
Auth storage (OAuth + API keys)
Clawdbot stores auth profiles (OAuth + API keys) in:
~/.clawdbot/agent/auth-profiles.json
Legacy OAuth imports:
~/.clawdbot/credentials/oauth.json(or$CLAWDBOT_STATE_DIR/credentials/oauth.json)
The embedded Pi agent maintains a runtime cache at:
~/.clawdbot/agent/auth.json(managed automatically; don’t edit manually)
Overrides:
- OAuth dir (legacy import only):
CLAWDBOT_OAUTH_DIR - Agent dir:
CLAWDBOT_AGENT_DIR(preferred),PI_CODING_AGENT_DIR(legacy)
On first use, Clawdbot imports oauth.json entries into auth-profiles.json.
auth
Optional metadata for auth profiles. This does not store secrets; it maps profile IDs to a provider + mode (and optional email) and defines the provider rotation order used for failover.
{
auth: {
profiles: {
"anthropic:default": { provider: "anthropic", mode: "oauth", email: "me@example.com" },
"anthropic:work": { provider: "anthropic", mode: "api_key" }
},
order: {
anthropic: ["anthropic:default", "anthropic:work"]
}
}
}
identity
Optional agent identity used for defaults and UX. This is written by the macOS onboarding assistant.
If set, CLAWDBOT derives defaults (only when you haven’t set them explicitly):
messages.ackReactionfromidentity.emoji(falls back to 👀)routing.groupChat.mentionPatternsfromidentity.name(so “@Samantha” works in groups across Telegram/Slack/Discord/iMessage/WhatsApp)
{
identity: { name: "Samantha", theme: "helpful sloth", emoji: "🦥" }
}
wizard
Metadata written by CLI wizards (onboard, configure, doctor, update).
{
wizard: {
lastRunAt: "2026-01-01T00:00:00.000Z",
lastRunVersion: "2026.1.4",
lastRunCommit: "abc1234",
lastRunCommand: "configure",
lastRunMode: "local"
}
}
logging
- Default log file:
/tmp/clawdbot/clawdbot-YYYY-MM-DD.log - If you want a stable path, set
logging.fileto/tmp/clawdbot/clawdbot.log. - Console output can be tuned separately via:
logging.consoleLevel(defaults toinfo, bumps todebugwhen--verbose)logging.consoleStyle(pretty|compact|json)
- Tool summaries can be redacted to avoid leaking secrets:
logging.redactSensitive(off|tools, default:tools)logging.redactPatterns(array of regex strings; overrides defaults)
{
logging: {
level: "info",
file: "/tmp/clawdbot/clawdbot.log",
consoleLevel: "info",
consoleStyle: "pretty",
redactSensitive: "tools",
redactPatterns: [
// Example: override defaults with your own rules.
"\\bTOKEN\\b\\s*[=:]\\s*([\"']?)([^\\s\"']+)\\1",
"/\\bsk-[A-Za-z0-9_-]{8,}\\b/gi"
]
}
}
whatsapp.dmPolicy
Controls how WhatsApp direct chats (DMs) are handled:
"pairing"(default): unknown senders get a pairing code; owner must approve"allowlist": only allow senders inwhatsapp.allowFrom(or paired allow store)"open": allow all inbound DMs (requireswhatsapp.allowFromto include"*")"disabled": ignore all inbound DMs
Pairing approvals:
clawdbot pairing list --provider whatsappclawdbot pairing approve --provider whatsapp <code>
whatsapp.allowFrom
Allowlist of E.164 phone numbers that may trigger WhatsApp auto-replies (DMs only).
If empty and whatsapp.dmPolicy="pairing", unknown senders will receive a pairing code.
For groups, use whatsapp.groupPolicy + whatsapp.groupAllowFrom.
{
whatsapp: {
dmPolicy: "pairing", // pairing | allowlist | open | disabled
allowFrom: ["+15555550123", "+447700900123"],
textChunkLimit: 4000 // optional outbound chunk size (chars)
}
}
routing.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. - Mention gating is enforced only when mention detection is possible (native mentions or at least one
mentionPattern).
{
routing: {
groupChat: {
mentionPatterns: ["@clawd", "clawdbot", "clawd"],
historyLimit: 50
}
}
}
Mention gating defaults live per provider (whatsapp.groups, telegram.groups, imessage.groups, discord.guilds). When *.groups is set, it also acts as a group allowlist; include "*" to allow all groups.
To respond only to specific text triggers (ignoring native @-mentions):
{
whatsapp: {
// Include your own number to enable self-chat mode (ignore native @-mentions).
allowFrom: ["+15555550123"],
groups: { "*": { requireMention: true } }
},
routing: {
groupChat: {
// Only these text patterns will trigger responses
mentionPatterns: ["reisponde", "@clawd"]
}
}
}
Group policy (per provider)
Use *.groupPolicy to control whether group/room messages are accepted at all:
{
whatsapp: {
groupPolicy: "allowlist",
groupAllowFrom: ["+15551234567"]
},
telegram: {
groupPolicy: "allowlist",
groupAllowFrom: ["tg:123456789", "@alice"]
},
signal: {
groupPolicy: "allowlist",
groupAllowFrom: ["+15551234567"]
},
imessage: {
groupPolicy: "allowlist",
groupAllowFrom: ["chat_id:123"]
},
discord: {
groupPolicy: "allowlist",
guilds: {
"GUILD_ID": {
channels: { help: { allow: true } }
}
}
},
slack: {
groupPolicy: "allowlist",
channels: { "#general": { allow: true } }
}
}
Notes:
"open"(default): groups bypass allowlists; mention-gating still applies."disabled": block all group/room messages."allowlist": only allow groups/rooms that match the configured allowlist.- WhatsApp/Telegram/Signal/iMessage use
groupAllowFrom(fallback: explicitallowFrom). - Discord/Slack use channel allowlists (
discord.guilds.*.channels,slack.channels). - Group DMs (Discord/Slack) are still controlled by
dm.groupEnabled+dm.groupChannels.
routing.queue
Controls how inbound messages behave when an agent run is already active.
{
routing: {
queue: {
mode: "collect", // steer | followup | collect | steer-backlog (steer+backlog ok) | interrupt (queue=steer legacy)
debounceMs: 1000,
cap: 20,
drop: "summarize", // old | new | summarize
bySurface: {
whatsapp: "collect",
telegram: "collect",
discord: "collect",
imessage: "collect",
webchat: "collect"
}
}
}
}
web (WhatsApp web provider)
WhatsApp runs through the gateway’s web provider. It starts automatically when a linked session exists.
Set web.enabled: false to keep it off by default.
{
web: {
enabled: true,
heartbeatSeconds: 60,
reconnect: {
initialMs: 2000,
maxMs: 120000,
factor: 1.4,
jitter: 0.2,
maxAttempts: 0
}
}
}
telegram (bot transport)
Clawdbot starts Telegram only when a telegram config section exists. The bot token is resolved from TELEGRAM_BOT_TOKEN or telegram.botToken.
Set telegram.enabled: false to disable automatic startup.
{
telegram: {
enabled: true,
botToken: "your-bot-token",
dmPolicy: "pairing", // pairing | allowlist | open | disabled
allowFrom: ["tg:123456789"], // optional; "open" requires ["*"]
groups: { "*": { requireMention: true } },
mediaMaxMb: 5,
proxy: "socks5://localhost:9050",
webhookUrl: "https://example.com/telegram-webhook",
webhookSecret: "secret",
webhookPath: "/telegram-webhook"
}
}
discord (bot transport)
Configure the Discord bot by setting the bot token and optional gating:
{
discord: {
enabled: true,
token: "your-bot-token",
mediaMaxMb: 8, // clamp inbound media size
actions: { // tool action gates (false disables)
reactions: true,
stickers: true,
polls: true,
permissions: true,
messages: true,
threads: true,
pins: true,
search: true,
memberInfo: true,
roleInfo: true,
roles: false,
channelInfo: true,
voiceStatus: true,
events: true,
moderation: false
},
replyToMode: "off", // off | first | all
slashCommand: { // user-installed app slash commands
enabled: true,
name: "clawd",
sessionPrefix: "discord:slash",
ephemeral: true
},
dm: {
enabled: true, // disable all DMs when false
policy: "pairing", // pairing | allowlist | open | disabled
allowFrom: ["1234567890", "steipete"], // optional DM allowlist ("open" requires ["*"])
groupEnabled: false, // enable group DMs
groupChannels: ["clawd-dm"] // optional group DM allowlist
},
guilds: {
"123456789012345678": { // guild id (preferred) or slug
slug: "friends-of-clawd",
requireMention: false, // per-guild default
reactionNotifications: "own", // off | own | all | allowlist
users: ["987654321098765432"], // optional per-guild user allowlist
channels: {
general: { allow: true },
help: { allow: true, requireMention: true }
}
}
},
historyLimit: 20 // include last N guild messages as context
}
}
Clawdbot starts Discord only when a discord config section exists. The token is resolved from DISCORD_BOT_TOKEN or discord.token (unless discord.enabled is false). Use user:<id> (DM) or channel:<id> (guild channel) when specifying delivery targets for cron/CLI commands.
Guild slugs are lowercase with spaces replaced by -; channel keys use the slugged channel name (no leading #). Prefer guild ids as keys to avoid rename ambiguity.
Reaction notification modes:
off: no reaction events.own: reactions on the bot's own messages (default).all: all reactions on all messages.allowlist: reactions fromguilds.<id>.userson all messages (empty list disables).
slack (socket mode)
Slack runs in Socket Mode and requires both a bot token and app token:
{
slack: {
enabled: true,
botToken: "xoxb-...",
appToken: "xapp-...",
dm: {
enabled: true,
policy: "pairing", // pairing | allowlist | open | disabled
allowFrom: ["U123", "U456", "*"], // optional; "open" requires ["*"]
groupEnabled: false,
groupChannels: ["G123"]
},
channels: {
C123: { allow: true, requireMention: true },
"#general": { allow: true, requireMention: false }
},
reactionNotifications: "own", // off | own | all | allowlist
reactionAllowlist: ["U123"],
actions: {
reactions: true,
messages: true,
pins: true,
memberInfo: true,
emojiList: true
},
slashCommand: {
enabled: true,
name: "clawd",
sessionPrefix: "slack:slash",
ephemeral: true
},
textChunkLimit: 4000,
mediaMaxMb: 20
}
}
Clawdbot starts Slack when the provider is enabled and both tokens are set (via config or SLACK_BOT_TOKEN + SLACK_APP_TOKEN). Use user:<id> (DM) or channel:<id> when specifying delivery targets for cron/CLI commands.
Reaction notification modes:
off: no reaction events.own: reactions on the bot's own messages (default).all: all reactions on all messages.allowlist: reactions fromslack.reactionAllowliston all messages (empty list disables).
Slack action groups (gate slack tool actions):
| Action group | Default | Notes |
|---|---|---|
| reactions | enabled | React + list reactions |
| messages | enabled | Read/send/edit/delete |
| pins | enabled | Pin/unpin/list |
| memberInfo | enabled | Member info |
| emojiList | enabled | Custom emoji list |
imessage (imsg CLI)
Clawdbot spawns imsg rpc (JSON-RPC over stdio). No daemon or port required.
{
imessage: {
enabled: true,
cliPath: "imsg",
dbPath: "~/Library/Messages/chat.db",
dmPolicy: "pairing", // pairing | allowlist | open | disabled
allowFrom: ["+15555550123", "user@example.com", "chat_id:123"],
includeAttachments: false,
mediaMaxMb: 16,
service: "auto",
region: "US"
}
}
Notes:
- Requires Full Disk Access to the Messages DB.
- The first send will prompt for Messages automation permission.
- Prefer
chat_id:<id>targets. Useimsg chats --limit 20to list chats.
agent.workspace
Sets the single global workspace directory used by the agent for file operations.
Default: ~/clawd.
{
agent: { workspace: "~/clawd" }
}
If agent.sandbox is enabled, non-main sessions can override this with their
own per-session workspaces under agent.sandbox.workspaceRoot.
agent.userTimezone
Sets the user’s timezone for system prompt context (not for timestamps in message envelopes). If unset, Clawdbot uses the host timezone at runtime.
{
agent: { userTimezone: "America/Chicago" }
}
messages
Controls inbound/outbound prefixes and optional ack reactions.
{
messages: {
messagePrefix: "[clawdbot]",
responsePrefix: "🦞",
ackReaction: "👀",
ackReactionScope: "group-mentions"
}
}
responsePrefix is applied to all outbound replies (tool summaries, block
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.
ackReactionScope controls when reactions fire:
group-mentions(default): only when a group/room requires mentions and the bot was mentionedgroup-all: all group/room messagesdirect: direct messages onlyall: all messages
talk
Defaults for Talk mode (macOS/iOS/Android). Voice IDs fall back to ELEVENLABS_VOICE_ID or SAG_VOICE_ID when unset.
apiKey falls back to ELEVENLABS_API_KEY (or the gateway’s shell profile) when unset.
voiceAliases lets Talk directives use friendly names (e.g. "voice":"Clawd").
{
talk: {
voiceId: "elevenlabs_voice_id",
voiceAliases: {
Clawd: "EXAVITQu4vr4xnSDxMaL",
Roger: "CwhRBWXzGAHq8TQ4Fs17"
},
modelId: "eleven_v3",
outputFormat: "mp3_44100_128",
apiKey: "elevenlabs_api_key",
interruptOnSpeech: true
}
}
agent
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.
Clawdbot also ships a few built-in alias shorthands. Defaults only apply when the model
is already present in agent.models:
opus->anthropic/claude-opus-4-5sonnet->anthropic/claude-sonnet-4-5gpt->openai/gpt-5.2gpt-mini->openai/gpt-5-minigemini->google/gemini-3-pro-previewgemini-flash->google/gemini-3-flash-preview
If you configure the same alias name (case-insensitive) yourself, your value wins (defaults never override).
{
agent: {
models: {
"anthropic/claude-opus-4-5": { alias: "Opus" },
"anthropic/claude-sonnet-4-1": { alias: "Sonnet" },
"openrouter/deepseek/deepseek-r1:free": {}
},
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,
bash: {
backgroundMs: 10000,
timeoutSec: 1800,
cleanupMs: 1800000
},
contextTokens: 200000
}
}
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 800–1200 chars, prefers paragraph breaks (\n\n), then newlines, then sentences. Example:{ agent: { blockStreamingChunk: { minChars: 800, maxChars: 1200 } } }
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).
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:
every: duration string (ms,s,m,h); default unit minutes. Omit or set0mto disable.model: optional override model for heartbeat runs (provider/model).target: optional delivery channel (last,whatsapp,telegram,discord,imessage,none). Default:last.to: optional recipient override (E.164 for WhatsApp, chat id for Telegram).prompt: optional override for the heartbeat body (default:HEARTBEAT).ackMaxChars: max chars allowed afterHEARTBEAT_OKbefore delivery (default: 30).
agent.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.tools configures a global tool allow/deny policy (deny wins).
This is applied even when the Docker sandbox is off.
Example (disable browser/canvas everywhere):
{
agent: {
tools: {
deny: ["browser", "canvas"]
}
}
}
agent.elevated controls elevated (host) bash access:
enabled: allow elevated mode (default true)allowFrom: per-surface allowlists (empty = disabled)whatsapp: E.164 numberstelegram: chat ids or usernamesdiscord: user ids or usernames (falls back todiscord.dm.allowFromif omitted)signal: E.164 numbersimessage: handles/chat idswebchat: session ids or usernames
Example:
{
agent: {
elevated: {
enabled: true,
allowFrom: {
whatsapp: ["+15555550123"],
discord: ["steipete", "1234567890123"]
}
}
}
}
agent.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
Optional per-session Docker sandboxing for the embedded agent. Intended for non-main sessions so they cannot access your host system.
Defaults (if enabled):
- one container per session
- Debian bookworm-slim based image
- workspace per session under
~/.clawdbot/sandboxes - auto-prune: idle > 24h OR age > 7d
- tools: allow only
bash,process,read,write,edit(deny wins) - optional sandboxed browser (Chromium + CDP, noVNC observer)
- hardening knobs:
network,user,pidsLimit,memory,cpus,ulimits,seccompProfile,apparmorProfile
{
agent: {
sandbox: {
mode: "non-main", // off | non-main | all
perSession: true,
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",
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"]
},
browser: {
enabled: false,
image: "clawdbot-sandbox-browser:bookworm-slim",
containerPrefix: "clawdbot-sbx-browser-",
cdpPort: 9222,
vncPort: 5900,
noVncPort: 6080,
headless: false,
enableNoVnc: true
},
tools: {
allow: ["bash", "process", "read", "write", "edit"],
deny: ["browser", "canvas", "nodes", "cron", "discord", "gateway"]
},
prune: {
idleHours: 24, // 0 disables idle pruning
maxAgeDays: 7 // 0 disables max-age pruning
}
}
}
}
Build the default sandbox image once with:
scripts/sandbox-setup.sh
Note: sandbox containers default to network: "none"; set agent.sandbox.docker.network
to "bridge" (or your custom network) if the agent needs outbound access.
Build the optional browser image with:
scripts/sandbox-browser-setup.sh
When agent.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
URL is injected per session.
models (custom providers + base URLs)
Clawdbot uses the pi-coding-agent model catalog. You can add custom providers
(LiteLLM, local OpenAI-compatible servers, Anthropic proxies, etc.) by writing
~/.clawdbot/agent/models.json or by defining the same schema inside your
Clawdbot config under models.providers.
When models.providers is present, Clawdbot writes/merges a models.json into
~/.clawdbot/agent/ on startup:
- 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).
{
agent: {
model: { primary: "custom-proxy/llama-3.1-8b" },
models: {
"custom-proxy/llama-3.1-8b": {}
}
},
models: {
mode: "merge",
providers: {
"custom-proxy": {
baseUrl: "http://localhost:4000/v1",
apiKey: "LITELLM_KEY",
api: "openai-completions",
models: [
{
id: "llama-3.1-8b",
name: "Llama 3.1 8B",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 128000,
maxTokens: 32000
}
]
}
}
}
}
Local models (LM Studio) — recommended setup
Best current local setup (what we’re running): MiniMax M2.1 on a beefy Mac Studio via LM Studio using the Responses API.
{
agent: {
model: { primary: "lmstudio/minimax-m2.1-gs32" },
models: {
"anthropic/claude-opus-4-5": { alias: "Opus" },
"lmstudio/minimax-m2.1-gs32": { alias: "Minimax" }
}
},
models: {
mode: "merge",
providers: {
lmstudio: {
baseUrl: "http://127.0.0.1:1234/v1",
apiKey: "lmstudio",
api: "openai-responses",
models: [
{
id: "minimax-m2.1-gs32",
name: "MiniMax M2.1 GS32",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 196608,
maxTokens: 8192
}
]
}
}
}
}
Notes:
- LM Studio must have the model loaded and the local server enabled (default URL above).
- Responses API enables clean reasoning/output separation; WhatsApp sees only final text.
- Adjust
contextWindow/maxTokensif your LM Studio context length differs.
Notes:
- Supported APIs:
openai-completions,openai-responses,anthropic-messages,google-generative-ai - Use
authHeader: true+headersfor custom auth needs. - Override the agent config root with
CLAWDBOT_AGENT_DIR(orPI_CODING_AGENT_DIR) if you wantmodels.jsonstored elsewhere.
session
Controls session scoping, idle expiry, reset triggers, and where the session store is written.
{
session: {
scope: "per-sender",
idleMinutes: 60,
resetTriggers: ["/new", "/reset"],
store: "~/.clawdbot/sessions/sessions.json",
// mainKey is ignored; primary key is fixed to "main"
agentToAgent: {
// Max ping-pong reply turns between requester/target (0–5).
maxPingPongTurns: 5
},
sendPolicy: {
rules: [
{ action: "deny", match: { surface: "discord", chatType: "group" } }
],
default: "allow"
}
}
}
Fields:
agentToAgent.maxPingPongTurns: max reply-back turns between requester/target (0–5, default 5).sendPolicy.default:allowordenyfallback when no rule matches.sendPolicy.rules[]: match bysurface(provider),chatType(direct|group|room), orkeyPrefix(e.g.cron:). First deny wins; otherwise allow.
skills (skills config)
Controls bundled allowlist, install preferences, extra skill folders, and per-skill
overrides. Applies to bundled skills and ~/.clawdbot/skills (workspace skills
still win on name conflicts).
Fields:
allowBundled: optional allowlist for bundled skills only. If set, only those bundled skills are eligible (managed/workspace skills unaffected).load.extraDirs: additional skill directories to scan (lowest precedence).install.preferBrew: prefer brew installers when available (default: true).install.nodeManager: node installer preference (npm|pnpm|yarn, default: npm).entries.<skillKey>: per-skill config overrides.
Per-skill fields:
enabled: setfalseto disable a skill even if it’s bundled/installed.env: environment variables injected for the agent run (only if not already set).apiKey: optional convenience for skills that declare a primary env var (e.g.nano-banana-pro→GEMINI_API_KEY).
Example:
{
skills: {
allowBundled: ["brave-search", "gemini"],
load: {
extraDirs: [
"~/Projects/agent-scripts/skills",
"~/Projects/oss/some-skill-pack/skills"
]
},
install: {
preferBrew: true,
nodeManager: "npm"
},
entries: {
"nano-banana-pro": {
apiKey: "GEMINI_KEY_HERE",
env: {
GEMINI_API_KEY: "GEMINI_KEY_HERE"
}
},
peekaboo: { enabled: true },
sag: { enabled: false }
}
}
}
browser (clawd-managed Chrome)
Clawdbot can start a dedicated, isolated Chrome/Chromium instance for clawd and expose a small loopback control server.
Profiles can point at a remote Chrome via profiles.<name>.cdpUrl. Remote
profiles are attach-only (start/stop/reset are disabled).
browser.cdpUrl remains for legacy single-profile configs and as the base
scheme/host for profiles that only set cdpPort.
Defaults:
- enabled:
true - control URL:
http://127.0.0.1:18791(CDP uses18792) - CDP URL:
http://127.0.0.1:18792(control URL + 1, legacy single-profile) - profile color:
#FF4500(lobster-orange) - Note: the control server is started by the running gateway (Clawdbot.app menubar, or
clawdbot gateway).
{
browser: {
enabled: true,
controlUrl: "http://127.0.0.1:18791",
// cdpUrl: "http://127.0.0.1:18792", // legacy single-profile override
defaultProfile: "clawd",
profiles: {
clawd: { cdpPort: 18800, color: "#FF4500" },
work: { cdpPort: 18801, color: "#0066CC" },
remote: { cdpUrl: "http://10.0.0.42:9222", color: "#00AA00" }
},
color: "#FF4500",
// Advanced:
// headless: false,
// noSandbox: false,
// executablePath: "/usr/bin/chromium",
// attachOnly: false, // set true when tunneling a remote CDP to localhost
}
}
ui (Appearance)
Optional accent color used by the native apps for UI chrome (e.g. Talk Mode bubble tint).
If unset, clients fall back to a muted light-blue.
{
ui: {
seamColor: "#FF4500" // hex (RRGGBB or #RRGGBB)
}
}
gateway (Gateway server mode + bind)
Use gateway.mode to explicitly declare whether this machine should run the Gateway.
Defaults:
- mode: unset (treated as “do not auto-start”)
- bind:
loopback - port:
18789(single port for WS + HTTP)
{
gateway: {
mode: "local", // or "remote"
port: 18789, // WS + HTTP multiplex
bind: "loopback",
// controlUi: { enabled: true, basePath: "/clawdbot" }
// auth: { mode: "token", token: "your-token" } // token is for multi-machine CLI access
// tailscale: { mode: "off" | "serve" | "funnel" }
}
}
Control UI base path:
gateway.controlUi.basePathsets the URL prefix where the Control UI is served.- Examples:
"/ui","/clawdbot","/apps/clawdbot". - Default: root (
/) (unchanged).
Notes:
clawdbot gatewayrefuses to start unlessgateway.modeis set tolocal(or you pass the override flag).gateway.portcontrols the single multiplexed port used for WebSocket + HTTP (control UI, hooks, A2UI).- Precedence:
--port>CLAWDBOT_GATEWAY_PORT>gateway.port> default18789.
Auth and Tailscale:
gateway.auth.modesets the handshake requirements (tokenorpassword).gateway.auth.tokenstores the shared token for token auth (used by the CLI on the same machine).- When
gateway.auth.modeis set, only that method is accepted (plus optional Tailscale headers). gateway.auth.passwordcan be set here, or viaCLAWDBOT_GATEWAY_PASSWORD(recommended).gateway.auth.allowTailscalecontrols whether Tailscale identity headers can satisfy auth.gateway.tailscale.mode: "serve"uses Tailscale Serve (tailnet only, loopback bind).gateway.tailscale.mode: "funnel"exposes the dashboard publicly; requires auth.gateway.tailscale.resetOnExitresets Serve/Funnel config on shutdown.
Remote client defaults (CLI):
gateway.remote.urlsets the default Gateway WebSocket URL for CLI calls whengateway.mode = "remote".gateway.remote.tokensupplies the token for remote calls (leave unset for no auth).gateway.remote.passwordsupplies the password for remote calls (leave unset for no auth).
macOS app behavior:
- Clawdbot.app watches
~/.clawdbot/clawdbot.jsonand switches modes live whengateway.modeorgateway.remote.urlchanges. - If
gateway.modeis unset butgateway.remote.urlis set, the macOS app treats it as remote mode. - When you change connection mode in the macOS app, it writes
gateway.mode(andgateway.remote.urlin remote mode) back to the config file.
{
gateway: {
mode: "remote",
remote: {
url: "ws://gateway.tailnet:18789",
token: "your-token",
password: "your-password"
}
}
}
gateway.reload (Config hot reload)
The Gateway watches ~/.clawdbot/clawdbot.json (or CLAWDBOT_CONFIG_PATH) and applies changes automatically.
Modes:
hybrid(default): hot-apply safe changes; restart the Gateway for critical changes.hot: only apply hot-safe changes; log when a restart is required.restart: restart the Gateway on any config change.off: disable hot reload.
{
gateway: {
reload: {
mode: "hybrid",
debounceMs: 300
}
}
}
Hot reload matrix (files + impact)
Files watched:
~/.clawdbot/clawdbot.json(orCLAWDBOT_CONFIG_PATH)
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)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)
Requires full Gateway restart:
gateway(port/bind/auth/control UI/tailscale)bridgediscoverycanvasHost- Any unknown/unsupported config path (defaults to restart for safety)
Multi-instance isolation
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)gateway.port(unique per instance)
Convenience flags (CLI):
clawdbot --dev …→ uses~/.clawdbot-dev+ shifts ports from base19001clawdbot --profile <name> …→ uses~/.clawdbot-<name>(port via config/env/flags)
See docs/gateway.md for the derived port mapping (gateway/bridge/browser/canvas).
Example:
CLAWDBOT_CONFIG_PATH=~/.clawdbot/a.json \
CLAWDBOT_STATE_DIR=~/.clawdbot-a \
clawdbot gateway --port 19001
hooks (Gateway webhooks)
Enable a simple HTTP webhook surface on the Gateway HTTP server.
Defaults:
- enabled:
false - path:
/hooks - maxBodyBytes:
262144(256 KB)
{
hooks: {
enabled: true,
token: "shared-secret",
path: "/hooks",
presets: ["gmail"],
transformsDir: "~/.clawdbot/hooks",
mappings: [
{
match: { path: "gmail" },
action: "agent",
wakeMode: "now",
name: "Gmail",
sessionKey: "hook:gmail:{{messages[0].id}}",
messageTemplate:
"From: {{messages[0].from}}\nSubject: {{messages[0].subject}}\n{{messages[0].snippet}}",
},
],
}
}
Requests must include the hook token:
Authorization: Bearer <token>orx-clawdbot-token: <token>or?token=<token>
Endpoints:
POST /hooks/wake→{ text, mode?: "now"|"next-heartbeat" }POST /hooks/agent→{ message, name?, sessionKey?, wakeMode?, deliver?, channel?, to?, thinking?, timeoutSeconds? }POST /hooks/<name>→ resolved viahooks.mappings
/hooks/agent always posts a summary into the main session (and can optionally trigger an immediate heartbeat via wakeMode: "now").
Mapping notes:
match.pathmatches the sub-path after/hooks(e.g./hooks/gmail→gmail).match.sourcematches a payload field (e.g.{ source: "gmail" }) so you can use a generic/hooks/ingestpath.- Templates like
{{messages[0].subject}}read from the payload. transformcan point to a JS/TS module that returns a hook action.
Gmail helper config (used by clawdbot hooks gmail setup / run):
{
hooks: {
gmail: {
account: "clawdbot@gmail.com",
topic: "projects/<project-id>/topics/gog-gmail-watch",
subscription: "gog-gmail-watch-push",
pushToken: "shared-push-token",
hookUrl: "http://127.0.0.1:18789/hooks/gmail",
includeBody: true,
maxBytes: 20000,
renewEveryMinutes: 720,
serve: { bind: "127.0.0.1", port: 8788, path: "/" },
tailscale: { mode: "funnel", path: "/gmail-pubsub" },
}
}
}
Gateway auto-start:
- If
hooks.enabled=trueandhooks.gmail.accountis set, the Gateway startsgog gmail watch serveon boot and auto-renews the watch. - Set
CLAWDBOT_SKIP_GMAIL_WATCHER=1to disable the auto-start (for manual runs). - Avoid running a separate
gog gmail watch servealongside the Gateway; it will fail withlisten tcp 127.0.0.1:8788: bind: address already in use.
Note: when tailscale.mode is on, Clawdbot defaults serve.path to / so
Tailscale can proxy /gmail-pubsub correctly (it strips the set-path prefix).
canvasHost (LAN/tailnet Canvas file server + live reload)
The Gateway serves a directory of HTML/CSS/JS over HTTP so iOS/Android nodes can simply canvas.navigate to it.
Default root: ~/clawd/canvas
Default port: 18793 (chosen to avoid the clawd browser CDP port 18792)
The server listens on the bridge bind host (LAN or Tailnet) so nodes can reach it.
The server:
- serves files under
canvasHost.root - injects a tiny live-reload client into served HTML
- watches the directory and broadcasts reloads over a WebSocket endpoint at
/__clawdbot/ws - auto-creates a starter
index.htmlwhen the directory is empty (so you see something immediately) - also serves A2UI at
/__clawdbot__/a2ui/and is advertised to nodes ascanvasHostUrl(always used by nodes for Canvas/A2UI)
Disable live reload (and file watching) if the directory is large or you hit EMFILE:
- config:
canvasHost: { liveReload: false }
{
canvasHost: {
root: "~/clawd/canvas",
port: 18793,
liveReload: true
}
}
Changes to canvasHost.* require a gateway restart (config reload will restart).
Disable with:
- config:
canvasHost: { enabled: false } - env:
CLAWDBOT_SKIP_CANVAS_HOST=1
bridge (node bridge server)
The Gateway can expose a simple TCP bridge for nodes (iOS/Android), typically on port 18790.
Defaults:
- enabled:
true - port:
18790 - bind:
lan(binds to0.0.0.0)
Bind modes:
lan:0.0.0.0(reachable on any interface, including LAN/Wi‑Fi and Tailscale)tailnet: bind only to the machine’s Tailscale IP (recommended for Vienna ⇄ London)loopback:127.0.0.1(local only)auto: prefer tailnet IP if present, elselan
{
bridge: {
enabled: true,
port: 18790,
bind: "tailnet"
}
}
discovery.wideArea (Wide-Area Bonjour / unicast DNS‑SD)
When enabled, the Gateway writes a unicast DNS-SD zone for _clawdbot-bridge._tcp under ~/.clawdbot/dns/ using the standard discovery domain clawdbot.internal.
To make iOS/Android discover across networks (Vienna ⇄ London), pair this with:
- a DNS server on the gateway host serving
clawdbot.internal.(CoreDNS is recommended) - Tailscale split DNS so clients resolve
clawdbot.internalvia that server
One-time setup helper (gateway host):
clawdbot dns setup --apply
{
discovery: { wideArea: { enabled: true } }
}
Template variables
Template placeholders are expanded in routing.transcribeAudio.command (and any future templated command fields).
| Variable | Description |
|---|---|
{{Body}} |
Full inbound message body |
{{BodyStripped}} |
Body with group mentions stripped (best default for agents) |
{{From}} |
Sender identifier (E.164 for WhatsApp; may differ per surface) |
{{To}} |
Destination identifier |
{{MessageSid}} |
Provider message id (when available) |
{{SessionId}} |
Current session UUID |
{{IsNewSession}} |
"true" when a new session was created |
{{MediaUrl}} |
Inbound media pseudo-URL (if present) |
{{MediaPath}} |
Local media path (if downloaded) |
{{MediaType}} |
Media type (image/audio/document/…) |
{{Transcript}} |
Audio transcript (when enabled) |
{{ChatType}} |
"direct" or "group" |
{{GroupSubject}} |
Group subject (best effort) |
{{GroupMembers}} |
Group members preview (best effort) |
{{SenderName}} |
Sender display name (best effort) |
{{SenderE164}} |
Sender phone number (best effort) |
{{Surface}} |
Surface hint (whatsapp |
Cron (Gateway scheduler)
Cron is a Gateway-owned scheduler for wakeups and scheduled jobs. See Cron + wakeups for the full RFC and CLI examples.
{
cron: {
enabled: true,
maxConcurrentRuns: 2
}
}
Next: Agent Runtime 🦞