Custom agents
Custom Agents
The built-in agents are the happy path: they ship with hook integration for authoritative session matching. If you run an agent ccmux doesn’t support out of the box, you can teach it one in ~/.config/ccmux/ccmux.json. Custom agents fall back to process matching plus terminal pattern scanning (no hooks), so detection is less precise than a built-in, but it gets unsupported agents onto the board.
Defining a custom agent
{ "agents": { "myagent": { "processMatch": "myagent", "terminalRules": [ { "matchAny": ["thinking...", "esc to interrupt"], "status": "working" }, { "matchAll": ["approve?", "[y/n]"], "status": "waiting", "attentionType": "permission", "pendingTool": "Command" } ], "resumeCommand": "myagent resume {id}", "promptCommand": "{bin} '{prompt}'" } }}promptCommand is what ccmux spawn --prompt types into the new pane. It
must start an interactive session with the prompt submitted, not a
one-shot/print run. {prompt} is the prompt text and has to stay wrapped in
single quotes, because that is the quoting ccmux escapes for. ccmux reads the
template the way sh does and refuses it unless every {prompt} lands in a
real single-quoted context with the template’s quotes balanced, so an unquoted
or double-quoted placeholder is rejected, and so is one whose single quotes sit
inside double quotes (sh -c "agent '{prompt}'"), where ' is just an
ordinary character and the escaping would do nothing. Backticks, $(,
backslashes, and bash/zsh $'...' quoting are rejected too, and the error
names whichever one it found. The optional {bin}
resolves to the agent’s launcher, so a wrapper binary or executable override
survives.
You can also override built-in agent settings by using the agent’s name as the key (e.g., "claude", "codex"). An override of notificationActions (the notification button/reply keystroke map) replaces the whole map, it is not merged key by key; it also controls the reply surfaces (replyOnQuestion, replyOnFinished, permissionReplyPrelude, the plan* keys, and the unsafeReplyPattern reply guard, written as a regex string like readyPattern), so any key you leave out is dropped rather than inherited from the built-in default. Copy across every key you still want when you override it. The one exception is unsafeReplyPattern: it is carried forward from the built-in as a safety default even when your override omits it, so a partial override can’t accidentally re-enable unapproved shell execution through a reply. To disable it on purpose, set an explicit never-match pattern (e.g. "/(?!x)x/").
| Field | Required | Description |
|---|---|---|
processMatch |
Yes* | Regex to match the process executable |
commandPatterns |
No | Additional regex patterns to match full commands |
terminalRules |
No | Ordered terminal matching rules |
versionCommand |
No | Command to get agent version |
versionPatterns |
No | Regex patterns to extract version from output |
resumeCommand |
No | Command template for restarting ({id} placeholder) |
promptCommand |
No | Command template for spawn --prompt ({prompt} placeholder, single-quoted) |
forkCommand |
No | Command template for Fork / spawn --fork ({path} single-quoted, or {id}) |
sessionFilePattern |
No | Regex to extract session ID from log filenames |
executable |
No | Command used to launch the agent (defaults to key) |
hooks |
No | { type } (built-in override only; internal) |
notificationActions |
No | Notification button/reply keystroke map (built-in override only; whole-map replace) |
* Required for new agents; optional when overriding built-in agents.
Invoke-related fields (invokeMode, errorRules, readyPattern) are documented in docs/invoke.md.
Each terminalRules entry must define exactly one matcher:
matchAny: matches when any string is present in the last 30 lines (case-insensitive)matchAll: matches only when every string is present in the last 30 lines (case-insensitive)
Rules are evaluated top-to-bottom, and the first match wins. This lets you express broad “working” prompts and more specific multi-line waiting prompts without detector-specific logic.