bots.json Configuration
Configure bots via ~/.botmux/bots.json. Run botmux setup to create it interactively, or edit it by hand. The file is an array; each element is a bot (in production, one bot maps to one dedicated daemon process).
Most fields are optional — just
larkAppId/larkAppSecretis enough to run; add the rest as needed. Applies to: manually tuning CLI / model / working dir / permissions / sandbox, etc.; for everyday config the dashboard's Bot Config page is preferred (it edits the samebots.json). Runbotmux restartto apply changes.
There are many fields, listed below grouped by purpose. The vast majority are optional — just larkAppId / larkAppSecret is enough to get running, and you add the rest as needed.
Required
CLI and model
Codex-compatible distributions
An independently released CLI that preserves Codex's arguments, interaction, rollout / resume, and authentication semantics does not need a new cliId. Keep cliId: "codex" as the protocol adapter and describe the concrete runtime separately:
idis a stable identity using letters, numbers,.,_, or-, up to 64 characters. Changing it is treated as switching distributions.executableis one executable name or path, not a shell command; do not append arguments. The Dashboard performs a read-only--versionprobe before saving, and its output must contain a recognizableX.Y.Zversion.displayNamecontrols cards, status, and Dashboard labels only; it defaults toid.update.provideris one ofauto,self,npm, ornone.autotrusts only a unique npm package proven to own that exact binary. If no source can be established, the runtime is shown as unmanaged and is never compared with official Codex. Onlyselfuses the CLI's structured doctor data, and its current version must match--version;npmrequires the distribution's ownpackageName;nonedisables update checks for that runtime.cliRuntimecurrently applies only tocliId: "codex"and cannot be combined withwrapperCli. BotMux writers generate acliPathOverridedowngrade shadow that exactly matchesexecutable: new versions usecliRuntimeas canonical, while old versions still launch the same binary from the shadow. Manual configs must include the same exact shadow as shown above; a missing or mismatched value fails validation so an accepted config is always safe to roll back. Wrappers and gateways keep using the legacy entry-point mechanism below.- Existing
cliPathOverrideconfigs remain launch-compatible and receive the same safeautoupdate behavior. The Dashboard shows them in a read-only compatibility state: model-only saves preserve the old entry point, choosing Official Codex explicitly clears it, and choosing Custom Compatible migrates it tocliRuntime. Because a raw path does not assert the full compatibility contract, Codex RPC enhancements remain disabled.
A session freezes its runtime snapshot when created. Model-only changes affect new sessions; switching CLI, runtime, or wrapper immediately closes active sessions that still use the old launch identity so they cannot lazy-resume into the wrong distribution. Existing sessions are never silently switched to another runtime.
Run a bot on GLM / a third-party provider (per-bot env)
Run one bot on a GLM Coding Plan (or any Anthropic-compatible provider) while another keeps using official Claude — give the former an env:
- For GLM in China, use
https://open.bigmodel.cn/api/anthropicforANTHROPIC_BASE_URL. - For an OpenAI-protocol CLI like Codex, set
OPENAI_BASE_URL/OPENAI_API_KEY(the provider's OpenAI-compatible endpoint) instead ofANTHROPIC_*. - Isolation: env is injected per-session into the CLI process, consistently across backends (tmux / zellij inject it per-pane, never into the shared server env), so one bot's provider config can't leak into another's.
- Security: values live in
bots.jsonand the process environment in plaintext — not a secret vault; chat surfaces like/config getmask the values (the owner-authenticated dashboard editor shows real values). - Takes effect from the next session.
Clean Codex App input (experimental)
codexAppCleanInput keeps user messages shown in Codex App clean while preserving the context Botmux needs when invoking the model. It defaults to false / off; when disabled, Botmux keeps the original combined-prompt behavior unchanged.
An owner or allowedUsers member can hot-update it with /botconfig; no daemon restart is required:
You can also add it to the corresponding bot entry directly (manual bots.json edits still require the restart described at the end of this page):
- The flag applies only to Botmux-managed sessions whose actual CLI is
codex-app; other CLIs and externally bridged/adoptsessions are unaffected. A session-frozen CLI takes precedence over a later bot-default CLI change. - When enabled, user-authored turns use the original text as the Codex App text
UserMessage; Botmux-authored synthetic turns such as external triggers and document prewarm use a short readable label. Message-level sender, mentions, attachment paths, quotes, role, whiteboard, Skills, and synthetic-turn instructions primarily move to hiddenadditionalContext. Readable absolute-path images are also sent aslocalImage; missing, relative, or unreadable images skip the native image item with a diagnostic while their attachment path remains in context. - A detectable Codex CLI
>= 0.135enables clean text plusadditionalContext;>= 0.136also attaches a separateclientUserMessageId. Older or unknown versions use the legacy combined prompt directly. - The runner retries the legacy prompt once only when app-server explicitly rejects
additionalContext/clientUserMessageIdbeforeturn/started, then disables clean mode for that runner lifetime. Network, timeout, model, and generic turn errors are never auto-retried, avoiding duplicate work. - A
/botconfigchange is sampled at the next dispatch to the Codex worker. For ordinary live messages this is normally the next message; a first turn waiting on repo selection is sampled when the repo is committed. Already queued or running turns are not rewritten, and existing history is never backfilled. additionalContextis omitted from the ordinary Codex App user-message bubble, but it may still be retained in raw rollout or diagnostic records. When enabled, Botmux also keeps the legacy prompt and structured sidecar for compatibility fallback andretry_last_task. This feature improves App presentation and ordinary history reading; it is not a privacy-erasure or security-redaction mechanism.
Working directory
Permissions and authorization
File sandbox
ZMX cannot enforce the file sandbox or effective read isolation, so configurations that enable those boundaries fail closed; see ZMX backend boundaries.
Cards and terminal
Proactive start
Group message listener
Have a bot actively watch a group: matching group messages start a session automatically, no @ required. The classic use is alert operations — your monitoring/alerting system usually already has its own Lark bot posting alerts into a group, so just add this bot to that group and enable the listener; every alert triggers an investigation session, with no need to set up a separate Webhook integration point.
Configure it per-group in the Dashboard "Roles → Message Listener" tab (with Preview of the last 24h of matches and a dry run to validate); or write messageListeners directly in bots.json (keyed by chat_id, valued by the config below):
Conventions and limits (V1):
- Top-level group messages only: ordinary replies inside an existing topic are not handled; a message that explicitly @s this bot still goes through normal @ routing (no double trigger).
- One session per matched message, replied to in a new topic beneath it.
- Delivery: the realtime event path covers messages Lark pushes; messages from other bots, and non-@ messages, are backfilled by a history poll roughly every 30s (so up to ~30s of latency). That's why listening to a third-party alert bot works most reliably in blacklist mode (
all_except_excluded+ include"bot") — whitelist matches byopen_id, but the history API reports third-party bots byapp_id, which may not resolve to anopen_idand therefore won't match.
Summary command
Example:
- Only the explicit
@bot /summarycommand triggers a summary. Messages that do not mention the bot still follow the existing group/topic routing rules and are not woken up by keywords. - The dashboard "/summary Range" controls this
summaryRangefield; the "Enable memory" toggle and "Memory file path" input savesummaryMemoryandsummaryMemoryPathrespectively. - If an earlier
@same bot /summaryexists before the current trigger, the summary window includes only messages after that earlier command and up to the current trigger; otherwise botmux falls back tolimit/sinceHours. limitandsinceHoursare safety caps for the default (no explicit boundary) summary window. If both are0, that dimension is not limited. An explicit boundary intentionally takes precedence over these caps: whensummaryMemoryis on and/summarycarries boundary text, botmux honors the user's explicit "start from this message" intent and includes everything from the matched boundary onward — in a regular grouplimitstill bounds the scan, but a boundary older thansinceHours, and any arbitrarily old boundary in a topic group, is accepted and may exceed the default configured range. If you do not want a bot to read in very old content, the reliable approach is to omit the boundary text; in a regular group you can also lowerlimitto bound the scan (butsinceHours, and any boundary in a topic group, are not constrained by the configured range).- Only when
summaryMemoryis enabled, text following the/summarycommand is treated as a hard boundary: it locates the most recent message before the trigger that contains that text and summarizes only from there up to the current trigger; if the boundary is not found in the scanned history, botmux does not fall back to a wider range but hands the agent a "boundary not found" error together with an empty history (the memory write instruction still runs). WhensummaryMemoryis off, text after/summaryis only a focus hint for the summary and the history window still followssummaryRange. - The memory file is written by the agent within its working directory. If the bot has sandbox enabled and
summaryMemoryPathpoints outside the working directory (an absolute path, or a relative path that escapes via../), add the file's existing parent directory tosandboxPaths.readWrite; the worker filters out paths that do not yet exist at spawn time, and a new memory file usually does not exist yet, so adding only the file path itself is dropped (unless the file is pre-created). Otherwise the write may be denied by the sandbox.
Legacy content trigger config
Voice
Meeting listener roles and group placement
vcMeetingAgent.meetingConsumer.consumerProfiles defines reusable meeting-listener roles. responseMode controls whether automatic model output is authorized; listenerDelivery.placement independently controls where authorized output appears:
The Dashboard meeting-role editor includes a local built-in template library: Important information sync, Meeting minutes and action items, Meeting facilitator, Solution review and risk challenge, and Interview and requirement insights. “Use template” copies the selected template into a normal, fully editable profile; later template changes never overwrite user configuration. The catalog has stable template IDs, versions, and source metadata so a community source can use the same model later. This release makes no network request and uploads no usage data, so popularity rankings are intentionally unavailable.
silent: automatic model output stays hidden.listener_thread: automatic output may be sent to the listener group and requireslistener.output.request.- placement
auto(also the default when omitted): preserve legacy session routing. - placement
chat: send every update as a top-level group message. - placement
topic: use the first useful update as a stable topic root and thread later updates under it. Removing and re-enabling the profile starts a new topic.
Automatic listener_thread output uses an internal skip | publish control protocol. The agent decides whether the current meeting state warrants publication; botmux sends only the publish message body and never renders the control JSON in Lark. There is no semantic-fingerprint deduplication or debounce/interval setting: novelty, observation time, and publication timing remain the agent's responsibility under the profile prompt and full meeting context. Malformed envelopes fail closed. Explicit human IM replies keep their existing quote/thread routing and do not use this protocol.
Example generic “important meeting information sync” profile:
Set agentAppId to the bot that executes the role. Add the profile id to defaultConsumerIds with defaultMode: "agents" to enable it by default, or select it manually from the in-meeting consumer card.
Runtime state (auto-maintained, do not edit)
The following fields are written by botmux itself and persisted into bots.json alongside authorizations / switches. They are listed only for reference — do not edit them by hand:
Configuration precedence: the
BOTS_CONFIGenvironment variable →~/.botmux/bots.json. Runbotmux restartafter editing to take effect.
