Configuration 🔧
Clawdia reads an optional JSON5 config from~/.nelsonmuntz-c/clawdia.json (comments + trailing commas allowed).
If the file is missing, Clawdia 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 (
channels.whatsapp.allowFrom,channels.telegram.allowFrom, etc.) - control group allowlists + mention behavior (
channels.whatsapp.groups,channels.telegram.groups,channels.discord.guilds,agents.list[].groupChat) - customize message prefixes (
messages) - set the agent’s workspace (
agents.defaults.workspaceoragents.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 guide for complete examples with detailed explanations!
Strict config validation
Clawdia only accepts configurations that fully match the schema. Unknown keys, malformed types, or invalid values cause the Gateway to refuse to start for safety. When validation fails:- The Gateway does not boot.
- Only diagnostic commands are allowed (for example:
clawdia doctor,clawdia logs,clawdia health,clawdia status,clawdia service,clawdia help). - Run
clawdia doctorto see the exact issues. - Run
clawdia doctor --fix(or--yes) to apply migrations/repairs.
--fix/--yes.
Schema + UI hints
The Gateway exposes a JSON Schema representation of the config viaconfig.schema for UI editors.
The Control UI renders a form from this schema, with a Raw JSON editor as an escape hatch.
Channel plugins and extensions can register schema + UI hints for their config, so channel settings
stay schema-driven across apps without hard-coded forms.
Hints (labels, grouping, sensitive fields) ship alongside the schema so clients can render
better forms without hard-coding config knowledge.
Apply + restart (RPC)
Useconfig.apply to validate + write the full config and restart the Gateway in one step.
It writes a restart sentinel and pings the last active session after the Gateway comes back.
Warning: config.apply replaces the entire config. If you want to change only a few keys,
use config.patch or clawdia config set. Keep a backup of ~/.nelsonmuntz-c/clawdia.json.
Params:
raw(string) — JSON5 payload for the entire configbaseHash(optional) — config hash fromconfig.get(required when a config already exists)sessionKey(optional) — last active session key for the wake-up pingnote(optional) — note to include in the restart sentinelrestartDelayMs(optional) — delay before restart (default 2000)
gateway call):
Partial updates (RPC)
Useconfig.patch to merge a partial update into the existing config without clobbering
unrelated keys. It applies JSON merge patch semantics:
- objects merge recursively
nulldeletes a key- arrays replace
Like
config.apply, it validates, writes the config, stores a restart sentinel, and schedules the Gateway restart (with an optional wake whensessionKeyis provided).
raw(string) — JSON5 payload containing just the keys to changebaseHash(required) — config hash fromconfig.getsessionKey(optional) — last active session key for the wake-up pingnote(optional) — note to include in the restart sentinelrestartDelayMs(optional) — delay before restart (default 2000)
Minimal config (recommended starting point)
Self-chat mode (recommended for group control)
To prevent the bot from responding to WhatsApp @-mentions in groups (only respond to specific text triggers):Config Includes ($include)
Split your config into multiple files using the $include directive. This is useful for:
- Organizing large configs (e.g., per-client agent definitions)
- Sharing common settings across environments
- Keeping sensitive configs separate
Basic usage
Merge behavior
- Single file: Replaces the object containing
$include - Array of files: Deep-merges files in order (later files override earlier ones)
- With sibling keys: Sibling keys are merged after includes (override included values)
- Sibling keys + arrays/primitives: Not supported (included content must be an object)
Nested includes
Included files can themselves contain$include directives (up to 10 levels deep):
Path resolution
- Relative paths: Resolved relative to the including file
- Absolute paths: Used as-is
- Parent directories:
../references work as expected
Error handling
- Missing file: Clear error with resolved path
- Parse error: Shows which included file failed
- Circular includes: Detected and reported with include chain
Example: Multi-client legal setup
Common options
Env vars + .env
Clawdia 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~/.clawdia/.env(aka$CLAWDIA_STATE_DIR/.env)
.env file overrides existing env vars.
You can also provide inline env vars in config. These are only applied if the
process env is missing the key (same non-overriding rule):
env.shellEnv (optional)
Opt-in convenience: if enabled and none of the expected keys are set yet, Clawdia runs your login shell and imports only the missing expected keys (never overrides).
This effectively sources your shell profile.
CLAWDIA_LOAD_SHELL_ENV=1CLAWDIA_SHELL_ENV_TIMEOUT_MS=15000
Env var substitution in config
You can reference environment variables directly in any config string value using${VAR_NAME} syntax. Variables are substituted at config load time, before validation.
- Only uppercase env var names are matched:
[A-Z_][A-Z0-9_]* - Missing or empty env vars throw an error at config load
- Escape with
$${VAR}to output a literal${VAR} - Works with
$include(included files also get substitution)
Auth storage (OAuth + API keys)
Clawdia stores per-agent auth profiles (OAuth + API keys) in:<agentDir>/auth-profiles.json(default:~/.clawdia/agents/<agentId>/agent/auth-profiles.json)
~/.clawdia/credentials/oauth.json(or$CLAWDIA_STATE_DIR/credentials/oauth.json)
<agentDir>/auth.json(managed automatically; don’t edit manually)
~/.clawdia/agent/*(migrated byclawdia doctorinto~/.clawdia/agents/<defaultAgentId>/agent/*)
- OAuth dir (legacy import only):
CLAWDIA_OAUTH_DIR - Agent dir (default agent root override):
CLAWDIA_AGENT_DIR(preferred),PI_CODING_AGENT_DIR(legacy)
oauth.json entries into auth-profiles.json.
Clawdia also auto-syncs OAuth tokens from external CLIs into auth-profiles.json (when present on the gateway host):
- Claude Code →
anthropic:claude-cli- macOS: Keychain item “Claude Code-credentials” (choose “Always Allow” to avoid launchd prompts)
- Linux/Windows:
~/.claude/.credentials.json
~/.codex/auth.json(Codex CLI) →openai-codex:codex-cli
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.
anthropic:claude-cli should use mode: "oauth" even when the stored
credential is a setup-token. Clawdia auto-migrates older configs that used
mode: "token".
agents.list[].identity
Optional per-agent identity used for defaults and UX. This is written by the macOS onboarding assistant.
If set, Clawdia derives defaults (only when you haven’t set them explicitly):
messages.ackReactionfrom the active agent’sidentity.emoji(falls back to 👀)agents.list[].groupChat.mentionPatternsfrom the agent’sidentity.name/identity.emoji(so “@Samantha” works in groups across Telegram/Slack/Discord/Google Chat/iMessage/WhatsApp)identity.avataraccepts a workspace-relative image path or a remote URL/data URL. Local files must live inside the agent workspace.
identity.avatar accepts:
- Workspace-relative path (must stay within the agent workspace)
http(s)URLdata:URI
wizard
Metadata written by CLI wizards (onboard, configure, doctor).
logging
- Default log file:
/tmp/nelsonmuntz-c/clawdia-YYYY-MM-DD.log - If you want a stable path, set
logging.fileto/tmp/nelsonmuntz-c/clawdia.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)
channels.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 inchannels.whatsapp.allowFrom(or paired allow store)"open": allow all inbound DMs (requireschannels.whatsapp.allowFromto include"*")"disabled": ignore all inbound DMs
clawdia pairing list whatsappclawdia pairing approve whatsapp <code>
channels.whatsapp.allowFrom
Allowlist of E.164 phone numbers that may trigger WhatsApp auto-replies (DMs only).
If empty and channels.whatsapp.dmPolicy="pairing", unknown senders will receive a pairing code.
For groups, use channels.whatsapp.groupPolicy + channels.whatsapp.groupAllowFrom.
channels.whatsapp.sendReadReceipts
Controls whether inbound WhatsApp messages are marked as read (blue ticks). Default: true.
Self-chat mode always skips read receipts, even when enabled.
Per-account override: channels.whatsapp.accounts.<id>.sendReadReceipts.
channels.whatsapp.accounts (multi-account)
Run multiple WhatsApp accounts in one gateway:
- Outbound commands default to account
defaultif present; otherwise the first configured account id (sorted). - The legacy single-account Baileys auth dir is migrated by
clawdia doctorintowhatsapp/default.
channels.telegram.accounts / channels.discord.accounts / channels.googlechat.accounts / channels.slack.accounts / channels.mattermost.accounts / channels.signal.accounts / channels.imessage.accounts
Run multiple accounts per channel (each account has its own accountId and optional name):
defaultis used whenaccountIdis omitted (CLI + routing).- Env tokens only apply to the default account.
- Base channel settings (group policy, mention gating, etc.) apply to all accounts unless overridden per account.
- Use
bindings[].match.accountIdto route each account to a different agents.defaults.
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, Google Chat, and iMessage group chats.
Mention types:
- Metadata mentions: Native platform @-mentions (e.g., WhatsApp tap-to-mention). Ignored in WhatsApp self-chat mode (see
channels.whatsapp.allowFrom). - 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).
messages.groupChat.historyLimit sets the global default for group history context. Channels can override with channels.<channel>.historyLimit (or channels.<channel>.accounts.*.historyLimit for multi-account). Set 0 to disable history wrapping.
DM history limits
DM conversations use session-based history managed by the agent. You can limit the number of user turns retained per DM session:- Per-DM override:
channels.<provider>.dms[userId].historyLimit - Provider default:
channels.<provider>.dmHistoryLimit - No limit (all history retained)
telegram, whatsapp, discord, slack, signal, imessage, msteams.
Per-agent override (takes precedence when set, even []):
channels.whatsapp.groups, channels.telegram.groups, channels.imessage.groups, channels.discord.guilds). When *.groups is set, it also acts as a group allowlist; include "*" to allow all groups.
To respond only to specific text triggers (ignoring native @-mentions):
Group policy (per channel)
Usechannels.*.groupPolicy to control whether group/room messages are accepted at all:
"open": groups bypass allowlists; mention-gating still applies."disabled": block all group/room messages."allowlist": only allow groups/rooms that match the configured allowlist.channels.defaults.groupPolicysets the default when a provider’sgroupPolicyis unset.- WhatsApp/Telegram/Signal/iMessage/Microsoft Teams use
groupAllowFrom(fallback: explicitallowFrom). - Discord/Slack use channel allowlists (
channels.discord.guilds.*.channels,channels.slack.channels). - Group DMs (Discord/Slack) are still controlled by
dm.groupEnabled+dm.groupChannels. - Default is
groupPolicy: "allowlist"(unless overridden bychannels.defaults.groupPolicy); if no allowlist is configured, group messages are blocked.
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.
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>(formain, falls back toagents.defaults.workspace).agentDir: default~/.clawdia/agents/<agentId>/agent.model: per-agent default model, overridesagents.defaults.modelfor that agent.- string form:
"provider/model", overrides onlyagents.defaults.model.primary - object form:
{ primary, fallbacks }(fallbacks overrideagents.defaults.model.fallbacks;[]disables global fallbacks for that agent)
- string form:
identity: per-agent name/theme/emoji (used for mention patterns + ack reactions).groupChat: per-agent mention-gating (mentionPatterns).sandbox: per-agent sandbox config (overridesagents.defaults.sandbox).mode:"off"|"non-main"|"all"workspaceAccess:"none"|"ro"|"rw"scope:"session"|"agent"|"shared"workspaceRoot: custom sandbox workspace rootdocker: per-agent docker overrides (e.g.image,network,env,setupCommand, limits; ignored whenscope: "shared")browser: per-agent sandboxed browser overrides (ignored whenscope: "shared")prune: per-agent sandbox pruning overrides (ignored whenscope: "shared")
subagents: per-agent sub-agent defaults.allowAgents: allowlist of agent ids forsessions_spawnfrom this agent (["*"]= allow any; default: only same agent)
tools: per-agent tool restrictions (applied before sandbox tool policy).profile: base tool profile (applied before allow/deny)allow: array of allowed tool namesdeny: array of denied tool names (deny wins)
agents.defaults: shared agent defaults (model, workspace, sandbox, etc.).bindings[]: routes inbound messages to anagentId.match.channel(required)match.accountId(optional;*= any account; omitted = default account)match.peer(optional;{ kind: dm|group|channel, id })match.guildId/match.teamId(optional; channel-specific)
match.peermatch.guildIdmatch.teamIdmatch.accountId(exact, no peer/guild/team)match.accountId: "*"(channel-wide, no peer/guild/team)- default agent (
agents.list[].default, else first list entry, else"main")
bindings wins.
Per-agent access profiles (multi-agent)
Each agent can carry its own sandbox + tool policy. Use this to mix access levels in one gateway:- Full access (personal agent)
- Read-only tools + workspace
- No filesystem access (messaging/session tools only)
tools.agentToAgent (optional)
Agent-to-agent messaging is opt-in:
messages.queue
Controls how inbound messages behave when an agent run is already active.
messages.inbound
Debounce rapid inbound messages from the same sender so multiple back-to-back
messages become a single agent turn. Debouncing is scoped per channel + conversation
and uses the most recent message for reply threading/IDs.
- Debounce batches text-only messages; media/attachments flush immediately.
- Control commands (e.g.
/queue,/new) bypass debouncing so they stay standalone.
commands (chat command handling)
Controls how chat commands are enabled across connectors.
- Text commands must be sent as a standalone message and use the leading
/(no plain-text aliases). commands.text: falsedisables parsing chat messages for commands.commands.native: "auto"(default) turns on native commands for Discord/Telegram and leaves Slack off; unsupported channels stay text-only.- Set
commands.native: true|falseto force all, or override per channel withchannels.discord.commands.native,channels.telegram.commands.native,channels.slack.commands.native(bool or"auto").falseclears previously registered commands on Discord/Telegram at startup; Slack commands are managed in the Slack app. channels.telegram.customCommandsadds extra Telegram bot menu entries. Names are normalized; conflicts with native commands are ignored.commands.bash: trueenables! <cmd>to run host shell commands (/bash <cmd>also works as an alias). Requirestools.elevated.enabledand allowlisting the sender intools.elevated.allowFrom.<channel>.commands.bashForegroundMscontrols how long bash waits before backgrounding. While a bash job is running, new! <cmd>requests are rejected (one at a time).commands.config: trueenables/config(reads/writesclawdia.json).channels.<provider>.configWritesgates config mutations initiated by that channel (default: true). This applies to/config set|unsetplus provider-specific auto-migrations (Telegram supergroup ID changes, Slack channel ID changes).commands.debug: trueenables/debug(runtime-only overrides).commands.restart: trueenables/restartand the gateway tool restart action.commands.useAccessGroups: falseallows commands to bypass access-group allowlists/policies.
web (WhatsApp web channel runtime)
WhatsApp runs through the gateway’s web channel (Baileys Web). It starts automatically when a linked session exists.
Set web.enabled: false to keep it off by default.
channels.telegram (bot transport)
Clawdia starts Telegram only when a channels.telegram config section exists. The bot token is resolved from channels.telegram.botToken (or channels.telegram.tokenFile), with TELEGRAM_BOT_TOKEN as a fallback for the default account.
Set channels.telegram.enabled: false to disable automatic startup.
Multi-account support lives under channels.telegram.accounts (see the multi-account section above). Env tokens only apply to the default account.
Set channels.telegram.configWrites: false to block Telegram-initiated config writes (including supergroup ID migrations and /config set|unset).
- Uses Telegram
sendMessageDraft(draft bubble, not a real message). - Requires private chat topics (message_thread_id in DMs; bot has topics enabled).
/reasoning streamstreams reasoning into the draft, then sends the final answer. Retry policy defaults and behavior are documented in Retry policy.
channels.discord (bot transport)
Configure the Discord bot by setting the bot token and optional gating:
Multi-account support lives under channels.discord.accounts (see the multi-account section above). Env tokens only apply to the default account.
channels.discord config section exists. The token is resolved from channels.discord.token, with DISCORD_BOT_TOKEN as a fallback for the default account (unless channels.discord.enabled is false). Use user:<id> (DM) or channel:<id> (guild channel) when specifying delivery targets for cron/CLI commands; bare numeric IDs are ambiguous and rejected.
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.
Bot-authored messages are ignored by default. Enable with channels.discord.allowBots (own messages are still filtered to prevent self-reply loops).
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). Outbound text is chunked bychannels.discord.textChunkLimit(default 2000). Setchannels.discord.chunkMode="newline"to split on line boundaries before length chunking. Discord clients can clip very tall messages, sochannels.discord.maxLinesPerMessage(default 17) splits long multi-line replies even when under 2000 chars. Retry policy defaults and behavior are documented in Retry policy.
channels.googlechat (Chat API webhook)
Google Chat runs over HTTP webhooks with app-level auth (service account).
Multi-account support lives under channels.googlechat.accounts (see the multi-account section above). Env vars only apply to the default account.
- Service account JSON can be inline (
serviceAccount) or file-based (serviceAccountFile). - Env fallbacks for the default account:
GOOGLE_CHAT_SERVICE_ACCOUNTorGOOGLE_CHAT_SERVICE_ACCOUNT_FILE. audienceType+audiencemust match the Chat app’s webhook auth config.- Use
spaces/<spaceId>orusers/<userId|email>when setting delivery targets.
channels.slack (socket mode)
Slack runs in Socket Mode and requires both a bot token and app token:
channels.slack.accounts (see the multi-account section above). Env tokens only apply to the default account.
Clawdia 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.
Set channels.slack.configWrites: false to block Slack-initiated config writes (including channel ID migrations and /config set|unset).
Bot-authored messages are ignored by default. Enable with channels.slack.allowBots or channels.slack.channels.<id>.allowBots.
Reaction notification modes:
off: no reaction events.own: reactions on the bot’s own messages (default).all: all reactions on all messages.allowlist: reactions fromchannels.slack.reactionAllowliston all messages (empty list disables).
channels.slack.thread.historyScopecontrols whether thread history is per-thread (thread, default) or shared across the channel (channel).channels.slack.thread.inheritParentcontrols whether new thread sessions inherit the parent channel transcript (default: false).
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 |
channels.mattermost (bot token)
Mattermost ships as a plugin and is not bundled with the core install.
Install it first: clawdia plugins install @clawdia/mattermost (or ./extensions/mattermost from a git checkout).
Mattermost requires a bot token plus the base URL for your server:
channels.mattermost.botToken + channels.mattermost.baseUrl or MATTERMOST_BOT_TOKEN + MATTERMOST_URL for the default account (unless channels.mattermost.enabled is false).
Chat modes:
oncall(default): respond to channel messages only when @mentioned.onmessage: respond to every channel message.onchar: respond when a message starts with a trigger prefix (channels.mattermost.oncharPrefixes, default[">", "!"]).
- Default DMs:
channels.mattermost.dmPolicy="pairing"(unknown senders get a pairing code). - Public DMs:
channels.mattermost.dmPolicy="open"pluschannels.mattermost.allowFrom=["*"]. - Groups:
channels.mattermost.groupPolicy="allowlist"by default (mention-gated). Usechannels.mattermost.groupAllowFromto restrict senders.
channels.mattermost.accounts (see the multi-account section above). Env vars only apply to the default account.
Use channel:<id> or user:<id> (or @username) when specifying delivery targets; bare ids are treated as channel ids.
channels.signal (signal-cli)
Signal reactions can emit system events (shared reaction tooling):
off: no reaction events.own: reactions on the bot’s own messages (default).all: all reactions on all messages.allowlist: reactions fromchannels.signal.reactionAllowliston all messages (empty list disables).
channels.imessage (imsg CLI)
Clawdia spawns imsg rpc (JSON-RPC over stdio). No daemon or port required.
channels.imessage.accounts (see the multi-account section above).
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. channels.imessage.cliPathcan point to a wrapper script (e.g.sshto another Mac that runsimsg rpc); use SSH keys to avoid password prompts.- For remote SSH wrappers, set
channels.imessage.remoteHostto fetch attachments via SCP whenincludeAttachmentsis enabled.
agents.defaults.workspace
Sets the single global workspace directory used by the agent for file operations.
Default: ~/clawd.
agents.defaults.sandbox is enabled, non-main sessions can override this with their
own per-scope workspaces under agents.defaults.sandbox.workspaceRoot.
agents.defaults.repoRoot
Optional repository root to show in the system prompt’s Runtime line. If unset, Clawdia
tries to detect a .git directory by walking upward from the workspace (and current
working directory). The path must exist to be used.
agents.defaults.skipBootstrap
Disables automatic creation of the workspace bootstrap files (AGENTS.md, SOUL.md, TOOLS.md, IDENTITY.md, USER.md, and BOOTSTRAP.md).
Use this for pre-seeded deployments where your workspace files come from a repo.
agents.defaults.bootstrapMaxChars
Max characters of each workspace bootstrap file injected into the system prompt
before truncation. Default: 20000.
When a file exceeds this limit, Clawdia logs a warning and injects a truncated
head/tail with a marker.
agents.defaults.userTimezone
Sets the user’s timezone for system prompt context (not for timestamps in
message envelopes). If unset, Clawdia uses the host timezone at runtime.
agents.defaults.timeFormat
Controls the time format shown in the system prompt’s Current Date & Time section.
Default: auto (OS preference).
messages
Controls inbound/outbound prefixes and optional ack reactions.
See Messages for queueing, sessions, and streaming context.
responsePrefix is applied to all outbound replies (tool summaries, block
streaming, final replies) across channels unless already present.
If messages.responsePrefix is unset, no prefix is applied by default. WhatsApp self-chat
replies are the exception: they default to [{identity.name}] when set, otherwise
[clawdia], so same-phone conversations stay legible.
Set it to "auto" to derive [{identity.name}] for the routed agent (when set).
Template variables
TheresponsePrefix string can include template variables that resolve dynamically:
| Variable | Description | Example |
|---|---|---|
{model} | Short model name | claude-opus-4-5, gpt-4o |
{modelFull} | Full model identifier | anthropic/claude-opus-4-5 |
{provider} | Provider name | anthropic, openai |
{thinkingLevel} | Current thinking level | high, low, off |
{identity.name} | Agent identity name | (same as "auto" mode) |
{MODEL} = {model}). {think} is an alias for {thinkingLevel}.
Unresolved variables remain as literal text.
[claude-opus-4-5 | think:high] Here's my response...
WhatsApp inbound prefix is configured via channels.whatsapp.messagePrefix (deprecated:
messages.messagePrefix). Default stays unchanged: "[clawdia]" when
channels.whatsapp.allowFrom is empty, otherwise "" (no prefix). When using
"[clawdia]", Clawdia will instead use [{identity.name}] when the routed
agent has identity.name set.
ackReaction sends a best-effort emoji reaction to acknowledge inbound messages
on channels that support reactions (Slack/Discord/Telegram/Google Chat). Defaults to the
active agent’s identity.emoji when set, otherwise "👀". Set it to "" to disable.
ackReactionScope controls when reactions fire:
group-mentions(default): only when a group/room requires mentions and the bot was mentionedgroup-all: all group/room messagesdirect: direct messages onlyall: all messages
removeAckAfterReply removes the bot’s ack reaction after a reply is sent
(Slack/Discord/Telegram/Google Chat only). Default: false.
messages.tts
Enable text-to-speech for outbound replies. When on, Clawdia generates audio
using ElevenLabs or OpenAI and attaches it to responses. Telegram uses Opus
voice notes; other channels send MP3 audio.
messages.tts.autocontrols auto‑TTS (off,always,inbound,tagged)./tts off|always|inbound|taggedsets the per‑session auto mode (overrides config).messages.tts.enabledis legacy; doctor migrates it tomessages.tts.auto.prefsPathstores local overrides (provider/limit/summarize).maxTextLengthis a hard cap for TTS input; summaries are truncated to fit.summaryModeloverridesagents.defaults.model.primaryfor auto-summary.- Accepts
provider/modelor an alias fromagents.defaults.models.
- Accepts
modelOverridesenables model-driven overrides like[[tts:...]]tags (on by default)./tts limitand/tts summarycontrol per-user summarization settings.apiKeyvalues fall back toELEVENLABS_API_KEY/XI_API_KEYandOPENAI_API_KEY.elevenlabs.baseUrloverrides the ElevenLabs API base URL.elevenlabs.voiceSettingssupportsstability/similarityBoost/style(0..1),useSpeakerBoost, andspeed(0.5..2.0).
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").
agents.defaults
Controls the embedded agent runtime (model/thinking/verbose/timeouts).
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).
params is also applied to streaming runs (embedded agent + compaction). Supported keys today: temperature, maxTokens. These merge with call-time options; caller-supplied values win. temperature is an advanced knob—leave unset unless you know the model’s defaults and need a change.
Example:
- set
--thinking off, or - define
agents.defaults.models["zai/<model>"].params.thinkingyourself.
agents.defaults.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
MINIMAX_API_KEY (env) or configure models.providers.minimax.
agents.defaults.cliBackends (CLI fallback)
Optional CLI backends for text-only fallback runs (no tool calls). These are useful as a
backup path when API providers fail. Image pass-through is supported when you configure
an imageArg that accepts file paths.
Notes:
- CLI backends are text-first; tools are always disabled.
- Sessions are supported when
sessionArgis set; session ids are persisted per backend. - For
claude-cli, defaults are wired in. Override the command path if PATH is minimal (launchd/systemd).
agents.defaults.contextPruning (tool-result pruning)
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.
High level:
- Never touches user/assistant messages.
- Protects the last
keepLastAssistantsassistant messages (no tool results after that point are pruned). - Protects the bootstrap prefix (nothing before the first user message is pruned).
- Modes:
adaptive: soft-trims oversized tool results (keep head/tail) when the estimated context ratio crossessoftTrimRatio. Then hard-clears the oldest eligible tool results when the estimated context ratio crosseshardClearRatioand there’s enough prunable tool-result bulk (minPrunableToolChars).aggressive: always replaces eligible tool results before the cutoff with thehardClear.placeholder(no ratio checks).
- Soft-trim: only for oversized tool results. Keeps the beginning + end and inserts
...in the middle.- Before:
toolResult("…very long output…") - After:
toolResult("HEAD…\n...\n…TAIL\n\n[Tool result trimmed: …]")
- Before:
- Hard-clear: replaces the entire tool result with the placeholder.
- Before:
toolResult("…very long output…") - After:
toolResult("[Old tool result content cleared]")
- Before:
- Tool results containing image blocks are skipped (never trimmed/cleared) right now.
- The estimated “context ratio” is based on characters (approximate), not exact tokens.
- If the session doesn’t contain at least
keepLastAssistantsassistant messages yet, pruning is skipped. - In
aggressivemode,hardClear.enabledis ignored (eligible tool results are always replaced withhardClear.placeholder).
mode is "adaptive" or "aggressive"):
keepLastAssistants:3softTrimRatio:0.3(adaptive only)hardClearRatio:0.5(adaptive only)minPrunableToolChars:50000(adaptive only)softTrim:{ maxChars: 4000, headChars: 1500, tailChars: 1500 }(adaptive only)hardClear:{ enabled: true, placeholder: "[Old tool result content cleared]" }
agents.defaults.compaction (reserve headroom + memory flush)
agents.defaults.compaction.mode selects the compaction summarization strategy. Defaults to default; set safeguard to enable chunked summarization for very long histories. See /concepts/compaction.
agents.defaults.compaction.reserveTokensFloor enforces a minimum reserveTokens
value for Pi compaction (default: 20000). Set it to 0 to disable the floor.
agents.defaults.compaction.memoryFlush runs a silent agentic turn before
auto-compaction, instructing the model to store durable memories on disk (e.g.
memory/YYYY-MM-DD.md). It triggers when the session token estimate crosses a
soft threshold below the compaction limit.
Legacy defaults:
memoryFlush.enabled:truememoryFlush.softThresholdTokens:4000memoryFlush.prompt/memoryFlush.systemPrompt: built-in defaults withNO_REPLY- Note: memory flush is skipped when the session workspace is read-only
(
agents.defaults.sandbox.workspaceAccess: "ro"or"none").
agents.defaults.blockStreamingDefault:"on"/"off"(default off).- Channel overrides:
*.blockStreaming(and per-account variants) to force block streaming on/off. Non-Telegram channels require an explicit*.blockStreaming: trueto enable block replies. agents.defaults.blockStreamingBreak:"text_end"or"message_end"(default: text_end).agents.defaults.blockStreamingChunk: soft chunking for streamed blocks. Defaults to 800–1200 chars, prefers paragraph breaks (\n\n), then newlines, then sentences. Example:agents.defaults.blockStreamingCoalesce: merge streamed blocks before sending. Defaults to{ idleMs: 1000 }and inheritsminCharsfromblockStreamingChunkwithmaxCharscapped to the channel text limit. Signal/Slack/Discord/Google Chat default tominChars: 1500unless overridden. Channel overrides:channels.whatsapp.blockStreamingCoalesce,channels.telegram.blockStreamingCoalesce,channels.discord.blockStreamingCoalesce,channels.slack.blockStreamingCoalesce,channels.mattermost.blockStreamingCoalesce,channels.signal.blockStreamingCoalesce,channels.imessage.blockStreamingCoalesce,channels.msteams.blockStreamingCoalesce,channels.googlechat.blockStreamingCoalesce(and per-account variants).agents.defaults.humanDelay: randomized pause between block replies after the first. Modes:off(default),natural(800–2500ms),custom(useminMs/maxMs). Per-agent override:agents.list[].humanDelay. Example:
agents.defaults.typingMode:"never" | "instant" | "thinking" | "message". Defaults toinstantfor direct chats / mentions andmessagefor unmentioned group chats.session.typingMode: per-session override for the mode.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 for behavior details.
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, Clawdia 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.
agents.defaults.heartbeat configures periodic heartbeat runs:
every: duration string (ms,s,m,h); default unit minutes. Default:30m. Set0mto disable.model: optional override model for heartbeat runs (provider/model).includeReasoning: whentrue, heartbeats will also deliver the separateReasoning:message when available (same shape as/reasoning on). Default:false.session: optional session key to control which session the heartbeat runs in. Default:main.to: optional recipient override (channel-specific id, e.g. E.164 for WhatsApp, chat id for Telegram).target: optional delivery channel (last,whatsapp,telegram,discord,slack,msteams,signal,imessage,none). Default:last.prompt: optional override for the heartbeat body (default:Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.). Overrides are sent verbatim; include aRead HEARTBEAT.mdline if you still want the file read.ackMaxChars: max chars allowed afterHEARTBEAT_OKbefore delivery (default: 300).
- Set
agents.list[].heartbeatto enable or override heartbeat settings for a specific agent. - If any agent entry defines
heartbeat, only those agents run heartbeats; defaults become the shared baseline for those agents.
every, keep HEARTBEAT.md tiny, and/or choose a cheaper model.
tools.exec configures background exec 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)notifyOnExit: enqueue a system event + request heartbeat when backgrounded exec exits (default true)applyPatch.enabled: enable experimentalapply_patch(OpenAI/OpenAI Codex only; default false)applyPatch.allowModels: optional allowlist of model ids (e.g.gpt-5.2oropenai/gpt-5.2) Note:applyPatchis only undertools.exec.
tools.web configures web search + fetch tools:
tools.web.search.enabled(default: true when key is present)tools.web.search.apiKey(recommended: set viaclawdia configure --section web, or useBRAVE_API_KEYenv var)tools.web.search.maxResults(1–10, default 5)tools.web.search.timeoutSeconds(default 30)tools.web.search.cacheTtlMinutes(default 15)tools.web.fetch.enabled(default true)tools.web.fetch.maxChars(default 50000)tools.web.fetch.timeoutSeconds(default 30)tools.web.fetch.cacheTtlMinutes(default 15)tools.web.fetch.userAgent(optional override)tools.web.fetch.readability(default true; disable to use basic HTML cleanup only)tools.web.fetch.firecrawl.enabled(default true when an API key is set)tools.web.fetch.firecrawl.apiKey(optional; defaults toFIRECRAWL_API_KEY)tools.web.fetch.firecrawl.baseUrl(default https://api.firecrawl.dev)tools.web.fetch.firecrawl.onlyMainContent(default true)tools.web.fetch.firecrawl.maxAgeMs(optional)tools.web.fetch.firecrawl.timeoutSeconds(optional)
tools.media configures inbound media understanding (image/audio/video):
tools.media.models: shared model list (capability-tagged; used after per-cap lists).tools.media.concurrency: max concurrent capability runs (default 2).tools.media.image/tools.media.audio/tools.media.video:enabled: opt-out switch (default true when models are configured).prompt: optional prompt override (image/video append amaxCharshint automatically).maxChars: max output characters (default 500 for image/video; unset for audio).maxBytes: max media size to send (defaults: image 10MB, audio 20MB, video 50MB).timeoutSeconds: request timeout (defaults: image 60s, audio 60s, video 120s).language: optional audio hint.attachments: attachment policy (mode,maxAttachments,prefer).scope: optional gating (first match wins) withmatch.channel,match.chatType, ormatch.keyPrefix.models: ordered list of model entries; failures or oversize media fall back to the next entry.
- Each
models[]entry:- Provider entry (
type: "provider"or omitted):provider: API provider id (openai,anthropic,google/gemini,groq, etc).model: model id override (required for image; defaults togpt-4o-mini-transcribe/whisper-large-v3-turbofor audio providers, andgemini-3-flash-previewfor video).profile/preferredProfile: auth profile selection.
- CLI entry (
type: "cli"):command: executable to run.args: templated args (supports{{MediaPath}},{{Prompt}},{{MaxChars}}, etc).
capabilities: optional list (image,audio,video) to gate a shared entry. Defaults when omitted:openai/anthropic/minimax→ image,google→ image+audio+video,groq→ audio.prompt,maxChars,maxBytes,timeoutSeconds,languagecan be overridden per entry.
- Provider entry (
enabled: false), understanding is skipped; the model still receives the original attachments.
Provider auth follows the standard model auth order (auth profiles, env vars like OPENAI_API_KEY/GROQ_API_KEY/GEMINI_API_KEY, or models.providers.*.apiKey).
Example:
agents.defaults.subagents configures sub-agent defaults:
model: default model for spawned sub-agents (string or{ primary, fallbacks }). If omitted, sub-agents inherit the caller’s model unless overridden per agent or per call.maxConcurrent: max concurrent sub-agent runs (default 1)archiveAfterMinutes: auto-archive sub-agent sessions after N minutes (default 60; set0to disable)- Per-subagent tool policy:
tools.subagents.tools.allow/tools.subagents.tools.deny(deny wins)
tools.profile sets a base tool allowlist before tools.allow/tools.deny:
minimal:session_statusonlycoding:group:fs,group:runtime,group:sessions,group:memory,imagemessaging:group:messaging,sessions_list,sessions_history,sessions_send,session_statusfull: no restriction (same as unset)
agents.list[].tools.profile.
Example (messaging-only by default, allow Slack + Discord tools too):
tools.byProvider lets you further restrict tools for specific providers (or a single provider/model).
Per-agent override: agents.list[].tools.byProvider.
Order: base profile → provider profile → allow/deny policies.
Provider keys accept either provider (e.g. google-antigravity) or provider/model
(e.g. openai/gpt-5.2).
Example (keep global coding profile, but minimal tools for Google Antigravity):
tools.allow / tools.deny configure a global tool allow/deny policy (deny wins).
Matching is case-insensitive and supports * wildcards ("*" means all tools).
This is applied even when the Docker sandbox is off.
Example (disable browser/canvas everywhere):
group:runtime:exec,bash,processgroup:fs:read,write,edit,apply_patchgroup:sessions:sessions_list,sessions_history,sessions_send,sessions_spawn,session_statusgroup:memory:memory_search,memory_getgroup:web:web_search,web_fetchgroup:ui:browser,canvasgroup:automation:cron,gatewaygroup:messaging:messagegroup:nodes:nodesgroup:clawdia: all built-in Clawdia tools (excludes provider plugins)
tools.elevated controls elevated (host) exec access:
enabled: allow elevated mode (default true)allowFrom: per-channel allowlists (empty = disabled)whatsapp: E.164 numberstelegram: chat ids or usernamesdiscord: user ids or usernames (falls back tochannels.discord.dm.allowFromif omitted)signal: E.164 numbersimessage: handles/chat idswebchat: session ids or usernames
tools.elevatedis the global baseline.agents.list[].tools.elevatedcan only further restrict (both must allow)./elevated on|off|ask|fullstores state per session key; inline directives apply to a single message.- Elevated
execruns on the host and bypasses sandboxing. - Tool policy still applies; if
execis denied, elevated cannot be used.
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.
agents.defaults.sandbox
Optional Docker sandboxing for the embedded agent. Intended for non-main
sessions so they cannot access your host system.
Details: Sandboxing
Defaults (if enabled):
- scope:
"agent"(one container + workspace per agent) - Debian bookworm-slim based image
- agent workspace access:
workspaceAccess: "none"(default)"none": use a per-scope sandbox workspace under~/.clawdia/sandboxes
"ro": keep the sandbox workspace at/workspace, and mount the agent workspace read-only at/agent(disableswrite/edit/apply_patch)"rw": mount the agent workspace read/write at/workspace
- auto-prune: idle > 24h OR age > 7d
- tool policy: allow only
exec,process,read,write,edit,apply_patch,sessions_list,sessions_history,sessions_send,sessions_spawn,session_status(deny wins)- configure via
tools.sandbox.tools, override per-agent viaagents.list[].tools.sandbox.tools - tool group shorthands supported in sandbox policy:
group:runtime,group:fs,group:sessions,group:memory(see Sandbox vs Tool Policy vs Elevated)
- configure via
- optional sandboxed browser (Chromium + CDP, noVNC observer)
- hardening knobs:
network,user,pidsLimit,memory,cpus,ulimits,seccompProfile,apparmorProfile
scope: "shared" means a shared container and shared workspace. No
cross-session isolation. Use scope: "session" for per-session isolation.
Legacy: perSession is still supported (true → scope: "session",
false → scope: "shared").
setupCommand runs once after the container is created (inside the container via sh -lc).
For package installs, ensure network egress, a writable root FS, and a root user.
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.
Note: docker.binds mounts additional host directories; global and per-agent binds are merged.
Build the optional browser image with:
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
URL is injected per session.
agents.defaults.sandbox.browser.allowHostControl (default: false) allows
sandboxed sessions to explicitly target the host browser control server
via the browser tool (target: "host"). Leave this off if you want strict
sandbox isolation.
Allowlists for remote control:
allowedControlUrls: exact control URLs permitted fortarget: "custom".allowedControlHosts: hostnames permitted (hostname only, no port).allowedControlPorts: ports permitted (defaults: http=80, https=443). Defaults: all allowlists are unset (no restriction).allowHostControldefaults to false.
models (custom providers + base URLs)
Clawdia uses the pi-coding-agent model catalog. You can add custom providers
(LiteLLM, local OpenAI-compatible servers, Anthropic proxies, etc.) by writing
~/.clawdia/agents/<agentId>/agent/models.json or by defining the same schema inside your
Clawdia config under models.providers.
Provider-by-provider overview + examples: /concepts/model-providers.
When models.providers is present, Clawdia writes/merges a models.json into
~/.clawdia/agents/<agentId>/agent/ on startup:
- default behavior: merge (keeps existing providers, overrides on name)
- set
models.mode: "replace"to overwrite the file contents
agents.defaults.model.primary (provider/model).
OpenCode Zen (multi-model proxy)
OpenCode Zen is a multi-model gateway with per-model endpoints. Clawdia uses the built-inopencode provider from pi-ai; set OPENCODE_API_KEY (or
OPENCODE_ZEN_API_KEY) from https://opencode.ai/auth.
Notes:
- Model refs use
opencode/<modelId>(example:opencode/claude-opus-4-5). - If you enable an allowlist via
agents.defaults.models, add each model you plan to use. - Shortcut:
clawdia onboard --auth-choice opencode-zen.
Z.AI (GLM-4.7) — provider alias support
Z.AI models are available via the built-inzai provider. Set ZAI_API_KEY
in your environment and reference the model by provider/model.
Shortcut: clawdia onboard --auth-choice zai-api-key.
z.ai/*andz-ai/*are accepted aliases and normalize tozai/*.- If
ZAI_API_KEYis missing, requests tozai/*will fail with an auth error at runtime. - Example error:
No API key found for provider "zai". - Z.AI’s general API endpoint is
https://api.z.ai/api/paas/v4. GLM coding requests use the dedicated Coding endpointhttps://api.z.ai/api/coding/paas/v4. The built-inzaiprovider uses the Coding endpoint. If you need the general endpoint, define a custom provider inmodels.providerswith the base URL override (see the custom providers section above). - Use a fake placeholder in docs/configs; never commit real API keys.
Moonshot AI (Kimi)
Use Moonshot’s OpenAI-compatible endpoint:- Set
MOONSHOT_API_KEYin the environment or useclawdia onboard --auth-choice moonshot-api-key. - Model ref:
moonshot/kimi-k2-0905-preview. - Use
https://api.moonshot.cn/v1if you need the China endpoint.
Kimi Code
Use Kimi Code’s dedicated OpenAI-compatible endpoint (separate from Moonshot):- Set
KIMICODE_API_KEYin the environment or useclawdia onboard --auth-choice kimi-code-api-key. - Model ref:
kimi-code/kimi-for-coding.
Synthetic (Anthropic-compatible)
Use Synthetic’s Anthropic-compatible endpoint:- Set
SYNTHETIC_API_KEYor useclawdia onboard --auth-choice synthetic-api-key. - Model ref:
synthetic/hf:MiniMaxAI/MiniMax-M2.1. - Base URL should omit
/v1because the Anthropic client appends it.
Local models (LM Studio) — recommended setup
See /gateway/local-models for the current local guidance. TL;DR: run MiniMax M2.1 via LM Studio Responses API on serious hardware; keep hosted models merged for fallback.MiniMax M2.1
Use MiniMax M2.1 directly without LM Studio:- Set
MINIMAX_API_KEYenvironment variable or useclawdia onboard --auth-choice minimax-api. - Available model:
MiniMax-M2.1(default). - Update pricing in
models.jsonif you need exact cost tracking.
Cerebras (GLM 4.6 / 4.7)
Use Cerebras via their OpenAI-compatible endpoint:- Use
cerebras/zai-glm-4.7for Cerebras; usezai/glm-4.7for Z.AI direct. - Set
CEREBRAS_API_KEYin the environment or config.
- Supported APIs:
openai-completions,openai-responses,anthropic-messages,google-generative-ai - Use
authHeader: true+headersfor custom auth needs. - Override the agent config root with
CLAWDIA_AGENT_DIR(orPI_CODING_AGENT_DIR) if you wantmodels.jsonstored elsewhere (default:~/.clawdia/agents/main/agent).
session
Controls session scoping, reset policy, reset triggers, and where the session store is written.
mainKey: direct-chat bucket key (default:"main"). Useful when you want to “rename” the primary DM thread without changingagentId.- Sandbox note:
agents.defaults.sandbox.mode: "non-main"uses this key to detect the main session. Any session key that does not matchmainKey(groups/channels) is sandboxed.
- Sandbox note:
dmScope: how DM sessions are grouped (default:"main").main: all DMs share the main session for continuity.per-peer: isolate DMs by sender id across channels.per-channel-peer: isolate DMs per channel + sender (recommended for multi-user inboxes).
identityLinks: map canonical ids to provider-prefixed peers so the same person shares a DM session across channels when usingper-peerorper-channel-peer.- Example:
alice: ["telegram:123456789", "discord:987654321012345678"].
- Example:
reset: primary reset policy. Defaults to daily resets at 4:00 AM local time on the gateway host.mode:dailyoridle(default:dailywhenresetis present).atHour: local hour (0-23) for the daily reset boundary.idleMinutes: sliding idle window in minutes. When daily + idle are both configured, whichever expires first wins.
resetByType: per-session overrides fordm,group, andthread.- If you only set legacy
session.idleMinuteswithout anyreset/resetByType, Clawdia stays in idle-only mode for backward compatibility.
- If you only set legacy
heartbeatIdleMinutes: optional idle override for heartbeat checks (daily reset still applies when enabled).agentToAgent.maxPingPongTurns: max reply-back turns between requester/target (0–5, default 5).sendPolicy.default:allowordenyfallback when no rule matches.sendPolicy.rules[]: match bychannel,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 ~/.clawdia/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.
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).
plugins (extensions)
Controls plugin discovery, allow/deny, and per-plugin config. Plugins are loaded
from ~/.clawdia/extensions, <workspace>/.clawdia/extensions, plus any
plugins.load.paths entries. Config changes require a gateway restart.
See /plugin for full usage.
Fields:
enabled: master toggle for plugin loading (default: true).allow: optional allowlist of plugin ids; when set, only listed plugins load.deny: optional denylist of plugin ids (deny wins).load.paths: extra plugin files or directories to load (absolute or~).entries.<pluginId>: per-plugin overrides.enabled: setfalseto disable.config: plugin-specific config object (validated by the plugin if provided).
browser (clawd-managed browser)
Clawdia can start a dedicated, isolated Chrome/Brave/Edge/Chromium instance for clawd and expose a small loopback control server.
Profiles can point at a remote Chromium-based browser via profiles.<name>.cdpUrl. Remote
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(cat-orange) - Note: the control server is started by the running gateway (Clawdia.app menubar, or
clawdia gateway). - Auto-detect order: default browser if Chromium-based; otherwise Chrome → Brave → Edge → Chromium → Chrome Canary.
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.
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.controlUi.basePathsets the URL prefix where the Control UI is served.- Examples:
"/ui","/clawdia","/apps/clawdia". - Default: root (
/) (unchanged). gateway.controlUi.allowInsecureAuthallows token-only auth over HTTP (no device identity). Default:false. Prefer HTTPS (Tailscale Serve) or127.0.0.1.
gateway.trustedProxies: list of reverse proxy IPs that terminate TLS in front of the Gateway.- When a connection comes from one of these IPs, Clawdia uses
x-forwarded-for(orx-real-ip) to determine the client IP for local pairing checks and HTTP auth/local checks. - Only list proxies you fully control, and ensure they overwrite incoming
x-forwarded-for.
clawdia 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).- OpenAI Chat Completions endpoint: disabled by default; enable with
gateway.http.endpoints.chatCompletions.enabled: true. - Precedence:
--port>CLAWDIA_GATEWAY_PORT>gateway.port> default18789. - Non-loopback binds (
lan/tailnet/auto) require auth. Usegateway.auth.token(orCLAWDIA_GATEWAY_TOKEN). - The onboarding wizard generates a gateway token by default (even on loopback).
gateway.remote.tokenis only for remote CLI calls; it does not enable local gateway auth.gateway.tokenis ignored.
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 viaCLAWDIA_GATEWAY_PASSWORD(recommended).gateway.auth.allowTailscaleallows Tailscale Serve identity headers (tailscale-user-login) to satisfy auth when the request arrives on loopback withx-forwarded-for,x-forwarded-proto, andx-forwarded-host. Whentrue, Serve requests do not need a token/password; setfalseto require explicit credentials. Defaults totruewhentailscale.mode = "serve"and auth mode is notpassword.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.
gateway.remote.urlsets the default Gateway WebSocket URL for CLI calls whengateway.mode = "remote".gateway.remote.transportselects the macOS remote transport (sshdefault,directfor ws/wss). Whendirect,gateway.remote.urlmust bews://orwss://.ws://hostdefaults to port18789.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).
- Clawdia.app watches
~/.nelsonmuntz-c/clawdia.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.url+gateway.remote.transportin remote mode) back to the config file.
gateway.reload (Config hot reload)
The Gateway watches ~/.nelsonmuntz-c/clawdia.json (or CLAWDIA_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.
Hot reload matrix (files + impact)
Files watched:~/.nelsonmuntz-c/clawdia.json(orCLAWDIA_CONFIG_PATH)
hooks(webhook auth/path/mappings) +hooks.gmail(Gmail watcher restarted)browser(browser control server restart)cron(cron service restart + concurrency update)agents.defaults.heartbeat(heartbeat runner restart)web(WhatsApp web channel restart)telegram,discord,signal,imessage(channel restarts)agent,models,routing,messages,session,whatsapp,logging,skills,ui,talk,identity,wizard(dynamic reads)
gateway(port/bind/auth/control UI/tailscale)bridge(legacy)discoverycanvasHostplugins- Any unknown/unsupported config path (defaults to restart for safety)
Multi-instance isolation
To run multiple gateways on one host (for redundancy or a rescue bot), isolate per-instance state + config and use unique ports:CLAWDIA_CONFIG_PATH(per-instance config)CLAWDIA_STATE_DIR(sessions/creds)agents.defaults.workspace(memories)gateway.port(unique per instance)
clawdia --dev …→ uses~/.clawdia-dev+ shifts ports from base19001clawdia --profile <name> …→ uses~/.clawdia-<name>(port via config/env/flags)
hooks (Gateway webhooks)
Enable a simple HTTP webhook endpoint on the Gateway HTTP server.
Defaults:
- enabled:
false - path:
/hooks - maxBodyBytes:
262144(256 KB)
Authorization: Bearer <token>orx-clawdia-token: <token>or?token=<token>
POST /hooks/wake→{ text, mode?: "now"|"next-heartbeat" }POST /hooks/agent→{ message, name?, sessionKey?, wakeMode?, deliver?, channel?, to?, model?, 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.deliver: truesends the final reply to a channel;channeldefaults tolast(falls back to WhatsApp).- If there is no prior delivery route, set
channel+toexplicitly (required for Telegram/Discord/Google Chat/Slack/Signal/iMessage/MS Teams). modeloverrides the LLM for this hook run (provider/modelor alias; must be allowed ifagents.defaults.modelsis set).
clawdia webhooks gmail setup / run):
hooks.gmail.modelspecifies a model to use for Gmail hook processing (defaults to session primary).- Accepts
provider/modelrefs or aliases fromagents.defaults.models. - Falls back to
agents.defaults.model.fallbacks, thenagents.defaults.model.primary, on auth/rate-limit/timeouts. - If
agents.defaults.modelsis set, include the hooks model in the allowlist. - At startup, warns if the configured model is not in the model catalog or allowlist.
hooks.gmail.thinkingsets the default thinking level for Gmail hooks and is overridden by per-hookthinking.
- If
hooks.enabled=trueandhooks.gmail.accountis set, the Gateway startsgog gmail watch serveon boot and auto-renews the watch. - Set
CLAWDIA_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.
tailscale.mode is on, Clawdia defaults serve.path to / so
Tailscale can proxy /gmail-pubsub correctly (it strips the set-path prefix).
If you need the backend to receive the prefixed path, set
hooks.gmail.tailscale.target to a full URL (and align serve.path).
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/canvasDefault port:
18793 (chosen to avoid the clawd browser CDP port 18792)The server listens on the gateway 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
/__clawdia/ws - auto-creates a starter
index.htmlwhen the directory is empty (so you see something immediately) - also serves A2UI at
/__clawdia__/a2ui/and is advertised to nodes ascanvasHostUrl(always used by nodes for Canvas/A2UI)
EMFILE:
- config:
canvasHost: { liveReload: false }
canvasHost.* require a gateway restart (config reload will restart).
Disable with:
- config:
canvasHost: { enabled: false } - env:
CLAWDIA_SKIP_CANVAS_HOST=1
bridge (legacy TCP bridge, removed)
Current builds no longer include the TCP bridge listener; bridge.* config keys are ignored.
Nodes connect over the Gateway WebSocket. This section is kept for historical reference.
Legacy behavior:
- The Gateway could expose a simple TCP bridge for nodes (iOS/Android), typically on port
18790.
- enabled:
true - port:
18790 - bind:
lan(binds to0.0.0.0)
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.tls.enabled: enable TLS for bridge connections (TLS-only when enabled).bridge.tls.autoGenerate: generate a self-signed cert when no cert/key are present (default: true).bridge.tls.certPath/bridge.tls.keyPath: PEM paths for the bridge certificate + private key.bridge.tls.caPath: optional PEM CA bundle (custom roots or future mTLS).
bridgeTls=1 and bridgeTlsSha256 in discovery TXT
records so nodes can pin the certificate. Manual connections use trust-on-first-use if no
fingerprint is stored yet.
Auto-generated certs require openssl on PATH; if generation fails, the bridge will not start.
discovery.wideArea (Wide-Area Bonjour / unicast DNS‑SD)
When enabled, the Gateway writes a unicast DNS-SD zone for _clawdia-bridge._tcp under ~/.clawdia/dns/ using the standard discovery domain clawdia.internal.
To make iOS/Android discover across networks (Vienna ⇄ London), pair this with:
- a DNS server on the gateway host serving
clawdia.internal.(CoreDNS is recommended) - Tailscale split DNS so clients resolve
clawdia.internalvia that server
Template variables
Template placeholders are expanded intools.media.*.models[].args and tools.media.models[].args (and any future templated argument fields).
| Variable | Description | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|
{{Body}} | Full inbound message body | |||||||||
{{RawBody}} | Raw inbound message body (no history/sender wrappers; best for command parsing) | |||||||||
{{BodyStripped}} | Body with group mentions stripped (best default for agents) | |||||||||
{{From}} | Sender identifier (E.164 for WhatsApp; may differ per channel) | |||||||||
{{To}} | Destination identifier | |||||||||
{{MessageSid}} | Channel 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) | |||||||||
{{Prompt}} | Resolved media prompt for CLI entries | |||||||||
{{MaxChars}} | Resolved max output chars for CLI entries | |||||||||
{{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) | |||||||||
{{Provider}} | Provider hint (whatsapp | telegram | discord | googlechat | slack | signal | imessage | msteams | webchat | …) |
Cron (Gateway scheduler)
Cron is a Gateway-owned scheduler for wakeups and scheduled jobs. See Cron jobs for the feature overview and CLI examples.Next: Agent Runtime 🦞
