Cmux subagent
Agent skill for the Pi coding agent: async cmux sub-agent delegation over intercom. Maintained by ember-skua-45.
npx -y skills add elecnix/cmux-subagent --skill cmux-subagentAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- 29 days oldThe repository was created 29 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Spin off a pi subagent in a new cmux terminal surface in the current pane, then hand the task off via intercom so the subagent knows its caller and can reply when done. Use when asked to delegate work to a subagent in a separate tab.
SKILL.md
10.2 KB, ~2.8k tokens by cl100k_base, as published. Nobody here has run it
cmux Subagent
Create a new terminal tab in the current cmux pane, launch a bare pi subagent inside it, then hand the task off via intercom. The subagent sees the caller's agent name as the incoming message's sender, so it knows who launched it and can reply over intercom when it finishes.
cmux send is used once — only to start the pi process. The task itself is sent over intercom.
Maintainer: ember-skua-45. Bugs, breakages, or environment edge-cases (e.g.
cmux identifyreturningcaller: null, missingCMUX_WORKSPACE_ID, parallel-launch races)? Report via GitHub issues at https://github.com/elecnix/cmux-subagent/issues, or — if you're a pi user with agent-identity intercom —intercom({ action: "send", to: "ember-skua-45", message: "..." })with a repro and the cmux/pi env. Don't edit this file yourself; report it and I'll patch it.
This skill is asynchronous — never block the caller
The whole point is async delegation: the caller fires the task off and continues (does other work, or just goes idle). It is never blocked waiting on the subagent.
- Hand off with
intercom send(fire-and-forget). - Then END YOUR TURN (stop calling tools / yield). Do not poll, sleep, monitor, or check
pending. - The subagent's reply is delivered to you later as a 📨 intercom notification, which starts a new turn automatically — you handle the result there (report to the user, continue, or stop).
⚠️ The trap that has caused multi-minute flailing loops: a send reply is not a pending ask. intercom pending will forever say "No unresolved inbound asks", and the 📨 notification only delivers once your turn stops calling tools. So if you send and then try to wait for the reply in the same turn (polling pending, sleep, monitor, jobs, wait), you'll never see it and you'll spin for minutes. Send and stop. The notification will wake you.
Do NOT use intercom ask for the handoff — it blocks the caller up to 10 min, defeating the async goal. Use ask only in the rare case where you deliberately want to synchronize/block on the subagent.
What's verified vs. what doesn't work
Verified working:
cmux new-surface --type terminal --pane <ref> --focus false— creates a tab at far right, zero focus steal.cmux send --surface <ref> "cd ~/Source/<REPO>/main && pi\n"— launches the subagent (launch command only, not the task).cmux identify --json --id-format both | jq -r '.caller.pane_ref'— get the current pane ref.cmux tree— prints each surface assurface surface:N [terminal] "π - <agent-name> - <context>". The auto-generated agent name matches[a-z]+-[a-z]+-[0-9]+(e.g.tidal-grouse-50). This is the reliable way to read the subagent's name.- A bare
pisitting idle at its prompt does receive and act on an inbound intercomsend, and itssendreply is delivered to an idle caller as a 📨 notification that starts a new turn.
Does NOT work / common mistakes:
- Don't double-prefix the surface ref in grep.
cmux new-surfacereturnssurface:N(with thesurface:prefix); thecmux treeline issurface surface:N .... So grep for$NEW(→surface:N), never"surface:$NEW"(→surface:surface:N, matches nothing). This silently broke discovery in earlier versions. - Don't grep for the literal
πto detect the title — multibyte Unicode; has failed to match inside non-interactive shells. Match the agent-name pattern[a-z]+-[a-z]+-[0-9]+instead. pi --name <LABEL>//nameset only the session display name, not the agent-identity name intercom targets / that appears in the tab title. The agent-identity name is auto-generated and can't be set at launch. Don't rely on--name.cmux identify --jsoncan returncaller: nullwhen the skill runs from a pi bash tool (not a cmux-spawned shell) — the common case for an agent invoking the skill via a tool call. Read.caller.* // .focused.*, never.caller.*alone.cmux new-surfacedefaults--workspaceto$CMUX_WORKSPACE_ID, which is often unset in a bash-tool env →not_found: Workspace not found. Pass--workspaceexplicitly (fromfocused.workspace_ref).- Empty
$NEWis dangerous. Ifnew-surfacefails,$NEWis empty and the Step 2 pollgrep "$NEW"matches every tree line → you capture a stale, unrelated session's name andintercom sendthe task to the wrong agent. Always guard[ -z "$NEW" ] && abortright afternew-surface. intercom { action: "list" }is a useful confirmation, but don't trust its(id)to disambiguate (a duplicate-id display quirk has been seen). Match by name.- Both sides: respond with
send, notreply, after asend.intercom replyonly resolves a pendingask; using it to answer asendfails with"Reply target does not match a pending ask"/"No active intercom context to reply to". This bites the caller too, not just the subagent. cmux reorder-surface --focus false/cmux move-surface --focus false— steal focus despite the flag.
Rules:
- NEVER close surfaces.
- ALWAYS re-identify before acting — surface refs shift.
- Send the task over intercom, never by typing it into the tab with
cmux send. - After
send, end your turn. Do not wait for the reply in the same turn.
Step 1 — Launch the subagent (no task text)
J=$(cmux identify --json --id-format both)
PANE=$(echo "$J" | jq -r '.caller.pane_ref // .focused.pane_ref // empty')
WS=$(echo "$J" | jq -r '.caller.workspace_ref // .focused.workspace_ref // empty')
[ -z "$PANE" ] || [ -z "$WS" ] && { echo "abort: cmux identify returned no pane/workspace" >&2; exit 1; }
NEW=$(cmux new-surface --type terminal --pane "$PANE" --workspace "$WS" --focus false 2>&1 \
| grep -o 'surface:[0-9]*' | head -1)
[ -z "$NEW" ] && { echo "abort: new-surface failed" >&2; exit 1; }
cmux send --surface "$NEW" $'cd ~/Source/<REPO>/main && pi\n'
Replace <REPO>. Do not append the task here, and don't bother with --name (it won't change the intercom target). Keep $NEW — you'll use it to read the subagent's name.
Why this exact form (each line matters):
cmux identifycan returncaller: null(bash-tool env). Fall backcaller // focusedso the pane/workspace resolve regardless.--workspaceis passed explicitly becausenew-surfaceotherwise defaults to$CMUX_WORKSPACE_ID, which is often unset in a bash-tool env →not_found: Workspace not found.- The
[ -z "$NEW" ] && abortguard is critical: without it, a failednew-surfaceleaves$NEWempty, and Step 2'sgrep "$NEW"matches every tree line → you'd capture an unrelated session's name and send the task to the wrong agent.
Step 2 — Read the subagent's agent name from its tab title
Pi sets the new surface's title to π - <agent-name> - <context> within ~1–2s of booting. Poll for the agent-name pattern (π-independent, locale-safe), bounded so it can't hang:
SUBAGENT=""
for i in $(seq 1 60); do # max ~30s
SUBAGENT=$(cmux tree 2>/dev/null | grep "$NEW" | grep -oE '[a-z]+-[a-z]+-[0-9]+' | head -1)
[ -n "$SUBAGENT" ] && break
sleep 0.5
done
echo "$SUBAGENT" # e.g. tidal-grouse-50
This grabs the first word-word-digits token on the surface's line — the auto-generated agent-identity name. (Won't match a human-named session like smoke fix, but a bare pi launch always gets an auto-name.)
If $SUBAGENT is still empty after the loop, fall back to intercom list and find the live (not 💤 revivable), non-[self] entry whose cwd is the worktree you launched into; take its name (not id).
Step 3 — Hand off the task asynchronously (send, then stop)
intercom({
action: "send", // fire-and-forget, returns instantly
to: "<SUBAGENT>", // the name from Step 2
message: [
"<TASK details here>",
"",
"When done, reply over intercom to <CALLER-NAME> (your caller) with a short summary:",
'intercom({ action: "send", to: "<CALLER-NAME>", message: "..." })'
].join("\n")
});
// STOP. End your turn here. Do NOT poll/sleep/monitor/check pending.
// The reply will arrive as a 📨 notification that starts a new turn.
<CALLER-NAME> is your own agent name (the subagent also sees it as the sender). After this call, yield — return to the user, go idle, or do other work. You are not blocked.
Step 4 — When the 📨 reply arrives (a new turn)
The subagent used send, so there is no pending ask:
- Respond to the subagent with
intercom send, notintercom reply. There's no pending ask to resolve, soreplyfails with"Reply target does not match a pending ask"/"No active intercom context to reply to". This applies to the caller just as much as the subagent —replyonly works to resolve anask. - Just read the result from the 📨 notification and act on it (report to the user, continue your work, or stop per the user's original instruction).
- Optionally
intercom({ action: "send", to: "<SUBAGENT>", message: "got it, thanks" })a brief acknowledgment.
Step 5 — How the subagent replies
On receiving the caller's send, the subagent does the work, then sends the result back — intercom send, not reply (there's no pending ask to resolve):
intercom({ action: "send", to: "<CALLER-NAME>", message: "<TASK> complete. <summary>" })
This delivers to the caller as a 📨 notification (Step 4).
Optional — Report the tab name to the user
cmux tree | grep "$NEW" | sed 's/.*"\(.*\)".*/\1/'
# → π - <agent-name> - <context>
Repo Detection
Repos live under ~/Source as bare repos with worktrees. Detect the repo name from the current working directory:
# if CWD is under ~/Source/<reponame>/<branch>
REPO=$(pwd | sed -n 's|.*/Source/\([^/]*\).*|\1|p')
# or from git remote:
# REPO=$(git remote get-url origin | sed 's|.*/||; s|\.git$||')
The main worktree is at ~/Source/<REPO>/main.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.
Gives 0 of the 12 instructions most context ai engineering skills give in ~2.8k tokens
Counted across 1,193 of the 1,976 authors here whose files we hold, read 2026-08-07
- Dispatch a fresh implementer subagent per taskin 48 of 1193, across 19 files
- Dispatch a final code reviewer after all tasksin 33 of 1193, across 8 files
- Provide full task text to the subagentin 30 of 1193, across 9 files
- Review spec compliance before code qualityin 27 of 1193, across 10 files
- Make the hook script executablein 26 of 1193, across 8 files
- Re-snapshot after navigation or DOM changesin 25 of 1193, across 19 files
- Read files before editing themin 22 of 1193, across 11 files
- Answer subagent questions before proceedingin 22 of 1193, across 7 files
- Mark task complete in TodoWrite after approvalin 22 of 1193, across 6 files
- Merge hook into existing settingsin 21 of 1193, across 3 files
- Ask if installation is global or projectin 20 of 1193, across 2 files
- Copy the hook script to target locationin 20 of 1193, across 2 files
Said here and by no other author read
- create terminal tab in current pane
- launch bare subagent in new tab
- guard against empty new surface identifier
- read subagent name from tab title
- send task over intercom
- end turn after sending task
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.