Multiplexer for GitHub Copilot CLI agents
Project description
cpmux
A declarative multiplexer for GitHub Copilot CLI agents: "tmuxinator for copilot sessions."
Write one YAML file with a shared system prompt and a task list. cpmux starts one headless
copilot session per task, each in its own git worktree and branch, and opens a draft PR.
Monitor and steer all sessions.
Install
Requires macOS or Linux, Python ≥ 3.12, and the copilot,
git, and gh CLIs on your PATH.
pip install git+https://github.com/gugarosa/cpmux
Or from source, for development:
git clone https://github.com/gugarosa/cpmux
cd cpmux
pip install -e .
Quickstart
From the root of the GitHub repository you want to change, create cpmux.yml (or run
cpmux init for a starter):
system: |
Make the smallest change that fully addresses the task.
Follow the repository's conventions and add or update tests.
items:
- Fix the broken install link in the README
- name: pagination-regression
prompt: Add a regression test for the pagination helper.
Preview the plan, start the sessions in the background, and watch the run:
cpmux up --dry-run # preview the resolved plan
cpmux up --yes # start the run in the background
cpmux attach # watch it (Ctrl-C stops watching, not the run)
By default, cpmux up starts the run in the background and opens one draft PR per item.
Pass --foreground to stay attached and watch inline (Ctrl-C then stops the run).
Each PR's title and description are written by the session itself from the changes it made, following the target repository's pull-request template when one exists. If a session doesn't produce one, cpmux falls back to the item name and prompt.
The cpmux file
A file has a shared system prompt, run-wide defaults, and items. Each item is either a
prompt string or a mapping:
system: |
Make the smallest change that fully fixes the issue, follow the surrounding
conventions, and add or update a test.
defaults:
model: gpt-5.5 # any `copilot --model` id
effort: medium # none | minimal | low | medium | high | xhigh | max
permissions: edit # readonly | edit | full (yolo)
base: main # branch to fork from and open PRs against
remote: origin # git remote to push branches and open PRs on
branch_template: cpmux/{slug} # each item's branch name; {slug} or {id}, e.g. gderosa/{slug}
concurrency: 6 # max sessions running at once (1–64)
deps: symlink # seed a worktree's node_modules: symlink | copy | install | skip
port_base: 3000 # give each item a unique port (3000, 3001, …) via $PORT
port_env: PORT # rename the port variable (default $PORT)
pr:
draft: true
labels: [cpmux]
items:
- Fix the flaky login test # bare string → key is the slug "fix-the-flaky-login-test"
- Paginate the notifications list
- name: dark-mode-contrast # mapping → key is the slug of `name`
prompt: Fix the dark-mode contrast on secondary buttons; it fails WCAG AA.
model: claude-opus-4.8
effort: high
paths: [src/components/buttons]
labels: [a11y]
depends_on: [fix-the-flaky-login-test]
Item mappings accept prompt, name, id, model, effort, permissions, base,
branch, labels, draft, paths, depends_on, env, and include_system.
The pr block also accepts title_template and body_template — the fallback title and body
used when a session writes no .cpmux-pr.md; both expand {name}, {slug}, and {prompt},
while branch_template expands {slug} and {id}. permissions may be a bare preset or a
mapping that extends one —
{preset: edit, allow: [...], deny: [...], add_dir: [...], allow_url: [...]} — adding
copilot tool and network rules. Only full/yolo let the agent run git push itself; the
other presets keep push denied so cpmux owns delivery. An optional top-level version selects
the schema (currently only 1).
An item's key is its id when set, otherwise a slug of its name or prompt. Pass keys
to enter, send, logs, and kill; cpmux ls and --dry-run print them. Any string field
expands ${VAR} and ${VAR:-default} from the environment. Set include_system: false to
omit the shared prompt for an item.
Set port_base when items run dev servers: each item gets port_base + index in its
environment (as $PORT, or port_env to rename it), so parallel servers do not collide. An
item's own env takes precedence, and env values reach the session's subprocess.
Commands
Run-scoped commands accept --run <id> and default to the latest run.
| Group | Command | What it does |
|---|---|---|
| Create | cpmux init [FILE] |
Write a starter plan (defaults to cpmux.yml). Flag: --force/-f. |
cpmux plan [FILE] |
Compose a plan in your editor, or from text, speech, or audio. Flags: --text, --voice, --audio (mutually exclusive), --transcribe-model, --model, --force/-f, --up, --pr/--no-pr, --detach/--foreground/-d, --yes/-y. |
|
| Launch | cpmux up [FILE] |
Spawn one session per item (defaults to cpmux.yml). Flags: --dry-run, --detach/--foreground/-d/-f (background by default), --concurrency/-j, --pr/--no-pr, --deps, --strip-github-token/--no-strip-github-token, --yes/-y. |
| Monitor | cpmux ls |
Snapshot each item's status, elapsed time, and activity. |
cpmux attach |
Live, read-only monitor; reconnects to a background run (Ctrl-C to detach). | |
cpmux dash |
Interactive TUI: session list, live transcript, search. | |
cpmux logs KEY |
Print a transcript; --follow/-f to stream, --raw for the JSONL. |
|
cpmux search QUERY |
Search across transcripts; --all for every run, --regex, --fts to rank via Copilot's index. |
|
| Steer | cpmux enter KEY |
Drop into an interactive copilot session, resumed in place. |
cpmux send KEY "…" |
Append a follow-up turn and print the reply. | |
cpmux kill KEY |
Stop one running session. Flag: --yes/-y. |
|
| Teardown | cpmux down |
Stop a run's background daemon and any live sessions. Flag: --yes/-y. |
cpmux rm |
Remove the run's git worktrees. Flags: --yes/-y, --force/-f (delete uncommitted work), --purge (also delete run history). |
Composing a plan
Compose a cpmux file in your editor by default, or from text, speech, or audio:
cpmux plan issues.yml # compose in $EDITOR → cpmux file
cpmux plan issues.yml --text "fix the flaky login test and paginate the notifications"
cpmux plan issues.yml --voice # record from the mic (Enter to stop) instead
cpmux plan issues.yml --audio memo.wav # transcribe an existing recording instead
cpmux plan issues.yml --up # generate and launch it
cpmux plan opens your $EDITOR to describe the work (or takes --text), then asks copilot
to produce a validated cpmux file. Add --up to launch it. With --voice or --audio,
faster-whisper transcribes speech on-device.
Audio stays local. --voice shows a live transcript as you speak (a fast model streams
partials while recording; your chosen model produces the accurate final text on stop).
The cpmux[voice] extra installs sounddevice and faster-whisper. --text and the editor
need neither:
pip install "cpmux[voice] @ git+https://github.com/gugarosa/cpmux"
brew install portaudio # macOS only: sounddevice needs PortAudio
The default transcription model is large-v3-turbo (near-large-v3 accuracy, much faster
decoding). It downloads on first use (~1.6 GB) and is cached. Pick a lighter one with
--transcribe-model (for example, small, distil-large-v3, or base); larger models are
more accurate but slower on CPU.
How it works
- One item, one session. Each task becomes a headless
copilot -prun with a pre-assigned--session-id. - Separate worktrees. Each session runs in its own
git worktreeon acpmux/<slug>branch offorigin/<base>. - cpmux owns delivery. Sessions run with
git pushdenied. cpmux commits each worktree and opens one draft PR per item. With--no-pr, it commits locally and stops. - JSONL monitoring. cpmux reads copilot's
--output-format jsonevent stream and writes it to disk. Runs continue after detach and can be reattached. Crashed sessions resolve to a terminal state.
issues.yaml ──cpmux up──► session fix-login-test → worktree ─ branch ─ draft PR
system: … session paginate-list → worktree ─ branch ─ draft PR
items: … ───────────►session dark-mode-contrast→ worktree ─ branch ─ draft PR
session … (parallel · isolated)
│
monitor and steer: cpmux attach · dash · ls · logs · search
What a run leaves on disk
cpmux writes under a gitignored .cpmux/:
.cpmux/
runs/<run_id>/
manifest.json resolved run config
sessions/<key>/
prompt.md the exact prompt sent (system + item)
transcript.jsonl raw tee of copilot --output-format json
session.json per-session record (status, branch, PR url, …)
copilot-logs/ copilot's own --log-dir
worktrees/<run_id>/<key>/ one git worktree per item
Examples
See examples/minimal.yaml and a twelve-issue frontend run
in examples/frontend.yaml.
Development
pip install -e .
pytest
Conventions, architecture invariants, and roadmap live in CONVENTIONS.md.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cpmux-0.1.0.tar.gz.
File metadata
- Download URL: cpmux-0.1.0.tar.gz
- Upload date:
- Size: 64.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
641dc6e89e30207af48e8e93ec192dc16065a5f03fc62b1c6bf1fea7801c815f
|
|
| MD5 |
7c152a674d5afb807547917e00a8f731
|
|
| BLAKE2b-256 |
866e7050033fbe9c2830d49ed1b01dd21a5e0e5928739c0ffe2454318ee9a705
|
Provenance
The following attestation bundles were made for cpmux-0.1.0.tar.gz:
Publisher:
release.yml on gugarosa/cpmux
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cpmux-0.1.0.tar.gz -
Subject digest:
641dc6e89e30207af48e8e93ec192dc16065a5f03fc62b1c6bf1fea7801c815f - Sigstore transparency entry: 2174232800
- Sigstore integration time:
-
Permalink:
gugarosa/cpmux@494298017b3e693d5867e7d9e67d7b037f4fca17 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/gugarosa
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@494298017b3e693d5867e7d9e67d7b037f4fca17 -
Trigger Event:
push
-
Statement type:
File details
Details for the file cpmux-0.1.0-py3-none-any.whl.
File metadata
- Download URL: cpmux-0.1.0-py3-none-any.whl
- Upload date:
- Size: 53.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
83b7338604d9df6c3ff9364d67718f00c77465997e65fa617d64ea0006026921
|
|
| MD5 |
33b420879bba401f1d78fc8bde17fab7
|
|
| BLAKE2b-256 |
8d1ad750efc7b8f6000ed7bdb465917a03d4f6be9bbf7a513836b2998c4c40a5
|
Provenance
The following attestation bundles were made for cpmux-0.1.0-py3-none-any.whl:
Publisher:
release.yml on gugarosa/cpmux
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cpmux-0.1.0-py3-none-any.whl -
Subject digest:
83b7338604d9df6c3ff9364d67718f00c77465997e65fa617d64ea0006026921 - Sigstore transparency entry: 2174232832
- Sigstore integration time:
-
Permalink:
gugarosa/cpmux@494298017b3e693d5867e7d9e67d7b037f4fca17 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/gugarosa
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@494298017b3e693d5867e7d9e67d7b037f4fca17 -
Trigger Event:
push
-
Statement type: