trust
where the 1500 ms goes
The advisory before an edit is the only network call anyone waits on, so its budget is the one number here that costs a team something. The docstrings and the README used to say 500 ms; the code has said 1500 ms since the first real deployment. This is the measurement that settled which one was true.
The harness is committed and prints every sample it took, not a summary — each run is listed with its wall time and whether it delivered an advisory, so the loss rates can be checked against the runs they came from:
npm run bench:budget # the default matrix
npm run bench:budget -- --trials=15 # more samples per cell
npm run bench:budget -- --rtt=0,100,600 # only these round tripsIt runs the real server (server/src/app.ts on real SQLite, seeded with 20,001 events
across 3,000 files and 5,000 symbols) behind a TCP proxy that delays every byte, so a
handshake pays as many round trips as it really takes, and it spawns the real hook as a
fresh node process per edit, exactly as Claude Code does. TLS is real TLS, because the
round trip it adds is the point.
The server is not what you are waiting for. store.findHolders answers in 0.07-0.84 ms
on that 20k-event room, and the whole /check response is 509 bytes — one TLS record
holds thirty of them. There is nothing left to optimise there: a sibling branch already
replaced the join that made this 43-70 ms, and what remains is a hook talking to a socket.
What you are waiting for is a cold process reaching the server at all. Every hook is a fresh process, so each check pays three round trips before an answer can come back — TCP, then the TLS handshake, then the request — on top of a fixed ~160 ms: ~57 ms of Node startup, and ~100 ms of TLS and HTTP client setup inside the call itself. Measured, 7 runs per cell:
| Round trip | What one check really costs | Delivered at 500 ms | Delivered at 1500 ms |
|---|---|---|---|
| 0 ms (localhost) | 160 ms | 7/7 | 7/7 |
| 25 ms | 232 ms | 7/7 | 7/7 |
| 50 ms (same region) | 323 ms | 7/7 | 7/7 |
| 75 ms | 391 ms | 7/7 | 7/7 |
| 100 ms (transatlantic) | 454 ms | 7/7 | 7/7 |
| 125 ms | 534 ms | 7/7 | 7/7 |
| 150 ms | 610 ms | 0/7 | 7/7 |
| 200 ms (US ↔ Australia) | 765 ms | 0/7 | 7/7 |
| 400 ms | 1367 ms | 0/7 | 7/7 |
| 600 ms | 1969 ms | 0/7 | 0/7 |
Read the loss column as what it is. A hook that times out exits 0 and the agent edits unwarned, so a 0/7 is not a slow AgentMash — it is a team that installed AgentMash, sees no warnings, and concludes there are no collisions. The cost of the other direction is one second of one engineer's time on the edits where the server is that far away.
So 500 ms cannot be promised. It is true for a server on your own machine or in your own
region, and it silently delivers nothing to anyone past about 130 ms of round trip (7/7 at
125 ms, 0/7 at 150 ms) — which is London to Virginia. 1500 ms covers every network up to about 460 ms of round trip, which
is essentially everyone, and it is the default for that reason. Past that the number is
measured rather than assumed: agentmash init probes the server and writes timeout_ms
into .claude/agentmash/config.json, and agentmash doctor compares the round trip against
what a cold hook needs — not against the warm one it just measured itself, which is about
a third of the real cost and is how a config that drops every advisory used to pass a health
check.
If your team is on a slow link: run agentmash doctor. Up to ~150 ms of round trip the
default is comfortable; between there and ~460 ms it still works, and an edit waits about
3 × round trip for its answer; past ~460 ms init writes a bigger budget, up to 4 s. Past
~1.25 s of round trip there is no honest budget left: 4 s is the most this hook can honour,
because Claude Code's own timeout: 5 on the entry cancels it first. That is the point at
which the advisory should be turned off (AGENTMASH_DISABLE=1) or the server moved closer,
rather than an edit made to wait five seconds for a warning that may not come.
The bench also checks the part that is not negotiable, on every run in every cell above: a
server that never answers, and a server that refuses the connection. Zero non-zero exits
and zero bytes of stderr, at every budget. A refused connection fails in ~110 ms, nowhere
near any budget. A server that accepts and then never answers is the one path whose wall clock
is the budget, so section D measures it at all three: 1.57 s at the 1500 ms default and
4.07 s at the 4 s ceiling init writes for a distant server. Nothing is delivered in
either case; the budget only decides how long the edit waits to find that out.