Tools, skills, and workflow conventions for GitHub Copilot CLI power users — autonomous loop mode, multi-instance session management, agent-to-agent messaging, and spec-driven workflow conventions.
An independent personal project. Not affiliated with, endorsed by, or supported by GitHub or Microsoft.
| Component | Description |
|---|---|
copilot_operator.py |
Cross-platform Copilot CLI wrapper with metrics capture, autonomous loop mode, and multi-instance support |
operator_runner.py |
In-pane session supervisor: correct process attribution and metrics after detach |
operator_mux.py |
Session-backend abstraction (tmux / psmux) |
work_claims.py |
One work item, one owner: the claim store behind operator work |
operator_liveness.py |
Whether a claim's owner is provably gone: LIVE / DEAD / STALE |
operator_session.py |
The session lifecycle: the assignment resolved on the way in, the handoff and claim disposal on the way out |
operator_work.py |
The policy over the claim store: operator work, and the reclaim that preserves a departed owner's work first |
operator_worktree.py |
The checkout a claim owns: operator worktree new / finish / recover |
operator_ownership.py |
Whether a branch stayed inside its subproject: operator ownership check |
operator_ingest.py |
Pure-Python log parser for copilot process logs |
handoff_tool.py |
Atomic session handoff for agents |
backlog_tool.py |
Reads and enforces the tracked backlog/; ships the backlog command |
operator.sh, handoff.sh, operator-ingest.py |
Original bash implementation, retained on disk for rollback but no longer installed fresh by setup.sh |
skills/code-intelligence |
Roslyn-backed C# structural analysis |
skills/peer-agents |
Starting peer operator agents and messaging them |
skills/worktrees |
Worktree lifecycle, scratch discipline, safe delegation |
skills/backlog |
The tracked backlog format, the approval gate, and evidence |
skills/spec-driven |
The spec-kit workflow and when a change needs a spec update |
skills/field-notes |
The cross-project journal about working with AI agents |
skills/operator-backlog-* |
Filing, refining and checking in on the tracked backlog |
docs/rationale.md |
Why the rules exist — the incidents behind them. Linked, never loaded |
operator_mail.py |
Message store behind operator send / operator inbox |
conversation_log.py |
One record of everything said to an agent and back: seeds from the session store, the mail store and the capture spool |
conversation_viewer.py |
Loopback web viewer for that record — filter and search by project, day, speaker |
operator_trace.py |
Records who invoked the operator and how each invocation ended, for attributing incidents |
install_manifest.py |
Records what setup deployed and its hash, so upgrades know what's safe to replace |
mail_affiliation.py |
Which project each end of an operator message belongs to — recorded, never enforced |
extensions/ |
Copilot CLI runtime extensions: open-in-vs-code, lint-on-edit, security-shield, test-enforcer, architecture-enforcer, checkout-guard, copy-to-clipboard-tool, conversation-capture |
templates/ |
Configuration templates for copilot-instructions, MCP servers, and per-project setup |
docs/ |
Documentation for operator, skills, versioning, and spec-kit |
setup.sh |
Linux/WSL/macOS setup: migrates any legacy bash install to Python, then delegates to setup_tools.py |
setup.ps1 |
Windows setup: locates or installs Python 3.10+, then delegates to setup_tools.py |
| Component | Windows | Linux / WSL | macOS |
|---|---|---|---|
| Workflow conventions & templates | ✅ | ✅ | ✅ |
| Spec Kit workflow | ✅ | ✅ | ✅ |
| Runtime extensions | ✅ | ✅ | ✅ |
operator / handoff (Python) |
✅ | ✅ | ✅ |
operator.sh / handoff.sh (bash, legacy — rollback target, still tested) |
❌ | rollback only | rollback only |
operator restore (Windows Terminal tabs) |
✅ (native tabs) | ✅ (WSL-hosted tabs only) | ❌ |
The Python implementation is the supported entry point on every platform.
setup.sh migrates existing Linux/WSL/macOS installs off the bash scripts
automatically (see Quick Start); the bash scripts themselves
are left on disk, unmodified by setup, purely so a failed migration can never
strand a user without a working operator/handoff command.
Here "legacy" means superseded, not abandoned. A rollback target is only worth
having if it still runs, so the bash scripts stay under test: CI's "Shell
script syntax" job parses every *.sh with bash -n on every pull request
and every push to main, and the
suite exercises operator.sh and handoff.sh directly —
tests/test_operator_sh_bash32.py, tests/test_handoff_sh.py,
tests/test_operator_sh_entrypoint.py, tests/test_operator_sh_help.py and
tests/test_shell_bash32_conformance.py
all read or run them, the last of those because macOS /bin/bash is
permanently 3.2 and macOS is exactly where someone is most likely to need the
fallback. They still get bug fixes; they get no new features, and a divergence
in behaviour is resolved by changing the bash to match the Python. Don't
install them on purpose — see rolling
back for the one supported reason to.
tests/test_legacy_bash_status.py fails if this paragraph and that coverage
ever stop agreeing.
operator restore re-launches tracked Windows Terminal tabs. It works from
both native Windows PowerShell and from inside WSL (via wt.exe/wsl.exe
interop), but a restore invoked from WSL only sees tabs tracked by that WSL
distro and its siblings, not tabs tracked by a native Windows session, and
vice versa — the two sides don't share a tab registry.
Session management uses a terminal multiplexer: psmux on Windows, tmux elsewhere. See Operator for details.
PowerShell (Windows)
git clone https://github.com/darinh/copilot-tools.git $HOME\repos\copilot-tools
cd $HOME\repos\copilot-tools
./setup.ps1bash (Linux/macOS/WSL)
git clone https://github.com/darinh/copilot-tools.git ~/projects/copilot-tools
cd ~/projects/copilot-tools
chmod +x setup.sh
./setup.shSetup installs what's missing rather than telling you to go install it.
Both scripts locate a Python 3.10+ interpreter — installing one via winget
or your distro's package manager if the machine has none — and delegate to
setup_tools.py, which is itself cross-platform and idempotent. It will:
- Install any missing prerequisites: a terminal multiplexer (psmux via
winget, or its GitHub release as a fallback / tmux viaapt-get,dnf,pacman,zypper,apk, orbrew),git, and thecopilotCLI (vianpm, pulling in Node.js first if needed) - Install the
operator,handoffandoperator-ingestconsole scripts - Link runtime extensions into
~/.copilot/extensions/ - Install configuration templates to
~/.copilot/ - Install the Anvil plugin, the Spec Kit CLI (
specify, viauv), and optionallydotnet-roslyn-mcp
Anything installed to a directory that isn't on PATH yet (npm's global bin,
~/.local/bin, the psmux download) is added to your user PATH, so new
shells pick it up.
Only a genuinely unautomatable failure — no package manager at all, or an install that errors — stops setup, and it prints the exact manual command.
Useful flags: --yes (assume yes to overwrite prompts), --status (report
what's installed and whether an update is needed, changing nothing),
--check-only (report missing prerequisites and change nothing),
--no-install-prereqs (old check-and-bail behavior), --skip-optional (skip
Anvil, spec-kit, and the MCP servers), --skip-package (skip
pip install -e .).
Setup records what it deployed in ~/.operator/install-manifest.json, so a
later run can tell "the repository moved on and you never touched your copy"
(update silently) from "you customised this" (ask first). After pulling on
another machine, python setup_tools.py --status answers whether you need to
re-run setup. See Versioning.
git pull # or the clone above, on a machine that has none
./setup.ps1 # ./setup.sh on Linux/macOS/WSL — idempotent
Re-run setup after every pull. A pull updates the checkout; it does not
touch ~/.copilot/, and it cannot repair the installed package. Two distinct
failures follow from skipping it:
- A new extension or skill loads nowhere. It sits in the repository and nothing announces the gap, because an extension that never loaded cannot report its own absence.
- A new Python module is not importable at all. An editable install is not
a symlink — setuptools writes a finder holding a static table of module
name to path, built at install time.
git pullupdates the source of every module already in that table and cannot add one. The result is a correct checkout, a broken install, andModuleNotFoundErrorfrom a command that worked yesterday.
python setup_tools.py --status reports both: the installed version, every
deployed artifact with its state, and whether the installed package still
imports. The import check runs in an isolated subprocess from a temporary
directory, so it answers a question about the install rather than about the
directory the command was run from — from inside the checkout, every module
resolves from the current directory and the answer would always be yes.
Do not verify with operator --version: it prints a hardcoded literal
that has not moved since July, so it reports the same number whatever you
deployed (backlog item 0027).
Two things are per machine and are not deployed by git:
- Experimental mode. Extensions load only in experimental sessions, and a session without it loads none of them silently. See Experimental default.
- The conversation store.
~/.operator/conversations.dbholds what was said on that machine and is deliberately not in the repository. Runoperator conversations seedthere; it is idempotent, and re-running it also re-applies corrected classification rules to rows already stored.
You can also invoke python setup_tools.py / python3 setup_tools.py
directly if you don't need the migration step below.
sqlite3 is not required — the toolkit uses Python's standard-library
sqlite3 module.
pip install -e copies nothing. It writes the source directory into the
interpreter's import path and points the operator and handoff console
scripts at it, machine-wide for that interpreter. A git worktree exists in
order to be deleted, so an editable install made from one breaks every console
script the moment somebody else correctly finishes that branch and runs git worktree remove — and it breaks for them, days later, with no path back to
the cause. That has happened twice here.
So the project's PEP 517 backend (worktree_guard_backend.py) refuses to
build an editable install when the source directory is a linked worktree, and
tells you which checkout to install from instead. Wheels and sdists are
unaffected, submodules are allowed, and
COPILOT_TOOLS_ALLOW_WORKTREE_INSTALL=1 overrides the refusal if you really
mean it. setup_tools.py run from a worktree redirects to the primary
checkout on its own and says so.
Repairing a machine that already has one: reinstall from the primary checkout, naming it explicitly rather than relying on the working directory.
python -m pip install -e /path/to/the/primary/checkout --no-depsWhat setup.sh does beyond setup_tools.py (Linux/WSL/macOS)
If a previous run of the original bash installer left an operator and/or
handoff symlink in ~/.local/bin pointing at this checkout's
operator.sh/handoff.sh, setup.sh sets those symlinks aside, runs the
Python install, and confirms operator/handoff resolve to the new
console scripts before deleting the old symlinks. If anything else occupies
those paths instead — a symlink pointing elsewhere, a different checkout, or
an unrelated command with the same name — it's moved aside the same way but
never auto-deleted, since pip install -e . would otherwise silently
overwrite it with no backup of its own; look for a
~/.local/bin/{operator,handoff}.copilot-tools-preexisting-bak file if that
happens to you. If the Python install fails, the new commands don't resolve
on PATH, or setup is interrupted (Ctrl-C) mid-install, everything is
restored automatically and setup.sh exits non-zero — you're never left
without a working command.
That migration is all setup.sh still does on its own. Anvil,
dotnet-roslyn-mcp, and the Spec Kit CLI used to be installed here, which
meant Windows users never got them; they're now provisioned by
setup_tools.py on every platform. operator.sh/handoff.sh themselves are
still on disk and still maintained (see Platform
Support); they're just no longer the thing installed into
PATH.
See Spec Kit Workflow for project initialization, commands, upgrades, and parallel-agent coordination.
# Interactive session with Anvil agent
operator --agent=anvil:anvil --yolo
# Autonomous loop (restarts when agent signals)
operator --loop --name myproject --agent=anvil:anvil
# Restart later — auto-continues from where it left off
operator --loop --name myproject --agent=anvil:anvil
# Reset session numbering
operator --loop --name myproject --fresh --agent=anvil:anvil
# Multiple concurrent loops
operator --loop --name frontend --agent=anvil:anvil
operator --loop --name backend --agent=anvil:anvil
operator list
# Start a loop without attaching, then message it
operator --loop --headless --name payments-api --agent=anvil:anvil
operator send --from backend --to payments-api "POST /charges returns {id, status}"
operator inbox backend
# Usage reports
operator report costs
# What did I ask, and what came back — across every project on this machine
operator conversations seed # idempotent; run it again whenever you like
operator conversations serve # opens http://127.0.0.1:8765/
# Per-project feature configuration (which conventions a project opted into)
operator projectsNamed loop instances persist the active Copilot CLI session ID, so restarting the same loop after a WSL crash or Windows reboot automatically resumes the prior CLI session.
On Windows, operator also tracks which Windows Terminal tabs are running named instances. After a reboot or crash, operator restore lists tracked tabs so you can pick which to reopen (or operator restore --all for everything), replaying each launch command in a fresh tab so every Copilot session resumes where it left off — no manual re-cd-ing or hunting for session IDs.
See Operator Documentation for full details.
| Skill | Type | Source |
|---|---|---|
| code-intelligence | User skill (installed to ~/.copilot/skills/) |
Included in this repo |
| operator-backlog-newitem | User skill (installed to ~/.copilot/skills/) |
Included in this repo |
| operator-backlog-refinement | User skill (installed to ~/.copilot/skills/) |
Included in this repo |
| operator-backlog-scrum | User skill (installed to ~/.copilot/skills/) |
Included in this repo |
| peer-agents | User skill (installed to ~/.copilot/skills/) |
Included in this repo |
| worktrees | User skill (installed to ~/.copilot/skills/) |
Included in this repo |
| backlog | User skill (installed to ~/.copilot/skills/) |
Included in this repo |
| spec-driven | User skill (installed to ~/.copilot/skills/) |
Included in this repo |
| field-notes | User skill (installed to ~/.copilot/skills/) |
Included in this repo |
| Anvil | Installable plugin | burkeholland/anvil |
| frontend-design | Built-in CLI skill | Ships with Copilot CLI |
| find-skills | Built-in CLI skill | Ships with Copilot CLI |
See Skills Reference for details and setup.
The templates/copilot-instructions.md file establishes conventions for:
- Project Configuration System — per-project settings stored in
~/.operator/projects/ - Session Handoff — cross-session continuity via
next-session.mdfiles; an unread handoff is archived tosuperseded/rather than overwritten, and that archive is never pruned - Session History — SQL-based audit trail of work across sessions
- Spec-Driven Development — specs as the single source of truth (enabled by default)
- Parallel Agents — SQL-coordinated parallel task execution via
todo_claims - Operator Agents — parallel peer agents in their own terminals, talking via
operator send/operator inbox - Branching Strategy — feature branches worked on in worktrees, merged to
main, conventional commits - Git Worktrees — all work happens in
<repoRoot>/.worktrees/; always on, not optional
Setup no longer copies that file to ~/.copilot/copilot-instructions.md. A
file at that path is read by every Copilot session on the machine, in every
directory, project or not — and its enrollment section then tells the agent to
resolve a project root, read the catalog, and offer to set the directory up.
Opening a terminal anywhere started a conversation about the project system.
Instead each registered project gets its own AGENTS.md, holding only the
sections its features turned on and naming its project id outright, so nothing
in it has a catalog to look up or a directory to enroll:
operator projects retire
That writes every project's AGENTS.md before it touches the global file,
archives the global file to ~/.operator/retired/ and verifies the copy by
reading it back, and only then removes it. One project that cannot be written
stops the removal — the failure mode is the conventions in two places, never in
none. A repository that already has an AGENTS.md is asked about and appended
to, never overwritten, and regenerating replaces only the block between the
copilot-tools managed conventions markers.
If you already keep an AGENTS.md at user scope, it is reported and left
alone: it is not this toolkit's file.
Open work in this repository lives in backlog/, under
version control — one Markdown file per item, with YAML front matter.
backlog list # one line per item
backlog ready # what an agent may work right now
backlog new --title "..." --evidence "..." # file an item, awaiting approval
backlog approve 3 # the owner's act: proposed -> open
backlog close 3 # shipped: records today and the SHA HEAD resolves to
backlog close 3 --reject # considered and declined; no commit, because none
backlog scrum # what changed since the last check-in
backlog check # validate every item; non-zero on failure
backlog html --open # a self-contained page, in a browser
Three skills drive those commands in conversation:
/operator-backlog-newitem files what surfaces mid-task,
/operator-backlog-refinement walks the queue with you, and
/operator-backlog-scrum reports what changed since you last looked.
It exists because closed work is answerable from git log and open work was
answerable from nothing: it lived in next-session.md, which is read-once and
deleted at session start, and was carried between sessions as one
re-summarised sentence. That carry-forward is lossy by construction and
nothing could detect the loss.
An agent files items as proposed, never open: filing is not approving, and
backlog ready lists only approved work, so the gate is enforced rather than
merely documented. The one exception is narrow and checked — an item carrying
blocks: <id> may be worked while the approved item it names is open, so a
defect found mid-task has a legal path instead of a stall or a self-approval.
Each item names a spec under specs/, or the literal none. Closing an item
means setting status, closed and commit in the same change that does the
work, and updating the linked spec there too — a close landing separately
from its fix is a window in which the backlog is wrong. In practice the close
is the branch's last commit, naming the SHA of the one before it; filling in a
SHA and then git commit --amend records an object the amend destroys.
backlog close writes that pair for you and enforces the same gate ready
does: it closes only an item an agent was allowed to work, so filing your own
item and marking it shipped is not a path. Declining one is --reject, which
is deliberately not gated on approval — the ordinary thing to decline is an
unapproved proposal — and writes no commit, because nothing shipped. Both
verbs are also reachable as operator backlog …, which is a delegation to
this same tool rather than a second implementation of it.
tests/test_backlog_conformance.py enforces the format, the spec mapping, and
that every closing SHA actually resolves; tests/test_backlog_workflow.py
covers filing, approval, closing and the check-in. See
backlog/README.md for the field reference.
An optional MCP server provides structural code intelligence:
| Server | Language | Install |
|---|---|---|
| dotnet-roslyn-mcp | C# | dotnet tool install -g dotnet-roslyn-mcp |
Configure in ~/.copilot/mcp-config.json — see templates/mcp-config.json.
Every entry at the repository root appears below;
tests/test_readme_structure.py fails if this
tree and git ls-files ever disagree.
copilot-tools/
├── .github/
│ ├── copilot-instructions.md # Conventions for agents working in this repo
│ ├── skills/speckit-*/ # Copilot skills generated by Spec Kit
│ └── workflows/ci.yml # 8 jobs: 3 OSes x 2 Pythons, shell syntax, extensions
├── .specify/ # Spec Kit templates, scripts, and constitution
├── AGENTS.md # This repo's own conventions, written by `operator projects`
├── CLAUDE.md # The same conventions for Claude Code; imports AGENTS.md
├── specs/ # Feature specifications, plans, and tasks
├── backlog/ # Open work, one file per item; see its README
├── .gitattributes # Shell scripts check out LF on every platform
├── .gitignore # Caches, packaging output, and /.worktrees/
├── FROZEN.md # This repo takes safety fixes only; the kernel moved out
├── LICENSE # MIT
├── README.md # This file
├── pyproject.toml # Packaging and console scripts
├── copilot_operator.py # Operator CLI (all platforms)
├── operator_runner.py # In-pane session supervisor
├── operator_mux.py # Session-backend abstraction (tmux / psmux)
├── operator_ingest.py # Pure-Python log parser
├── operator_mail.py # Agent-to-agent mail, live and queued delivery
├── conversation_log.py # What was said to agents and back: store and seeders
├── conversation_viewer.py # Loopback web viewer for that store
├── operator_trace.py # Who invoked the operator, and how it ended
├── operator_liveness.py # Is a claim's owner still there? LIVE / DEAD / STALE
├── operator_session.py # Session lifecycle: assignment in, handoff out
├── operator_work.py # `operator work`: request, release, list, heartbeat, reclaim
├── operator_worktree.py # `operator worktree`: new, finish, recover
├── operator_ownership.py # `operator ownership check`: did this branch leave its subproject?
├── operator_console.py # UTF-8 console output
├── project_paths.py # Project identity: catalog and per-project dirs
├── project_features.py # The feature vocabulary, and each project's choices
├── project_instructions.py # Renders each project's AGENTS.md; retires the global file
├── handoff_tool.py # Session handoff
├── work_claims.py # One work item, one owner: the claim store
├── backlog_tool.py # Backlog parser, validator, and HTML view
├── setup_tools.py # Cross-platform environment setup
├── install_manifest.py # Records what setup deployed; upgrade strategies
├── mail_affiliation.py # What project each end of a message belongs to
├── copilot_tools_version.py # The single source of the version number
├── worktree_guard_backend.py # PEP 517 backend; refuses `pip install -e` of a worktree
├── backfill_unknown_metrics.py # One-off repair: fabricated zeros to NULL
├── verify_cross_platform.py # Stdlib-only verification; runs without pytest
├── git_identity.py # Refuses to certify a history it could not read
├── e2e_restart_loop.py # End-to-end restart-loop check, real processes
├── setup.sh # POSIX bootstrap; finds Python, runs setup_tools
├── setup.ps1 # Windows bootstrap; same, winget if no Python
├── operator.sh # Legacy bash wrapper (Linux/WSL/macOS)
├── operator-ingest.py # Legacy log parser, used by operator.sh
├── handoff.sh # Legacy bash handoff (Linux/WSL/macOS)
├── diagnose-restart-deleter.sh # Diagnostic: who deleted the restart directory
├── extensions/ # Copilot CLI runtime extensions; see its README
├── skills/
│ ├── code-intelligence/
│ │ └── SKILL.md # Roslyn routing
│ ├── peer-agents/
│ │ └── SKILL.md # Peer operator agents and messaging
│ ├── worktrees/
│ │ └── SKILL.md # Worktree lifecycle and safe delegation
│ ├── backlog/
│ │ └── SKILL.md # Backlog format, approval gate, evidence
│ ├── spec-driven/
│ │ └── SKILL.md # The spec-kit workflow
│ ├── field-notes/
│ │ └── SKILL.md # The cross-project agent journal
│ └── operator-backlog-*/
│ └── SKILL.md # Filing, refinement and the check-in
├── templates/
│ ├── copilot-instructions.md # Source for each project's AGENTS.md; no longer deployed
│ └── mcp-config.json # MCP server config
├── tests/ # pytest suite + bash coordination tests
└── docs/
├── operator.md # Operator documentation
├── skills.md # Skills reference
├── versioning.md # Install manifest and upgrade strategies
├── checkout-guard.md # Stray-artifact guard, and how to tell it is running
├── experimental-default.md # Measurement: the CLI loads no extensions without --experimental
└── spec-kit.md # GitHub spec-kit documentation
The version lives in one place, copilot_tools_version.py, and pyproject.toml
reads it dynamically. Setup records what it deployed — path, version, and
SHA-256 — in ~/.operator/install-manifest.json, which is what lets a later run
update a file you never touched without prompting while still asking about one
you customised.
python setup_tools.py --status # what's installed, and is it stale?See Versioning for the artifact states, the hashing rationale, and how to write a version-to-version upgrade function.
MIT — see LICENSE.
This is an independent personal project. It is not affiliated with, endorsed by, or supported by GitHub or Microsoft, and it is not an official distribution of the GitHub Copilot CLI. "GitHub", "GitHub Copilot", and "Microsoft" are trademarks of their respective owners and are used here only to describe what this toolkit interoperates with.