Documentation · v0.1.0
/.relay docs
/.relay is the coordination layer for AI coding agents. Claude Code, Cursor, Copilot, Codex and Antigravity all write to the same files and none of them know the others exist. /.relay gives them one shared lock table, a dependency-aware view of the blast radius, and live patch sync across every machine on the team.
Everything on this page runs on your own hardware. There is no hosted backend, no database, and no account server — one person on the team runs relay serve and that machine is the server.
- 12 agents · 12 blind writers · conflicts discovered at merge
+ 12 agents · 1 lock table · conflicts prevented at the edit
What is in here
| If you want to… | Go to |
|---|---|
| Install the CLI and run it for the first time | Installation → Quickstart |
| Understand what a lock actually guards | Core concepts |
| Look up a command and its flags | CLI reference |
| Wire a specific agent into the claim/release cycle | Agent hooks |
| Read room context from an MCP client | MCP server |
| Fix an empty board or a broken room | Troubleshooting |
Reading order matters less than you would think — the quickstart is four commands. If you are impatient, jump to Quickstart · Host and come back for the concepts once something is locked.
Get started
Installation
/.relay is a Node CLI plus a local API and a Next.js board. Everything ships in one repository, so installing means cloning it and linking the CLI onto your PATH.
Requirements
| Status | What | Why |
|---|---|---|
| Required | Node.js 18+ (20+ recommended), npm | Runs the CLI, the API, and Mission Control |
| Required | git | relay clone shells out to it |
| Optional | gh CLI | Fastest path for relay login |
| Optional | ngrok | Only needed for cross-machine rooms |
| Not needed | MongoDB, Redis, Docker, a hosted backend, an account | State lives in files on your disk |
Install the CLI
$ git clone https://github.com/prakrititz/relayBrain
$ cd relayBrain
$ npm install # deps for the CLI, API and Mission Control
$ npm link # puts `relay` on your PATH
Confirm the binary resolves:
$ relay help
Relay — GitHub Desktop for AI agents
relay login Sign in with GitHub (uses gh CLI, or device flow if GITHUB_CLIENT_ID is set)
relay logout Clear the local session
relay whoami Show the signed-in GitHub user
relay clone <url> [dir] git clone + register workspace + install agent hooks
relay add <path> Attach an existing local repo
relay serve Start API on 127.0.0.1:3001 and Mission Control on :3002
relay serve --no-ui Start API + coordinator only
relay push Send your dirty working-tree files to the shared room
relay pull Apply the host's current dirty files onto this clone
relay status Health check
relay mcp-url Print the MCP config for this room's shared context endpoint
relay doctor Why the Coordinator board is empty
npm link writes a global symlink. If your Node install lives under a system path you may need elevated permissions, or an nvm/fnm-managed Node — which is the friendlier option.
Get started
Quickstart · Host
The host is the machine that owns the lock table. Exactly one person on the team is the host; everyone else is a guest. Four commands and you are live.
Sign in with GitHub
Locks are attributed to a GitHub identity, so the board can say @pony holds this file rather than agent-4f2 holds this file.
$ relay login
This reuses the gh CLI session. If gh is not installed, set GITHUB_CLIENT_ID and /.relay falls back to the GitHub device flow.
Bring a repository under /.relay
Cloning through relay clones the repo, registers the workspace, and installs the claim/release hooks for every supported agent in one shot.
$ relay clone <repo-url> [dir]
Already have the repo on disk? Attach it in place instead — same hook install, no re-clone:
$ relay add /path/to/repo
Start the relay
$ relay serve # API → 127.0.0.1:3001 · Mission Control → :3002
$ relay serve --no-ui # API + coordinator only, no board
Mission Control is a Next.js dev server; the CLI waits for /api/health to answer before booting it, and shuts both down together on Ctrl-C.
Mint the invite link
Open localhost:3002 → Team → Share. /.relay opens an ngrok tunnel to your machine and hands you a link. That link, plus the member token baked into it, is the whole room.
Nothing else to remember. Once hooks are installed, agents claim and release locks on their own — no one on the team ever types a lock command.
Get started
Quickstart · Teammate
Guests run their own relay against their own filesystem. They do not share a working tree with the host — they share a lock table and a patch stream.
$ relay login # their own GitHub identity
$ relay clone <same-repo-url> # their own clone
$ relay serve # their own local relay
# then: Mission Control → Team → Join, paste the host's invite link
$ relay pull # take the host's current working state
From that point on, the two machines see one lock table. A claim on the guest's machine is arbitrated against the host over the tunnel, and the board mirrors the host's locks back down.
| Role | Owns | Board is fed by |
|---|---|---|
| Host | The canonical lock table | Its own local table |
| Guest | Its own clone and agents | The lock mirror polling the host |
If the host's tunnel dies, locks still arbitrate correctly on the machines that can reach it — the board is what degrades first. relay doctor walks that chain and names the broken link.
Get started
Your first coordinated write
Open your agent as you normally would and ask it to edit a file. Nothing about your workflow changes — the hooks fire underneath.
lock table — 4 held
├── src/auth/session.ts # @pony · claude · write
├── src/api/routes.ts # @unnath · cursor · write
├── lib/db/client.ts # @arjun · codex · write
└── ui/Header.tsx # @pony · copilot · write
└── dep-blocked: ui/Nav.tsx, ui/Layout.tsx
Now have a second agent try the same file. Instead of a silent overwrite discovered three hours later at the merge, the write is refused at the edit and the agent is told who holds it.
Confirm the whole chain is healthy at any time:
$ relay doctor
Core concepts
File locking
Every agent write goes through a claim/release cycle against a shared, TTL-based lock table. A pre-tool hook fires before the agent touches a file and claims it. A post-tool hook fires after and releases it. If another agent holds the file, the claim fails and the write never lands.
agent wants to write src/auth/session.ts
│
├─ pre-tool hook ──► POST /api/locks/claim
│ ├─ free? → granted, TTL starts
│ ├─ held by other? → REFUSED (+ holder identity)
│ └─ dep-affected? → REFUSED (+ blast radius)
│
├─ the edit runs
│
└─ post-tool hook ─► POST /api/locks/release
└─ change fans out as a patch to the room
Locks are TTL-based and auto-expire, so a crashed agent or a killed terminal cannot wedge a file permanently. Hooks call the host directly over HTTP — arbitration never depends on the board being up.
Core concepts
Dependency-graph locking
Files do not exist independently. Editing one module can break a dozen importers, so path-exact locking is not enough. /.relay parses imports with tree-sitter and asks the real question: does this change affect a file another agent is working on? — not merely is this exact path locked?
Agents stay parallel wherever the graph says they are independent. The blast radius is what gets serialized.
Languages parsed
| Family | Languages |
|---|---|
| Web | TypeScript, JavaScript |
| Systems | Go, Rust, C, C++ |
| JVM / .NET | Java, C# |
| Scripting | Python, PHP, Ruby |
The graph is rebuilt from the working tree, not from a hosted index. A file in a language outside this list still locks exactly — it just does not contribute edges to the blast radius.
Core concepts
Rooms & the host model
A room is one host machine plus the guests connected to it. There is no cloud component: relay serve binds the API to 127.0.0.1:3001, and sharing means opening an ngrok tunnel straight to that laptop.
Cursor ──┐ pre-tool hook: claim
Claude ──┤ post-tool hook: release ┌──────────────┐
Copilot ─┼──► lock table ◄── dep graph ──│ relay serve │──► Mission Control
Codex ───┤ ▲ │ (your host) │ ▲
Antigravity ┘ │ └──────┬───────┘ │
│ ngrok tunnel │ │
teammate's relay ◄──── patches ─────┴──────────────────┘
| Layer | Who runs it | What it guards |
|---|---|---|
| Claim / release | pre- and post-tool hooks | Exclusive write access to a file |
| Dependency graph | tree-sitter import scan | Files affected by the edit |
| Room | relay serve + ngrok tunnel | One shared lock table across machines |
| Patches | relay push / relay pull | Working-tree state without full copies |
| Board | Mission Control (:3002) | Who holds what, right now |
Room state is written to room.json and carries the role (host or guest), the tunnel URL, the host project id, and a member token for invite-only rooms.
Core concepts
Patch sync
Your teammate has their own filesystem, their own environment, and their own agents. /.relay does not copy projects around — it propagates patches of the dirty working tree, so both clones converge on the same uncommitted state without anyone committing.
$ relay push # send your dirty working-tree files to the room
$ relay pull # apply the host's current dirty files onto this clone
push 3 file(s)
src/auth/session.ts
src/api/routes.ts
lib/db/client.ts
Both commands are thin clients over the local API — if the relay is not running you get offline — run relay serve and a non-zero exit code, which makes them safe to use in scripts.
Reference
CLI reference
Every command /.relay ships. Expand one for usage, output, and the failure modes worth knowing.
Identity
relay loginSign in with GitHub
Reads the existing gh CLI session and stores a local /.relay session. Locks and board entries are attributed to this identity.
$ relay loginsigned in as Krishna V (@pony) via GitHub CLINo gh installed? Set GITHUB_CLIENT_ID (and GITHUB_CLIENT_SECRET) and the command falls back to the GitHub device flow, printing a verification URL and a one-time code. You can also sign in from Mission Control with Continue with GitHub.
relay whoamiShow the signed-in user
$ relay whoamiKrishna V (@pony)
project prj_8f31a2Exits non-zero with not signed in — run relay login when there is no session.
relay logoutClear the local session
$ relay logoutRemoves the stored session only. Your gh credentials are untouched.
Workspaces
relay clone <url> [dir]Clone + register + install hooks
Three steps in one: git clone, register the workspace as a /.relay project, and install pre-tool, post-tool, pre-read and stop hooks for every supported agent.
$ relay clone https://github.com/acme/api-server
$ relay clone https://github.com/acme/api-server ./apicloned https://github.com/acme/api-server
workspace /Users/pony/code/api-server
registered api-server (prj_8f31a2)relay add <path>Attach an existing local repo
Same registration and hook install as clone, without touching git. Use this for a repo you already have on disk.
$ relay add ~/code/api-serveradded /Users/pony/code/api-server as api-serverRuntime
relay serveStart the API and Mission Control
Boots the coordination API on 127.0.0.1:3001, waits for /api/health, then starts Mission Control on :3002. Both processes shut down together on Ctrl-C, including their child process trees on Windows.
$ relay serve # API + Mission Control
$ relay serve --no-ui # API + coordinator only
$ relay serve --port 4001 # move the API off 3001
| Flag | Effect |
|---|---|
--no-ui | Skip Mission Control; run the API and coordinator only |
--port <n> | API port (overrides RELAY_PORT, default 3001) |
The board port comes from RELAY_UI_PORT (default 3002). If next is missing you will get Mission Control is not installed — run npm install.
relay statusHealth check as JSON
Prints the raw /api/health payload — room, role, host, and ports.
$ relay status{
"ok": true,
"port": 3001,
"room": {
"role": "host",
"url": "https://a1b2c3.ngrok-free.app",
"hostProjectId": "prj_8f31a2",
"hostProjectName": "api-server",
"hostWorkspacePath": "/Users/pony/code/api-server"
}
}
Exits non-zero with offline — run relay serve when no local API answers.
Sync
relay pushSend dirty files to the room
$ relay pushpush 2 file(s)
src/api/routes.ts
lib/db/client.tsPrints push: nothing to push when the working tree is clean.
relay pullApply the host's working state
$ relay pullpull 3 file(s)
src/auth/session.ts
src/api/routes.ts
ui/Header.tsxPatches land on your clone — no whole-project copy, no commit required on either side.
Diagnostics & integration
relay doctorExplain an empty Coordinator board
Locks and the board are separate subsystems — hooks arbitrate straight against the host over HTTP, while the board is fed by a lock mirror and SSE. That makes "locking works but I see nothing" a normal and otherwise invisible failure. doctor walks that exact chain and names the broken link.
$ relay doctor
$ relay doctor --port 4001relay doctor — room + Coordinator board
ok local API on http://127.0.0.1:3001
ok room joined as guest -> https://a1b2c3.ngrok-free.app
ok mirroring host project prj_8f31a2
ok host answers through the tunnel
ok host reports 4 lock(s) for the shared project
board would render 4 lock(s):
3 mirrored from the room, 1 claimed on this machine
src/auth/session.ts @pony write
Run it on the machine that sees nothing — not on the host.
relay mcp-urlPrint the room's MCP config
Emits a ready-to-paste mcpServers block pointing at this room's shared-context endpoint. See MCP server.
$ relay mcp-urlExits non-zero with a hint when you are not in a room — locally your agents already reach relay over stdio, so there is nothing to configure.
relay helpFull command list
$ relay help
$ relay -h
$ relay --helpCheat sheet
| Command | Description |
|---|---|
relay login | GitHub identity via gh, or device flow with GITHUB_CLIENT_ID |
relay logout | Clear the local session |
relay whoami | Show the signed-in GitHub user and active project |
relay clone <url> [dir] | git clone + register workspace + install agent hooks |
relay add <path> | Attach an existing local repo |
relay serve | API on 127.0.0.1:3001 + Mission Control on :3002 |
relay serve --no-ui | API + coordinator only |
relay push | Send your dirty working-tree files to the room |
relay pull | Apply the host's current dirty files onto this clone |
relay status | Health check (room, role, host, ports) |
relay mcp-url | Print the MCP config for this room's shared-context endpoint |
relay doctor | Diagnose an empty Coordinator board |
Reference
Agent hooks
relay clone and relay add install four hooks into your project: pre-tool (claim), post-tool (release), pre-read, and stop. They are written into each agent's own config format, so every tool participates without a plugin.
| Agent | Config file | Write tools intercepted |
|---|---|---|
| Claude Code | .claude/settings.json | Edit, Write, NotebookEdit |
| Cursor | .cursor/hooks.json | Write, Edit, Delete |
| Codex | .codex/hooks.json | apply_patch, Edit, Write |
| Copilot CLI | .github/hooks/relay-os.json | edit, create |
| Antigravity | .agents/hooks.json | write_to_file, replace_file_content, multi_replace_file_content |
Verifying the install
$ ls -a .claude .cursor .codex .agents .github/hooks
The hook files are committed alongside the repo, so a teammate who clones it normally still gets the claim/release wiring — they only need their own relay serve running for it to have something to talk to.
Reference
MCP server
Any MCP client can read the room's shared context — even on a machine with no relay installed. Print the config and paste it into your agent:
$ relay mcp-url
{
"mcpServers": {
"relay-room": {
"type": "http",
"url": "https://a1b2c3.ngrok-free.app/mcp",
"headers": {
"ngrok-skip-browser-warning": "relay",
"x-relay-room-token": "rmt_9f81…"
}
}
}
}
The room endpoint is read-only for anyone off the host machine. Coordination tools that mutate state — relay_claim_file, relay_release_file, relay_status — run against each member's own local relay.
Tools exposed
| Tool | Purpose |
|---|---|
relay_claim_file | Acquire an exclusive write lock before editing |
relay_release_file | Release the lock after editing |
relay_status | View the full lock table |
relay_get_conflicts | Overlapping edits in the last 5 minutes |
relay_get_recent_changes | Recent code_edit events |
relay_get_chat_history | Unified agent chat across every room member |
relay_report_change | Push a code-change event |
relay_report_decision / relay_get_decisions | Append / read decisions |
relay_update_task / relay_get_active_tasks | Append / read tasks |
relay_get_project_context | Full JSON project context |
relay_sync | Re-read agent transcripts into unified history |
If you are a guest and no member token is on file, re-join with the invite link — the host may have made the room invite-only.
Reference
Mission Control
One board for every agent on every machine. Started by relay serve, running on your own hardware, with no hosted account behind it.
| Surface | URL |
|---|---|
| Dashboard | http://localhost:3002 |
| API health | http://127.0.0.1:3001/api/health |
Panels
| Panel | What it shows |
|---|---|
| File locks | Who holds what file, right now, with agent and mode |
| Lock graph | The dependency blast radius as a live canvas |
| Code edits | Recent code_edit events across the room |
| Agent chat | Unified session history from every agent on every machine |
| Activity timeline | Claims, releases, pushes and pulls in order |
| Team & presence | Who is online, plus Share and Join for the room |
| Conflict notices | Overlapping edits surfaced within seconds |
Reference
HTTP API
The CLI is a thin client over a local HTTP API. Anything the CLI does, you can script.
| Endpoint | Method | Purpose |
|---|---|---|
/api/health | GET | Liveness, port, room role and host details |
/api/locks | GET | The lock table this machine would render |
/api/locks?projectId=… | GET | Locks for one shared project (used across the tunnel) |
/api/push | POST | Send dirty working-tree files to the room |
/api/pull | POST | Apply the host's dirty files onto this clone |
/mcp | HTTP | Read-only room context for MCP clients |
$ curl -s http://127.0.0.1:3001/api/health
$ curl -s http://127.0.0.1:3001/api/locks
$ curl -s -X POST http://127.0.0.1:3001/api/push -d '{}'
Requests that cross the tunnel to a host carry two headers: ngrok-skip-browser-warning: relay and, in invite-only rooms, x-relay-room-token.
Reference
Configuration
Environment variables
| Variable | Default | What it controls |
|---|---|---|
RELAY_PORT | 3001 | Coordination API port |
RELAY_UI_PORT | 3002 | Mission Control port |
RELAY_UI_ORIGIN | http://localhost:3002 | Origin the API accepts board requests from (CORS) |
GITHUB_CLIENT_ID | — | Enables the device-flow login fallback |
GITHUB_CLIENT_SECRET | — | Pairs with the client id for Mission Control sign-in |
$ export RELAY_PORT=4001
$ export RELAY_UI_PORT=4002
$ export RELAY_UI_ORIGIN=http://localhost:4002
$ relay serve
Where state lives
There is no database. Everything is files on your own disk.
| File | Holds |
|---|---|
room.json | Role, tunnel URL, host project id and name, member token |
| Session store | The GitHub identity from relay login |
| Project registry | Workspaces registered by relay clone / relay add |
| Hook configs | Per-agent files listed under Agent hooks |
Bundled dependencies
| Area | Packages |
|---|---|
| API & coordination | express, cors, ws |
| Dependency graph | @vscode/tree-sitter-wasm |
| Mission Control | next, react |
Operations
Troubleshooting
Start here, always:
$ relay doctor # run it on the machine that sees nothing
The board is empty but locking works
Expected, and the single most common report. Hooks claim straight against the host over HTTP; the board is fed by a separate lock mirror. If the mirror is not committing its polls, locks stay correct while the board stays blank.
BROKE the host holds locks but this machine mirrors none of them.
Locks still arbitrate correctly (hooks call the host directly); it is only
the board that is fed by the mirror. Restart `relay serve` here.
Common failures
| Symptom | Cause | Fix |
|---|---|---|
offline — run relay serve |
No local API on the expected port | Start relay serve, or match RELAY_PORT |
no local API on http://127.0.0.1:3001 |
Same, seen from doctor |
Run relay serve on this machine |
room.json has no hostProjectId |
Joined without a complete invite | Re-join with the host's invite link |
cannot reach the host |
Tunnel down or rotated | Host re-shares; /.relay looks up the current tunnel and reconnects |
host refused /api/locks |
Missing member token in an invite-only room | Re-join with the invite link to store the token |
Mission Control is not installed |
next not present in node_modules |
npm install at the repo root |
Install GitHub CLI and run: gh auth login |
No gh session and no GITHUB_CLIENT_ID |
Install gh, or set the client id for device flow |
| No agent ever claims a lock | Hooks not installed in that workspace | Run relay add <path> on the repo |
Port already in use
$ relay serve --port 4001 # API only
$ RELAY_UI_PORT=4002 relay serve --port 4001 # both, plus set RELAY_UI_ORIGIN
An empty board with nothing locked anywhere is correct. Have someone start an agent edit and run relay doctor again before assuming something is broken.
Operations
FAQ
Do I need an account?
No. relay login reads a GitHub identity so locks can be attributed to a person, but there is no /.relay account server, no sign-up, and no hosted state.
Does this replace Git?
No — it sits under it. Git coordinates humans committing minutes apart. /.relay coordinates agents editing the same files right now. You still branch, commit and merge exactly as before.
What happens if the host goes offline?
Guests keep their own local relay and their own clone. Claims that need the host will fail loudly rather than silently succeed, and the board falls back to local locks until the tunnel is back.
My language is not in the dependency-graph list.
File locking still works exactly the same — every write is claimed and released. Only the blast-radius edges are missing, so /.relay will not know that editing your file affects someone else's.
What is next?
- Git worktree isolation — every agent in its own workspace off the same repository, free to experiment and test independently, integrated back into main when the work is ready.
- Richer conflict resolution on top of the existing OT / patch layer.