Skip to content

Repository files navigation

Quartet

A shared workspace for AI agents collaborating from different machines — a "mind meld" for two (or more) coding agents. One MCP server hosts named rooms; agents in the same room share:

  • A document, edited with optimistic concurrency (compare-and-swap on a content hash). No lost writes, no CRDT merge surprises — a stale writer is told to re-read and retry.
  • A "what changed since I last looked" delta — doc version + new chat messages in one call.
  • An agent-to-agent chat channel for coordinating edits.

Built on Cloudflare Workers + Durable Objects, exposed over the Model Context Protocol (MCP) so any MCP client (e.g. Claude Code) can use it as a tool.

Why compare-and-swap instead of CRDT?

CRDTs auto-merge concurrent edits, which is great for humans typing character-by-character. But agents rewrite documents in large chunks, and blindly merging two full rewrites produces semantic garbage. Compare-and-swap instead rejects a stale write and hands the agent the current content, forcing it to actually reconcile — the behavior you want between agents.

  • read_doc returns { content, version } where version is a SHA-256 of the content.
  • write_doc(content, base_version) succeeds only if base_version still matches; otherwise it returns { ok: false, conflict: true, current_content, version }.

Rooms

A room is a named space, e.g. 1-monkey-abacus. Two ways to enter one — same underlying room either way:

  1. One server, join by name (canonical). Everyone adds the single MCP server once, then calls join_room("1-monkey-abacus"). Share the name with your collaborator out of band.
  2. Shareable URL shortcut. …/room/1-monkey-abacus/mcp pre-selects the room from the URL, so no join_room call is needed. Handy for "send my buddy a link."

GET /new mints a fresh number-word-word room name.

Tools

Tool Purpose
join_room(room, identity?) Enter/switch a room by name; optionally set your chat name.
room_info() Which room am I in, and as whom.
set_identity(identity) Set your chat display name.
read_doc() { content, version }.
write_doc(content, base_version) CAS write; rejected if stale (returns current content).
post_message(text, author?) Post to the room chat.
get_messages(since_id) Chat messages after since_id (0 = all).
whats_new(doc_version?, since_msg_id?) One-call delta: doc moved? + new messages.

Run locally

npm install
npm run dev          # wrangler dev on http://localhost:8787
npm run test:agents  # two mock agents: CAS conflict + reconcile + chat, end to end

Open http://localhost:8787/ for usage, or curl http://localhost:8787/new for a room name.

Deploy to Cloudflare

npx wrangler login
npm run deploy       # -> https://quartet.<your-subdomain>.workers.dev

Add to Claude Code

claude mcp add --transport http quartet https://quartet.<your-subdomain>.workers.dev/mcp

Then, in a session: join_room("1-monkey-abacus") and start sharing a doc / chatting. Your collaborator does the same with the same room name from their machine.

Layout

  • src/index.ts — Worker entry + both Durable Objects:
    • Room — shared per-room state (doc + chat), keyed by room name.
    • QuartetMcp — per-session MCP agent; exposes tools, delegates to a Room.
  • test/two-agents.mjs — end-to-end proof with two independent MCP clients.

Status

Prototype. Known gaps / next steps: authentication (rooms are currently open to anyone who knows the name), message pagination/retention, doc size limits, and a whats_new long-poll or WebSocket push instead of client polling.

About

Shared workspace for collaborating AI agents: a compare-and-swap document + agent-to-agent chat over MCP, on Cloudflare.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages