Engineering
What we learned
letting agents run our board.
The tickets for Laver are worked by AI agents. They take an item from To Do, write the code, verify it, comment on the ticket and move it along — against the same board, and the same API, that everybody else gets. That turned out to be the most useful test the product has had, because an agent finds the parts of an API a person quietly works around.
ONE
Two writers will
meet on one ticket.
The first thing that broke was not the agent. It was a person and an agent editing the same ticket a few seconds apart.
Every ticket carries a version
A write sends the version it read. If anything changed in between, the write is refused rather than applied, and the refusal says what the version is now. The agent re-reads and tries again; the person never finds out their edit was quietly replaced, because it was not.
Concurrency and pollingPATCH /tasks/7f2a… { "version": 4, "status": "In progress" } 409 Conflict { "error": "This ticket changed since you read it.", "current_version": 5 }Last-write-wins is a data-loss bug
It is survivable when writers are people, because people are slow and talk to each other. Agents are neither. The same board with three agents on it turns a rare race into a daily one.
TWO
An error is an
instruction, or it is noise.
This changed how we write every refusal in the API, and it is the single cheapest improvement on this list.
"Something went wrong" ends the run
An agent that reads it has no next move. It retries the identical call, or it stops and waits for a person — and the whole point was that no person was watching.
"Re-read and retry" continues it
Say which field, say what the current value is, say whether trying again could work. Our MCP server passes refusals through verbatim for exactly this reason: a message rewritten to look tidy is a message with the actionable part removed.
THREE
A timeout is not
a failure.
An agent whose request times out does the obvious thing and sends it again. On a create, the obvious thing is wrong.
Two tickets, one intention
The first call had already landed; only the reply was lost. A person notices the duplicate and deletes it. An unattended agent does not, and the board fills up with pairs.
Creates take an idempotency key
Creating a ticket and posting a comment both accept an
Idempotency-Key, so a retry with the same key returns the original result instead of making a second one. Those two are covered because a duplicate there is expensive and the caller cannot detect it afterwards.
FOUR
Never poll
on your own clock.
Following a board sounds like the easy part. It is where the subtle data loss lives.
Clock drift silently skips tickets
Ask for everything changed since your idea of a moment ago and you will eventually miss a ticket that changed inside the gap between your clock and the server's. Nothing errors. You simply never see it.
The reply hands you the next cursor
Every listing comes back with the server's own
server_time; send that back asupdated_sinceand the window cannot drift. It is one field, and it removes a class of bug rather than a bug.Deletions need saying out loud
A delta of changed tickets cannot express a ticket that is gone — absence looks identical to unchanged. So a delta also carries the uuids that left, or every polling client slowly accumulates ghosts.
FIVE
Give the agent
a person's access.
The tempting design is a second permission model for machines. It is a trap, and it is the one we most want to talk other people out of.
Two models diverge
Integration scopes maintained separately from the membership list drift apart, and the drift is invisible until an agent reads something it should never have reached.
A key acts as whoever made it
It holds exactly that person's access and nothing more, and it can be revoked without touching their account. Offboard the person and their agents lose access with them, because there was only ever one model to update.
Agents as colleagues
SIX
Write it down
once.
The worst hour we lost had nothing to do with the API. It was three documents describing the same procedure.
They disagreed where it mattered
One said to merge and not deploy, because another agent handled deployment. The other two said to deploy. Two of them named a command the machine was refusing at the time. An agent following any single one of them stalled at the last step, and none of the three linked to the others.
A person guesses; an agent commits
Somebody reading conflicting instructions asks a colleague. An unsupervised agent picks one and proceeds confidently. Contradictory documentation is not untidy — it is a defect, and it behaves like one. Two of ours are now pointers to the third.
THE POINT OF ALL THIS
None of it is
agent-specific.
Versioned writes, honest errors, idempotent creates, a server-side clock and one source of truth are things a careful API should have had anyway. Agents did not create these problems. They just stopped us getting away with them.
Create your workspaceKeep reading
- The MCP server — point Claude or Cursor at a board in one config block.
- Concurrency and polling — versions, 409s and following a board without re-reading it.
- API reference — every endpoint an agent can call.
- Let an agent run your board — the access model underneath all of this.