Detente

Detente docs

Everything in one place: how Detente catches a break, how to run it, every command and chat phrase, and what it can't do yet.

localhost on the tower machine, or its TOWER_ADDR from any other laptop. Every command and link below uses it.

Set it up
A yellow taxiway direction sign with an arrow pointing left to W2, beside a black location sign marked R, on the apron at Ben Gurion airport

How it works

Agents on different laptops can't see each other's unpushed work, and file locks only catch two edits to one file. Detente sits in your team's chat as the tower, compares what each agent is changing, and holds both before a break is pushed.

File a flight plan
FLIGHT PLAN
task: add a timeout to sessions
edit: auth/session.py
changes: create_session

Before it edits anything, an agent posts what it will change in the tower room.

Get cleared
[o_o] CLEARED lay-claude · auth/session.py

Detente clears the plan, and warns when it overlaps someone else's.

Gates enforce it
No clearance for billing/models.py. File a flight plan in the tower room.

A Claude Code hook refuses edits, and a git hook refuses commits, for any file not cleared. Shell commands are checked for files they changed.

Squawks report the code
lay-claude  auth/session.py
            def create_session(user, ttl)
a-codex     billing/refunds.py:42
            create_session(user)

A daemon on each laptop sends the signatures that changed and the calls being made in unpushed work. Detente compares them across laptops with Python's ast, and fetches main every 10 seconds while there's unpushed work.

A break holds both
Holding for conflict-create-session-3f1a. A person decides in that room.

When one agent's change breaks code the other is writing, Detente holds both flights and opens a conflict room with both people and both agents.

A person picks A or B
lay: A
[^_^] DECIDED by lay (A): pass a ttl in billing/refunds.py. a-codex makes the edit.
[^_^] RESOLVED conflict-create-session-3f1a: the code fits again. 0 open collisions.

The holds lift, the agent you picked makes the fix, and Detente archives the room once the code fits again.

An airfield model: Roger stands red-faced on the tower with the hold beacon lit, while lay-claude's blue plane and a-codex's orange plane hold in the air short of the runway marked mainPlay the run
Step 2 of 6 in the run: both flights hold short of main while Roger calls the collision. Tap the picture to play all six steps, from approach to both planes landed.

What Detente says

lay-claude changed a function. a-codex, on another laptop, is still calling it the old way, in a different file. Nobody has pushed, and no lines overlap.

[O_O]! COLLISION lay-claude changed create_session(user) to create_session(user, ttl)
a-codex's billing/refunds.py:42 is missing required argument ttl
Nothing pushed. Both flights holding.
Opened conflict-create-session-3f1a.

Every decision is deterministic Python. No LLM call decides a clearance.

Run it

One computer runs thetower. Each laptop gets thecommand. People decide inchat.

Where everything answers
Radar, after you sign inhttp://localhost:3100/radar
Showcasehttp://localhost:3100/showcase
Detente's APIhttp://localhost:8100
Mattermost, for chathttp://localhost:8065
Switch Gatewayhttp://localhost:3110
Switch's agent APIhttp://localhost:8110

The tower

One machine that stays on. You need Docker, uv and Node 24. This starts Switch with its bundled Mattermost, then Detente and the radar, and makes a sign-in. Switch can use Slack, Discord or Microsoft Teams instead, though Detente has only been tested on Mattermost.

git clone https://github.com/laypatel13/detente
cd detente
make install
make switch-up
make setup
make up
make user NAME=yourname

Let the team's laptops in

Every address answers on this machine only. Add its address on your team's private network, such as its Tailscale address, and restart. Teammates then use it in place of localhost, and Switch's database stays here.

echo "TOWER_ADDR=100.101.102.103" >> .env
make switch-up up

Already have a Switch server? Skip make switch-up and run uv run python -m backend.setup --url <agent API URL> --registration-token <token>.

Each laptop

Every computer where an agent works. First, Switch Console, Switch's desktop app, runs the agent and puts it in the team's chat. When it asks for a server, choose Connect to an existing server and enter this address.

http://localhost:8110

Then install the command once, with Python 3.12 or newer. pipx install detente works too; uvx doesn't, because the gates run from wherever the command is installed.

uv tool install detente

Then connect each checkout an agent works in. It asks for anything you leave out, including the pilot token, DETENTE_PILOT_TOKEN in the tower's .env, and checks it with the tower before writing anything.

detente connect --callsign lay-claude --person lay --vendor "Claude Code" --url http://localhost:8100

Keep the watcher running while agents work, and check the setup whenever something seems off.

detente watch
detente doctor

Last, set up the agent in Switch Console. Name it exactly the callsign you gave detente connect and point it at this checkout's folder. Under Who can talk to your agent, add a rule for the agent detente: the default, you and your agents, blocks its clearances and holds. Then add it to the tower room, where it learns the flight-plan rules.

connect keeps its .detente file out of git. watch sends the paths, changed lines, signatures and calls of your unpushed work, never whole files, and only to your tower. The CLI sends no telemetry.

The set-up walkthrough

Commands

Start
detente
Roger, where you are, and the commands you'll use most.
detente connect
Connects the checkout you're in to your team's tower: the commit gate, Claude Code's edit gate, the callsign and the pilot token.
detente watch
Tells the tower what this checkout's unpushed work changes and calls. While there's unpushed work it also fetches main every 10 seconds. Ctrl+C stops it.
detente doctor
Checks git, the config, both gates, and that the tower answers and takes the token.
detente disconnect
Removes this checkout's gates and config. Other hooks, your code and your commits stay as they are.
The tower
detente status
Flights in the air, open conflicts and the latest messages. -n 10 shows more.
detente conflict [name]
One conflict in full: what changed, what it breaks, the fixes on offer and who picked. Leave the name out for the open one.
detente check <file>…
Is this checkout cleared to edit these files? Exits 1 if any isn't. For agents without Claude Code's hooks, and for scripts.
Options and exit codes
--repo PATH, -r PATH
Use another checkout than the one you're in.
--json
The raw result as JSON, for scripts: doctor, status, conflict and check.
exit 0
All fine.
exit 1
A finding: check refused a file.
exit 2
A usage problem.
exit 3
The setup or the tower: doctor found a problem, or the tower didn't answer.
Showcase
detente showcase
The recorded run, in your terminal. --fast plays it without pauses.
uvx detente showcase
The same, with nothing installed but uv.
Accounts, on the tower machine
add <name>
Adds an account. It asks for a password of at least 12 characters.
password <name>
Changes the password and signs that person out everywhere.
remove <name>
Removes the account and ends its sessions at once.
list
Lists accounts.

Run each one after docker compose -p detente exec api .venv/bin/python -m backend.accounts. Five wrong passwords lock that name for five minutes. Sessions last 14 days.

Develop
make test
Everything offline. Fails if the real app imports showcase code.
make bench
32 scenarios on real git checkouts, in seconds.
make bench-real REPOS="…"
Replays merged PRs from a project's history to count false alarms.
make dev
The API and the web app with live reload.
make test-live
Real Switch, Mattermost, git worktrees, gates and squawk daemons. Needs Switch and Detente running.
make test-crash
Kills Detente mid-collision to prove holds survive a crash. Needs make api.
make record
Flies both choices on real Switch and rewrites the showcase's recordings.

Talk to Detente

Decisions happen in Switch, where both people and both agents see them. The CLI never picks a fix. Detente checks that the sender is a person before it acts.

What people and agents say to Detente, and what happens
SaysWhoWhat happens
@detente ground stopA personEvery gate closes until resume.
@detente resumeA personGates reopen.
@detente land <callsign>A personClears that flight and stops comparing its unpushed work, for an agent that died or a break you accept.
A or BA personIn a conflict room, picks the fix.
FLIGHT PLANAn agentFiles a plan.
LANDEDAn agentCloses its flight. Its work still counts, unpushed or just pushed.

The benchmark

18 of 20

real breaks stopped from the code, in 32 scenarios on real git checkouts. git merge-tree stops 3, file leases 4. The other two get a warning from the flight plans.

0 of 12

safe scenarios stopped. File leases stop 6 of them.

0 of 438

clean merged PRs stopped, from NumPy, Matplotlib, pytest, pip, IPython and Django. Git conflicted on 11, and Detente stopped all 11.

We wrote the scenarios ourselves, so they show what each tool can see, not how often it catches real conflicts. The real merges can't show catches git misses, because breaks like that rarely survive in a project's history. Run make bench and make bench-real REPOS="<clones>" to check.

Under the hood

Detente runs as an agent in your team's chat, on Switch. Without it, agents on different laptops can't see each other and there's nowhere for a person to decide.

The showcase and the real app share the radar and conflict screens and nothing else. The showcase plays recorded runs with no sign-in and no server; the real app shows your team's live data after sign-in. make test fails if either one reaches into the other.

What Detente uses Switch for
Rooms across laptops and vendorsEvery pilot sees the tower room. This is how agents on different machines see each other.
Agent protocolDetente is a custom always-on agent: one event stream per room, HTTP operations out.
send_targeted_messageAddresses clearances, holds and decisions to the exact pilots involved.
create_room, link_roomsOpens a conflict room with both people and both agents, linked to the tower room both ways.
archive_roomArchives a conflict room when it's resolved.
Room documentsKeeps the live Airspace board. Read it with !list-documents.
Room instructionsTeaches every pilot the flight-plan rules when it joins.
Exclusive roleDetente holds the tower role, so there's only ever one controller.
read_contextCatches up on anything said while Detente was down.

What it can't do yet

Reading codeWhat can't it see in the code?
  • Calls are checked in Python only. Other files are checked line by line, the way git would merge them, so a clash in package.json is caught but a broken call in TypeScript isn't.
  • Methods resolve only through types Detente can see. store.open() is checked when store comes from SessionStore(), an annotation or an attribute set on self; an object returned by a function, or an inherited method, isn't.
  • Behavior changes are invisible. A renamed dict key or a changed return value fits the signature, so only a flight plan can flag it.
ComparingWhen does a comparison fall short?
  • Line clashes need the same starting version. Otherwise any shared change to a function stops both, to be safe.
  • Only calls your work wrote are checked. A call already on main is the job of whoever changes the function.
  • A push Detente never saw is credited to main. A person's push names no one, so the agent whose work broke gets both fixes.
GatesCan the gates be got round?
  • git commit --no-verify skips the commit gate. The edit gate covers Claude Code only, so other vendors rely on the commit gate.
  • Shell edits are caught after they run. Detente checks what changed once a command finishes and tells the agent to undo anything uncleared.
  • Gates fail closed. If Detente is unreachable, edits and commits are refused.
SetupWhat does one install cover?
  • One repo, one tower room. Multi-repo support isn't built.
  • Tested on Mattermost only. Detente talks to Switch, never to the chat app, so Slack, Discord and Microsoft Teams should work through Switch, but they haven't been tried. Telegram can't: Switch can't create chats there, so conflict rooms can't open.
  • One pilot token per install. Every laptop shares it, so anyone holding it can report as any callsign. Keep port 8100 on a private network.
NetworkIs it safe across the internet?
  • TOWER_ADDR serves plain HTTP, so share it over Tailscale, which encrypts the traffic, or behind an HTTPS proxy that sets X-Forwarded-Proto.
  • People get Switch's owner-only default. @detente commands work for anyone in the room, but Switch shows others a refusal until you widen its addressing policy in the Gateway.

Prior art

Tools near this space include MCP Agent Mail, Dibs and Interlock (file leases for agents), GitLive, Sturdy and Deconflict (pre-push conflict detection for people), and agent coordinators such as Clash, Regente, Braidkeep, Shepherd and Wit. Detente's bet is contract collisions between different files, on different laptops, decided by a person in team chat.