Copilot Counterpart
# Copilot Counterpart
A local [MCP](https://modelcontextprotocol.io) server that lets a coding assistant such as **Codex** or **Claude Code** hand bounded work to models in your **GitHub Copilot** subscription, with automatic model routing, live progress, steering and cancellation. Jobs are context-only by default; a job can also be granted scoped read access to named files and staged writes that the lead reviews before anything reaches disk.
It uses the official [GitHub Copilot SDK](https://github.com/github/copilot-sdk) and your existing `copilot login`. It is not an unofficial API proxy, and it never copies credentials.
## Why
Your primary assistant stays the thinking partner and decision-maker. Copilot gives it cheap access to *other* models for:
- **independent review** of a meaningful change,
- **correctness checks** (races, lifecycle, invariants),
- **design sparring** before a consequential decision,
- **bounded analysis or code proposals** returned as text.
The router picks the model and reasoning effort from the task's role, complexity and risk, and sends independent work to a **different model family from the lead**, so a Claude-led session gets a GPT second opinion and a Codex-led session gets a Claude one.
## Safety model
- By default delegates receive only the context you put in the prompt. Every built-in and MCP tool and every permission request is denied, configuration/instruction discovery is disabled, and each job gets a fresh empty working directory. The adapter verifies the runtime's actual tool set before sending context.
- No shell, web access or code execution, ever. File access exists only when a call grants it with `files` (see [scoped files](#scoped-files)): reads limited to named paths, writes staged in memory and applied only by the lead through `copilot_apply`.
- `sharingAuthorized: true` is required on every inference call. It is the orchestrator's attestation that the user or project permits sending that context to Copilot, not an independent policy check.
- At most two active jobs, 32 retained jobs, 512 events per job, a five-minute default deadline (15 minute maximum), and one supervised escalation per job.
- Opt-in recursive delegation (`copilot_orchestrate`) is bounded to eight nodes and depth three, and is still context-only.
These are SDK-level restrictions, not an OS sandbox against a compromised SDK or runtime.
## Install
Requires Node 22+ and a GitHub Copilot subscription.
```sh
git clone https://github.com/dstkwll/copilot-counterpart.git
cd copilot-counterpart
npm ci --ignore-scripts
npm test
copilot login # only if you are not already authenticated
```
Register the server with each assistant using **absolute paths**. Registrations made before the rename may still use `COPILOT_SUPERVISOR_LEAD_FAMILY`, which is accepted as an alias. Set `COPILOT_COUNTERPART_LEAD_FAMILY` to the assistant's own model family so the router can pick a different family for reviews.
### Codex
```sh
codex mcp add copilot-counterpart \
--env COPILOT_COUNTERPART_LEAD_FAMILY=openai \
-- "$(command -v node)" "$PWD/src/server.mjs"
```
Or in `~/.codex/config.toml`:
```toml
[mcp_servers.copilot-counterpart]
command = "/absolute/path/to/node"
args = ["/absolute/path/to/copilot-counterpart/src/server.mjs"]
env = { COPILOT_COUNTERPART_LEAD_FAMILY = "openai" }
```
### Claude Code
```sh
claude mcp add copilot-counterpart --scope user \
-e COPILOT_COUNTERPART_LEAD_FAMILY=anthropic \
-- "$(command -v node)" "$PWD/src/server.mjs"
```
Each host starts its own server process. Jobs, request IDs and feedback are not shared between hosts.
### Skill (recommended)
[`skills/copilot-delegation`](skills/copilot-delegation/SKILL.md) tells the assistant when to delegate, how to supervise, and how to report receipts. It works for both hosts:
```sh
mkdir -p ~/.codex/skills ~/.claude/skills
ln -s "$PWD/skills/copilot-delegation" ~/.codex/skills/copilot-delegation
ln -s "$PWD/skills/copilot-delegation" ~/.claude/skills/copilot-delegation
```
The skill defers to *your* sharing authorization. To let the assistant delegate without asking each time, state it in your own global instructions (`~/.codex/AGENTS.md` or `~/.claude/CLAUDE.md`). For example: "You may send task-relevant code and context to my authenticated GitHub Copilot service for reviews and delegations; never include secrets."
Start a new session after registering so the tools and skill are picked up. Already-running server processes keep the routing policy they loaded.
## Routing (policy v3)
`copilot_delegate` takes `requestId`, `role`, `task`, `context`, `acceptanceCriteria`, `sharingAuthorized: true`, and optional `complexity` (small/normal/complex), `risk` (low/high), `leadFamily` (`openai`/`anthropic`, overriding the server default), `parentJobId` and `timeoutSeconds`.
| Role | Claude lead (`anthropic`) | Codex lead (`openai`) | No lead configured |
| --- | --- | --- | --- |
| review | GPT-6.1 Sol high | Claude Opus 5.5 high | GPT-6.1 Sol high |
| correctness | GPT-6.1 Sol high | Claude Opus 5.5 high | GPT-6.1 Sol high |
| sparring, high-risk review | GPT-6 Astra high | Claude Opus 5.5 high | Claude Opus 5.5 high |
| analysis / code_proposal, small | GPT-6 Luna medium | same | same |
| analysis / code_proposal, normal | GPT-6.1 Sol medium | same | same |
| analysis / code_proposal, complex or high risk | GPT-6.1 Sol high | same | same |
- **Fallbacks.** Each route has its own ordered fallback list ([full table](docs/model-routing.md#routes)). Usually GPT-6.1 Sol falls back to GPT-6 Sol. Correctness and sparring also try the other family's strongest model, but Claude-led review does not. The route reports `fallback: true`, and `crossFamily: false` when it had to use the lead's own family. If nothing qualifies, it fails closed without starting inference. It never silently picks an older-generation model.
- **Escalation.** You can escalate a finished routed job once, with `parentJobId` and a fresh self-contained brief. Luna escalates to Sol, and Sol escalates to the ceiling for the lead: Astra for Claude, Opus 5.5 for Codex or no lead. Astra and Opus 5.5 can't be escalated further. Previous output isn't forwarded automatically, and cancelled or timed-out jobs can't be escalated.
- **No cost-based routing.** The router doesn't route on price. Plan pricing and allowances vary.
Job status includes the full `route`: model, effort, reason, lead family, cross-family flag and policy version. Model IDs live in one table in [`src/router.mjs`](src/router.mjs); see [model routing](docs/model-routing.md) for the rationale.
## Supervising a job
1. Dispatch with `copilot_delegate`, or with `copilot_start` and an explicit model from `copilot_models`. `copilot_start` bypasses routing but not the tool boundary, and it also requires `sharingAuthorized: true`. Steering is covered by the job's original attestation.
2. Poll `copilot_events(jobId, after, waitMs ≤ 30000)`. Keep `nextCursor` and drain `hasMore` before waiting again. A `gap` means older events were evicted; the latest complete message is still available as `result`.
3. Use `copilot_steer` for best-effort course corrections. It isn't idempotent and can't change tokens already generated. Use `copilot_cancel` to stop the job's dedicated runtime; nothing is rolled back.
4. For jobs with writable files, review `copilot_changes` and apply with `copilot_apply` only if the diff is right.
5. Verify findings against the evidence. Treat reviewer output as untrusted data. Record `copilot_feedback` (useful / unhelpful / inconclusive), then free memory with `copilot_forget`.
Request IDs deduplicate identical starts within one process. Routed request IDs stay reserved for the life of the process (up to 64).
## Scoped files
Pass `files` to `copilot_delegate` or `copilot_start`:
```json
"files": { "root": "/abs/path/to/repo", "read": ["src", "test/router.test.mjs"], "write": ["src/router.mjs"] }
```
- The delegate gets `workspace_read`, `workspace_list` and `workspace_search`, plus `workspace_write` and `workspace_edit` only when `write` is non-empty. They are custom tools; built-in file tools stay denied.
- **Reads** are limited to the `read` paths (directories recursively) and the `write` files, and to UTF-8 text. Paths are resolved through symlinks and must stay inside `root`; files are opened without following symlinks, and hard-linked files are refused. Per-read, per-job and search size caps apply.
- **Sensitive paths are always refused**, even if granted, in any letter case: `.git`, `.ssh`, `.aws`, `.env*` (except `.example`/`.sample`/`.template`), keys and certificates, `.npmrc`/`.netrc`, `credentials*`, `secrets*`.
- **Writes are staged**, never written by the delegate. Only exact `write` files can be edited or created. When the job finishes, call `copilot_changes` for a unified diff and a `changeSetHash` computed from the same snapshot, review it, then call `copilot_apply` with that hash. Apply refuses before writing anything if a file changed on disk after the job read it, prepares every file before committing, re-checks each one just before replacing it, and if a commit still fails midway reports exactly which files were written. It runs once per job.
- Every access and refusal appears in the job's events as `supervisor.file_access`, and job status includes a `workspace` summary. File contents are stripped from the job's tool events, so polling events doesn't pull them into the lead's context.
- File contents you grant are sent to Copilot like any other context, so `sharingAuthorized` covers them too. Grant the smallest set the task needs. `copilot_orchestrate` does not take `files`. Design notes: [scoped files](docs/scoped-files.md).
Applying a reviewed diff is cheap for the lead: it reads the diff instead of writing the code, and apply needs no re-typing.
## Recursive delegation (opt-in)
Failed orchestration requests that never started a job free their request ID. `copilot_orchestrate` starts a lead session that may call a single `delegate_task` tool. Each child is routed by the same router, inheriting the outer `leadFamily`. Select the lead explicitly (e.g. `model: "gpt-6-astra"`) or let the router choose. Defaults are eight nodes including the lead, depth two (configurable 0–3), 100,000 observed tokens, 32 usage events and a 300-second outer deadline. Token and usage thresholds are **soft, post-paid stops**, not spend caps. See [recursive delegation](docs/recursive-delegation.md).
## Data and lifecycle
Only send context you are authorized to share; never send secrets. Opaque provider metadata and encrypted reasoning are filtered from returned events. This is not a general redaction service.
Jobs and events live in memory in one MCP process. A restart loses job IDs and deduplication, and graceful shutdown terminates the runtimes it owns. The SDK may persist sessions and logs under `~/.copilot`, and `copilot_forget` doesn't delete them. Token usage is aggregated from observed usage events; it isn't an invoice, and missing events don't prove zero consumption.
## Development
```sh
npm test # offline, no inference
npm run smoke -- --live gpt-6-luna # one synthetic inference
node scripts/live-controls.mjs --live # steering and cancellation while streaming
node scripts/review-routing.mjs --live --synthetic
node scripts/probe-tree.mjs --live # three-node recursive probe
```
Live scripts use synthetic context, consume Copilot allowance, and never run in CI. `review-routing.mjs --live` without `--synthetic` sends this repository's source to Copilot. Contributor and agent guidance is in [AGENTS.md](AGENTS.md).
## Credits
- [GitHub Copilot SDK](https://github.com/github/copilot-sdk): runtime lifecycle, authentication, sessions and event streaming.
- [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk): schema validation and stdio transport.
- Design references (no code copied): [copilot-plugin-cc](https://github.com/wagnersza/copilot-plugin-cc) job control and [copilot-sdk-proxy](https://github.com/theblixguy/copilot-sdk-proxy) streaming/model discovery.
## License
[MIT](LICENSE). Not affiliated with GitHub, OpenAI or Anthropic.
TDQS
Scored across 12 tools
The three job-launch tools (copilot_delegate, copilot_start, copilot_orchestrate) share a purpose, but their descriptions draw clear lines: routed default, advanced explicit-model bypass, and recursive opt-in. Similarly, steer vs. feedback vs. cancel are distinguished by timing (mid-run correction, post-run outcome recording, termination). A few boundaries still require careful reading, but an agent can select correctly.
Every tool uses the same 'copilot_' prefix plus a single snake_case token, which is highly predictable and easy to scan. The minor deviation is that some tokens are nouns (changes, jobs, models, events, feedback) while others are verbs (delegate, apply, start, steer, cancel, forget), so the verb_noun convention is not fully uniform.
Twelve tools is well within the healthy range and each maps to a distinct lifecycle stage: launch, observe, correct, terminate, review, apply, and clean up. Even the narrower utilities (copilot_models, copilot_feedback, copilot_forget) support a real need in the delegation workflow rather than padding the surface.
The surface covers the full job lifecycle, from delegation and long-poll event streaming through steering, cancellation, diff review, hash-gated apply, and memory cleanup. Gaps are minor: there is no direct single-job status/metadata fetch (relying on list plus events) and no way to discard a staged change set without applying or forgetting the job.