Share one AI coding session with a teammate over a link — from the editor you already use.
Status: 0.1.0, marked preview. Two people can pair: share a link, join, co-drive one agent. Verified by 141 tests including real host+guest sessions over a real relay driving a real ACP agent. The wire protocol may still change between 0.x releases. See Roadmap.
Search Agent Pairing in the extensions view, or:
cursor --install-extension agentpairing.agentpairing
code --install-extension agentpairing.agentpairing
Cursor resolves extensions through its own mirror of Open VSX rather than Open VSX itself,
so a freshly published version takes a while to become findable there. Until it is, download
the .vsix from the
Open VSX listing and install the
file directly.
Pairing changed shape. The work is no longer two people taking turns at one keyboard — it's two people discussing an approach, co-drafting a prompt, reading the output together, re-prompting, and watching edits land. Every current tool breaks the moment the two people aren't in the same room:
- Cursor's Agent panel is single-player and closed. No extension API can read its messages, inject prompts, or observe its stream.
- Live Share is Microsoft-proprietary and blocked in Cursor. Cursor users have no first-class remote pairing story at all.
- Zed has multiplayer agents natively — but only if you abandon your editor.
This is the multiplayer layer, built as an extension, for the editors people actually use.
The panel is a chat room that contains an agent, not an agent transcript with chat bolted on.
Messages default to pair scope — human to human, never sent to the model, never in its
context. Prefix @agent (or press ⌘⇧↵) to promote a message to agent scope,
which is what actually reaches the agent.
That inversion is the whole point: argue about approach for five minutes without any of it polluting the context window, then send one clean prompt.
The protocol also supports promoting specific side-chat into a prompt, recording exactly
which messages were handed over so the guarantee stays auditable rather than aspirational
(AgentPromptEvent.includedContext). There is currently no UI for it — the first attempt,
a per-message checkbox, cluttered the transcript and was removed. The plumbing and its
tests remain for a better affordance.
Host-authoritative. Only the host runs the agent and has the repo. The guest runs the same extension in guest mode: no agent process, no checkout, pure view and input. That single decision removes repo sync, merge, and environment parity from the problem.
The guest can still see the host's project. A read-only agentpairing-remote: filesystem provider
proxies reads to the host on demand, surfaced as two tree views — Host Changes (what
git status reports, so it covers the host's hand edits too) and Host Files (browse
anything). Visibility is defined by git, not by us: a path must appear in
git ls-files --cached --others --exclude-standard, so .env, keys, and build output are
excluded because git already excludes them. If git is unavailable, it fails closed.
HOST editor GUEST editor
┌────────────────────────┐ ┌────────────────────────┐
│ Webview (React) │ │ Webview (React) │
└──────────┬─────────────┘ └──────────┬─────────────┘
┌──────────┴─────────────┐ ┌────────────┐ ┌──────────┴─────────────┐
│ Extension host (Node) │◄──►│ Relay │◄►│ Extension host (Node) │
│ · session event log │ ws │ (dumb, E2E)│ws│ · replica of log │
│ · AgentAdapter │ └────────────┘ │ · no agent, no repo │
└──────────┬─────────────┘
│ JSON-RPC over stdio
┌──────┴───────┐
│ Claude Code /│ ACP agent subprocess
│ Codex / etc. │
└──────────────┘
One event type, two adapters. Everything normalizes to an internal AgentEvent, so
nothing downstream knows which backend is running:
-
ACP (Agent Client Protocol) via
@agentclientprotocol/sdk— one adapter yields Claude Code, Codex, Gemini CLI, opencode, Qwen and 25+ others. -
Cursor CLI — set
agentpairing.agenttocursor. Runscursor-agentdirectly: no API key (it uses the CLI's own login, socursor-agent loginis the whole setup), and the model picker is populated from your real account — 190 models on a Pro plan.Driven as one process per turn via
-p --output-format stream-json, capturing thesession_idand passing it back with--resumefor continuity. That deliberately avoidscursor-agent create-chat, which prints a chat id and then never exits — the reason both community ACP bridges for Cursor fail. This mode has no permission prompts:-pis non-interactive, so tool calls are reported, never proposed, and the symmetric approval below does not apply. The panel says so when the session starts.Neither this nor any extension can reuse your Cursor IDE login — that lives in the editor's own storage with no extension API, the same wall that stops us extending Cursor's agent panel.
Sequenced append-only log, not CRDTs. The host is the only writer of record, so ordering needs nothing cleverer than a counter: gapless sequence numbers give total order, free duplicate rejection, and trivial late-join replay. The one genuinely concurrent surface — the co-drafted prompt box — will be a Yjs document carried as ephemeral traffic; today each peer types into their own composer.
Zero-knowledge relay. The session key lives in the URL fragment, which browsers never
transmit. The relay forwards AES-GCM ciphertext between peers and is structurally incapable
of reading it. It also stores nothing — not even ciphertext: late joiners get their
backlog from the host, so there is no transcript on the server to seize or leak. A public instance runs at
https://relay.agentpairing.com. Self-host your own on one small box — see docs/DEPLOYING.md, which includes
measured capacity (1000 concurrent sessions in ~100MB and a fraction of a core) and the
one architectural catch: rooms are in memory, so a second instance needs sticky routing.
| Package | What it is |
|---|---|
packages/protocol |
Wire types, event log, crypto. Imported by every other package. |
packages/extension |
Extension host: adapters, session controller, panel. |
packages/webview |
React panel UI. |
packages/relay |
WebSocket relay server. |
npm install
npm test # 141 tests, including ACP integration against a live agent subprocess
npm run build
npm run lintThen launch an Extension Development Host:
npm run devhost # add `2` for a second window, to pair against yourselfIt opens scratch/ as the workspace, which carries settings pointing at the mock agent and
a local relay, so there is nothing to configure. Use the script rather than F5:
on macOS the editor is single-instance, so relaunching it forwards the arguments to the
running process and silently drops --extensionDevelopmentPath.
Set agentpairing.agent to mock in the dev host's settings. That runs a scripted agent bundled
with the extension — no key, no network, no cost — which speaks the real ACP wire format
through the real SDK. It walks through a thought, a plan, streaming text, a tool call, a
permission prompt, and a diff, so every render path in the panel is exercised.
This is the recommended way to develop UI, and the fastest way to see what the project does.
Step-by-step walkthrough with expected output at each stage:
docs/VERIFYING.md.
The test suite spawns a real ACP agent subprocess — a scripted mock in
packages/extension/src/testing/mock-acp-agent.ts that speaks the actual protocol through
the actual SDK. No API keys, no network, fully deterministic, and it covers the branches
that are painful to trigger live: mid-turn crashes, cancellation, and races on permission
approval.
- The relay is untrusted and sees only ciphertext bound to a session id.
- The guest is trusted. They can read everything and — by design — approve tool calls that write to the host's disk. Every approval is attributed permanently in the transcript, and the host always holds a pause and revoke switch.
- Guests read files through git's lens. Only what
git ls-fileswould show is readable; anything gitignored is never sent, and paths are resolved throughrealpathand confined to the workspace so neither..nor a symlink escapes.agentpairing.shareFilesturns browsing off entirely, read live so it can be revoked mid-session. - Guests never write. There is no write path in the protocol at all; the only route to the host's disk stays the agent.
- The Cursor backend has no permission prompts. On ACP, a tool call is gated on an
approval either peer can give. On
@cursor/sdkthere is no such gate — nothing to approve, for anyone. Prefer ACP if that gate is what you are relying on. - A share link is a credential. It grants access to whoever holds it, so treat posting one in a channel as adding everyone in that channel.
- M1 — Single-player ACP panel: streaming, tool calls, diffs, permissions
- M2 — Session log + relay: sequencing, snapshot/replay, E2E crypto
- M3 — Multiplayer: presence,
@agentscoping, symmetric approval (Yjs shared draft still to do) - M4 — Join flow: link generation, URI handler, host approval, guest mode
- M4.5 — Read-only remote workspace: Host Changes + Host Files, gitignore-scoped
- M5 — Cursor SDK adapter (translation tested; the live path needs a key and is unverified)
- M6 — Deploy the public relay, publish to Open VSX
Open VSX matters: Cursor no longer uses the VS Code Marketplace, so that is the registry that reaches its users. Not yet cross-published to the VS Code Marketplace, which needs a separate Azure DevOps token.
formulahendry/vscode-acp (MIT) is the
reference single-player ACP client for VS Code. Its handler decomposition and agent launch
configurations informed this implementation.