Skip to content

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.