Blog
Posts
Short pieces on backend engineering and systems design, from the decisions behind this site to the evidence behind an upgrade, an integration or an agent.
-
What changes when your caller is an agent
An HTTP API for agents is not a website with JSON. The caller has no browser, no cookies and no patience for an error it cannot act on.
-
The outbox is a table, not a library
The dual-write problem is not solved by a gem. The outbox is a row written in the same transaction as the change, and everything after it is delivery.
-
The boundary you cannot move
Some systems cannot be rewritten, paused or reset. The integration is a product boundary, and the work is translation, contracts and reconciliation.
-
Making a green build mean a runnable application
A pipeline that runs tests but never boots the application can be green while the container is broken. The build is a product, and green should mean runnable.
-
An experiment that can say no
A research repo should be able to return "no" and still be useful. Pre-register the decision rule, keep the null arms, and make the claim checkable from committed artifacts.
-
Walking a Rails application from 4.0 to 8.1, one rung at a time
A Rails upgrade is a sequence of small, evidenced moves. The interesting engineering is not the Gemfile edit; it is the ladder as data, the judgement as a decision, and the suite as the only verdict.
-
What an MCP server owes a model
An MCP server is a product surface for a model, not a JSON-RPC wrapper. Annotations, actionable errors and a harness that measures discovery decide whether an agent can use it.
-
Porting a live service one route at a time, then deleting the old one
A stack migration is safest when the old implementation is the oracle, each route moves on its own, and the last step is a deletion rather than a second code path.
-
Upgrading a production Rails application from 3.2 to 5.2, in steps
The framework upgrade is rarely the hard part. What makes a version hop survivable is treating it as scheduled maintenance, releasing each step, and clearing deprecations while they are still cheap.
-
Holding a boundary in a Rails app nobody is going to rewrite
Modularising an application that is not getting rewritten works when the boundary is a failing build rather than a diagram, and when each step is small enough to ship on an ordinary week.
-
The only thing two cells share is a port
Partitioning a world that is larger than memory settles one problem and creates a sharper one: two cells sharing an edge must stay consistent while being unable to see inside each other.
-
How this site is built
A Sinatra app with its content in YAML, shipped in a pinned, unprivileged, read-only container and published through a Cloudflare tunnel. The short version of why each of those was the boring choice.
-
The corpus stopped growing, and every number looked healthy
A background service that keeps cycling, reports healthy and does nothing is the hardest failure to see. The fixes were about what a bound is measured in, and what an empty answer says.
-
A bug report is a differential, not a description
Release notes record what somebody said changed. A corpus that answers upgrade questions has to execute one release against the next, file the interpreter's own refusal, and let somebody else reproduce it.