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.

    PATCH /tasks/7f2a… { "version": 4, "status": "In progress" }
    
    409 Conflict
    { "error": "This ticket changed since you read it.",
      "current_version": 5 }
    Concurrency and polling
  • 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 as updated_since and 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 workspace

Keep reading