← all notes

10 min read Alex

How an agent-run dev shop actually works

Our engineers choose the work and make the calls, coding agents keep it moving overnight, and gates decide what merges. Here is how a normal day of ours runs.

A typical morning here starts with reading, not writing. Overnight a pull request landed for a client’s mobile app, with a before-and-after storyboard of every screen it touched. A Linear issue picked up a comment explaining a root cause, file and line included. An agent stopped and is waiting on one product question.

None of that happened by accident. “Software engineering, at agent speed” is our pitch, and in practice it means three things: people who own every decision that matters, coding agents that multiply how fast those decisions become code, and a harness of gates and guardrails decides what is good enough to ship.

Who does what

Role Does
Coding agents read the code, plan, implement, test, open PRs, review each other, merge once the gate is green on projects that delegate merging
People pick what matters, make product calls, approve what ships and anything costly or irreversible
The harness runs every check, refuses unsafe merges, keeps state on disk and in git

Those calls get made from anywhere: a laptop, a phone, and Neuralink once it’s available to us. Every agent session can be driven remotely, so a decision takes a sentence, not a desk and a free afternoon.

Boxes built from code

The agents work on dedicated boxes, and none of them is precious. Each one is described entirely in a repository and rebuilt from it:

  • Terraform owns each box’s existence: the server itself, an IP address that outlives any rebuild, firewall rules, DNS. State sits in a private bucket, not on anyone’s laptop.
  • Ansible owns everything inside: packages, the toolchain manager, the agent CLI, the private network join, Docker, the libraries a headless browser needs for agents to check their UI, and a clone of every repository with its dependencies already in place.
  • chezmoi owns dotfiles, helper scripts and the systemd units each agent session runs in.
  • age encrypts every secret, committed to git next to the thing that uses it. A fresh box decrypts what it needs during provisioning; nothing is copied over by hand.

Bringing up a new box takes a handful of commands, with terraform apply and ansible-playbook doing the heavy lifting, plus one login. Start to finish that’s about fifteen minutes, and a person is busy for just a couple of them. A second playbook run should be close to a no-op; anything it still changes points to a buggy role, which then gets fixed. Provisioning changes are rehearsed twice in a disposable container before they can merge.

The repository is what’s real; a fix applied only to a running box is drift. A package installed by hand or a dotfile edited in place vanishes at the next rebuild. Agents are held to this like anyone: when something breaks, they ask where in the repo the fix should live, not how to patch the machine in front of them.

So boxes are disposable and identical: if one misbehaves we replace it, and the replacement has every tool, repository and rule the old one had.

Always in motion

An agent session is a conversation, and losing one is losing work. So sessions don’t live in a terminal tab. Each one is its own systemd unit, inside a memory-capped slice, with a detachable terminal underneath. It survives a dropped connection, a closed laptop and a reboot. If it crashes, it comes back as the same conversation, and a start that can’t find its transcript refuses to begin from scratch.

Dev servers are the opposite: stateless and restarted constantly. They get their own supervisor, so restarting a bundler can never take an agent down with it.

That separation is what lets work continue overnight. When a plan splits into streams that are independent, we use a hub: a single session coordinates, and each stream gets a builder of its own, working in a separate worktree from a brief cut out of the plan. Only the hub merges. It reads every PR, lands those that pass, and hands each contract between streams (an API payload, an error code) to both sides before either builds on it. The first time we ran it for real, two builders delivered ten items in nine PRs.

Long sessions need managing too. A hook watches each session’s context size and, past a threshold, asks it to roll over: write a handoff document, start a successor in the same place, park itself. Parked sessions post a clear notice as their last message, so nobody opening a chat mistakes a stopped session for a dropped connection.

One task, one worktree, one PR

Every change goes through a pull request. A one-line typo fix too. Each task gets its own git worktree, branched from a fresh fetch of the default branch, with env files and dependencies set up before the agent writes a line. Two sessions never share a checkout.

PRs follow one anatomy: a short summary, a before-and-after storyboard whenever any UI changed, a few bullets of changes, and a folded section holding the why, the rejected alternatives and the test evidence. The human part stays short. If it doesn’t fit in about fifteen lines, the PR is too big and gets split.

UI work isn’t done until someone has seen it. Agents run the app themselves, click through it, and attach screenshots of each screen and state the change touches, empty and error states included, before it can merge.

Zero friction: written where you’d look

We write as if the next reader knows nothing, because for an agent that’s literally true. So every piece of knowledge has a home, in the place you’d go looking for it, written to be read:

  • Code comments say why something is the way it is: the hidden constraint, the workaround, the invariant. Never what the code already says.
  • A README in every package, app and feature folder: what it is, what it depends on, its public API, how to extend it.
  • Rule files, one home per rule. The instructions every session loads: what holds for every project in one file, what’s specific to a repository in its own, an app’s in the app’s. A rule kept in two places is a bug waiting to happen, because the copies drift apart.
  • Docs and runbooks for how things work and how to do the occasional job, reached from the rule file’s routing table. They change in the same commit as the behavior they describe.
  • Decision records for the choices that shape a project, with their reasoning, so a later reader can tell when that reasoning has expired.
  • Issues carry the analysis, and every decision made after it, as comments. The why outlives the session that worked it out.
  • A ledger for what’s postponed: engineering follow-ups, product questions waiting on a trigger, ideas, and the things only a person can do, each in its own list. Nothing deferred lives only in a chat.
  • Commits are small and say what changed. Pull requests add a short summary, before-and-after screenshots for any change to the interface, and, folded underneath, the why, the alternatives we turned down and the test evidence.
  • Skills package the procedures we repeat, like publishing a post or onboarding a repository, so they’re done the same way every time.
  • Handoff notes when a long session passes its work to a fresh one, so nothing in flight depends on anyone’s memory.

Put together, that trail answers whatever an engineer would ask: why this, why not that, what else it touches, what’s still open. An agent reads it the same way, so nobody ever waits on a person for something the project already knows.

Gates instead of trust

Agents produce a lot of PRs, so the checks run on our own boxes, not on rented CI runners (When CI can’t keep up with your agents). Format, lint and typecheck run on push. The merge gate runs every test and every build, one gate per box at a time, and reports back on the PR as a commit status. Merging is refused unless the PR’s latest commit is green. Full end-to-end suites run at the release gate on clean runners.

Some guardrails exist because something went wrong. A PR once relocated a layout provider; its gate was green and its storyboard looked right, yet nine end-to-end specs broke. Since then, a PR that relocates a layout, provider or navigator has to run the specs it affects before merging. Anything that could spend CI minutes needs a person’s explicit approval, with an estimate attached.

Product mindset, not ticket closing

We don’t want agents that agree with everything. Ours are told to push back: if a request looks wrong, say so with the reasoning, and recommend something better before building. Check what people assert against the code rather than taking it on trust. Point out the missing empty state, the confusing flow, the screen with no way back.

The same goes for the code. Keeping projects clean outranks almost everything else: one way to do a thing, migrations finished rather than left at 80%, every invariant in exactly one place. Agents are fast, which is exactly why this matters: pace multiplies whatever habits a codebase already has.

What our engineers decide

People choose what gets built and in what order. They make the product calls the code can’t settle. They sign off on whatever spends money or can’t be reversed: a change to live data, a new ticket, a release. And they look at the storyboards.

Everything else keeps going through the night without waiting on anyone, and leaves a written trail. When someone checks in each morning, from wherever they are, the work has moved and the questions are waiting.