cop
Delegate coding tasks to a GitHub Copilot CLI
agent running in a Herdr pane, and collect the result later. cop
drives herdr directly (it's a plain subprocess call) — it doesn't need the /herdr
skill or any other intermediary to submit a task or collect its result; it's just as
happy called from a script or by hand as from a Claude session that happens to be
using the /herdr skill for other things.
The pattern is deliberately two steps, not one blocking call:
cop start "Add unit tests for src/foo.py" --dir ~/r/myrepo
# -> prints a job id immediately; the Copilot agent keeps working in its own pane
# (task can also come from stdin for a quick throwaway job, e.g.
# echo "fix the typo in README" | cop start --dir ~/r/myrepo)
# ... do other things, or poll ...
cop collect <job-id> --wait
# -> blocks until the agent settles, then prints/stores its response
Requirements
- Must run inside a Herdr-managed pane (
HERDR_ENV=1) — it drives theherdrCLI directly over the local socket API. herdrandcopilot(GitHub Copilot CLI) onPATH.
Install
uv tool install cop-pilot
or with pip:
pip install cop-pilot
Straight from GitHub:
uv tool install git+https://github.com/vivainio/cop-pilot
For local development:
uv sync
Commands
cop start [<task>] --dir <path> [--name <mnemonic>]— opens a new tab in a dedicatedcop-tasksworkspace (created on first use; unfocused, so it never steals the caller's view), starts acopilotagent there, sends the task, and returns a job id as soon as the agent confirms it started working (it does not wait for the task to finish). The agent is namedcop-<hint>, where<hint>is--nameif given, else the directory's name (e.g.--name unit-tests→cop-unit-tests) — readable inherdr agent list/ Herdr's agents panel instead of a bare hex id. The job id is only appended (cop-unit-tests-<job-id>) if that plain name is already in use by another live agent. Trust and permission handling (see below) are unconditional. All delegated jobs land in that one workspace, one tab per job, so they never clutter whatever window/tab the caller is in. Herdr workspaces are flat (no parent/child relationship, confirmed viaherdr api schema), so this can't be nested "under" the caller's own workspace the way a git worktree nests under its parent repo — a separate workspace is the closest available primitive.cop collect <job-id> [--wait] [--timeout MS] [--raw]— without--wait, does a non-blocking status check; with--wait, blocks until the agent reachesidle,done, orblocked, then reads back its terminal output as the result. By default the pane read is stripped down to just the last turn (see below); pass--rawfor the full dump.cop respond <job-id> "<text>" [--raw]— send a follow-up into a job's agent, e.g. to answer a permission/approval dialog that left itblocked.cop show <job-id> [--refresh]— full detail for one job.cop list— table of all known jobs, cached status (no herdr calls, works offline).cop status— table of all known jobs, refreshing every non-terminal job's live status from herdr first.
Every command accepts --json for machine-readable output (useful when another agent
is the caller).
Jobs are flat JSON files under ~/.cop/jobs (override with COP_HOME).
Why start always does three things
Herdr's agent-lifecycle detection reports a Copilot pane as idle/ready-for-input
based on the screen looking settled — it has no idea what's actually on screen. Three
of Copilot's own startup/runtime screens look exactly like that to herdr, but aren't a
normal chat prompt, so a task sent into one of them silently goes nowhere (no error,
no state change, the job just never starts). start unconditionally works around
all three:
- Tool/approval prompts — launches
copilotwith--allow-all-tools --no-ask-userso it never pauses for a permission or clarifying-question prompt. (Copilot's AI-judged--assisted-approvalmode can't be used here — it's documented as prompt-mode (-p) only and refuses to start in the persistent interactive session herdr needs.) - Folder trust — a never-before-seen
--dirshows an interactive "Confirm folder trust" dialog at startup, before anything else. No CLI flag suppresses it (checked--add-dir,--allow-all-tools,--allow-all) — the only way to skip it is to already be listed in Copilot's owntrustedFoldersconfig, sostartpre-seeds--dirinto~/.copilot/config.json(textually, leaving the rest of that file — including a live GitHub token — untouched). - Session restore — if a prior Copilot session in the same
--dirwas left "Interrupted" (e.g. its pane got closed mid-task), the next launch shows a "restore interrupted sessions" picker instead of a normal chat prompt. A fresh--session-idper job skips it.
start also confirms the prompt actually landed (waits for the agent to reach
working, retrying a couple of times on agent_prompt_stalled) instead of firing
blind — agent_start reporting ready can race a beat ahead of the TUI actually
accepting input.
Result extraction and the full session log
start gives each job its own --session-id. Copilot writes a full structured event
log for it to ~/.copilot/session-state/<session-id>/events.jsonl (one JSON object
per line: session.start, user.message, model.response, tool calls, ...) — shown
as session_file in every job. By default, collect/respond read the answer
straight from there: the last assistant.message event with phase: "final_answer"
has a plain content string, no chrome to parse around.
If the session file is missing, unparseable, or has no final answer yet (e.g. blocked
mid-turn), they fall back to scraping the pane — a full-screen dump (startup banner,
nav bar, tips, the echoed prompt in its own box, the turn, a status bar, an empty
input box) stripped down to just the last turn's content. Pass --raw to get that
raw pane dump directly, bypassing both.
session_file itself is worth keeping around regardless — it's the entire session,
queryable, for anything a single extracted answer can't show.
Development
uv sync # install with dev dependencies
uv run pytest tests/ -v # run tests
uv run ruff format . # format
uv run ruff check . # lint
uv run ty check # type check
hookmaster init . installs a pre-commit hook (githooks.toml) that runs
ruff format --check ..
Known limitations
agent readonly sees what's on Copilot's alternate screen / Herdr's scrollback of it; a very long response can be truncated. If that happens, ask the task (in a follow-up viarespond) to write its full answer to a file and reply with just the path, then read the file directly.- One job = one fresh pane + agent. There's no session reuse/continuation across jobs
yet (each
startbegins a brand new Copilot session).
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 cop_pilot-0.1.0.tar.gz.
File metadata
- Download URL: cop_pilot-0.1.0.tar.gz
- Upload date:
- Size: 21.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
669fd3a683c623fad6f03ca00ff2e340ac6725dc66422540e58db475811faec9
|
|
| MD5 |
0da9947716fdae09e6105f4f705aa35a
|
|
| BLAKE2b-256 |
0c6b0f6449994d9152f86096a1cd1257c486faf8991b5c416d59671b4cd0cd97
|
Provenance
The following attestation bundles were made for cop_pilot-0.1.0.tar.gz:
Publisher:
publish.yml on vivainio/cop-pilot
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cop_pilot-0.1.0.tar.gz -
Subject digest:
669fd3a683c623fad6f03ca00ff2e340ac6725dc66422540e58db475811faec9 - Sigstore transparency entry: 2690204908
- Sigstore integration time:
-
Permalink:
vivainio/cop-pilot@f1cb78740a441fdb536e6c03e489503cd301dcde -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/vivainio
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f1cb78740a441fdb536e6c03e489503cd301dcde -
Trigger Event:
release
-
Statement type:
File details
Details for the file cop_pilot-0.1.0-py3-none-any.whl.
File metadata
- Download URL: cop_pilot-0.1.0-py3-none-any.whl
- Upload date:
- Size: 17.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d43895b80a389c7de163e8eeecfa75f22c84b82b8726af6dbc5b06c512f31a11
|
|
| MD5 |
bb7aa05f99b24af6e66397ba6f358e5c
|
|
| BLAKE2b-256 |
f70b14488a4124e8c6d9ef6698cc61cda944bc269c6d0e34b614338647d4f9e7
|
Provenance
The following attestation bundles were made for cop_pilot-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on vivainio/cop-pilot
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cop_pilot-0.1.0-py3-none-any.whl -
Subject digest:
d43895b80a389c7de163e8eeecfa75f22c84b82b8726af6dbc5b06c512f31a11 - Sigstore transparency entry: 2690204959
- Sigstore integration time:
-
Permalink:
vivainio/cop-pilot@f1cb78740a441fdb536e6c03e489503cd301dcde -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/vivainio
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f1cb78740a441fdb536e6c03e489503cd301dcde -
Trigger Event:
release
-
Statement type: