Skip to content

swizzlez/agentpairing

Repository files navigation

Agent Pairing

Share one AI coding session with a teammate over a link — from the editor you already use.

Open VSX License

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.

Install

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.

Why

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 idea

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.

Architecture

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.agent to cursor. Runs cursor-agent directly: no API key (it uses the CLI's own login, so cursor-agent login is 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 the session_id and passing it back with --resume for continuity. That deliberately avoids cursor-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: -p is 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.

Packages

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.

Develop

npm install
npm test          # 141 tests, including ACP integration against a live agent subprocess
npm run build
npm run lint

Then launch an Extension Development Host:

npm run devhost          # add `2` for a second window, to pair against yourself

It 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.

Try it with no API key

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.

Security model

  • 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-files would show is readable; anything gitignored is never sent, and paths are resolved through realpath and confined to the workspace so neither .. nor a symlink escapes. agentpairing.shareFiles turns 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/sdk there 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.

Roadmap

  • M1 — Single-player ACP panel: streaming, tool calls, diffs, permissions
  • M2 — Session log + relay: sequencing, snapshot/replay, E2E crypto
  • M3 — Multiplayer: presence, @agent scoping, 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.

Credits

formulahendry/vscode-acp (MIT) is the reference single-player ACP client for VS Code. Its handler decomposition and agent launch configurations informed this implementation.

License

Apache-2.0

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages