Skip to main content

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 -p run with a pre-assigned --session-id.
  • Separate worktrees. Each session runs in its own git worktree on a cpmux/<slug> branch off origin/<base>.
  • cpmux owns delivery. Sessions run with git push denied. 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 json event 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cpmux-0.1.0.tar.gz (64.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cpmux-0.1.0-py3-none-any.whl (53.5 kB view details)

Uploaded Python 3

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

Hashes for cpmux-0.1.0.tar.gz
Algorithm Hash digest
SHA256 641dc6e89e30207af48e8e93ec192dc16065a5f03fc62b1c6bf1fea7801c815f
MD5 7c152a674d5afb807547917e00a8f731
BLAKE2b-256 866e7050033fbe9c2830d49ed1b01dd21a5e0e5928739c0ffe2454318ee9a705

See more details on using hashes here.

Provenance

The following attestation bundles were made for cpmux-0.1.0.tar.gz:

Publisher: release.yml on gugarosa/cpmux

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for cpmux-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 83b7338604d9df6c3ff9364d67718f00c77465997e65fa617d64ea0006026921
MD5 33b420879bba401f1d78fc8bde17fab7
BLAKE2b-256 8d1ad750efc7b8f6000ed7bdb465917a03d4f6be9bbf7a513836b2998c4c40a5

See more details on using hashes here.

Provenance

The following attestation bundles were made for cpmux-0.1.0-py3-none-any.whl:

Publisher: release.yml on gugarosa/cpmux

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page