Ongoing
How I build with AI
The model is the least interesting part. What makes AI-assisted work hold up is the written context around it and the gates underneath it.
- Role
- Personal practice
- Timeline
- 2025 – present
- Stack
- ClaudeCursorCLAUDE.md / AGENTS.mdADRs
Context#
Most of what I build now goes through an AI-assisted loop. That sentence is worth very little on its own, because it is currently true of most people who write software, and because the interesting variance is not in which model you use.
The variance is in what the model knows when it starts, and what has to be true before its output is allowed to count.
Constraints#
Working this way fails in a small number of predictable ways:
- Context evaporates. Every session starts from nothing. Decisions you made last week get relitigated, silently, in the direction of whatever is most conventional.
- Plausible is not correct. Generated code reads well. That is the entire problem — it removes the surface cue you were unconsciously using to decide what deserved a second look.
- Volume outruns review. It is trivially easy to produce more change than you can honestly check, at which point "I reviewed it" quietly becomes "I skimmed it".
- Conventions drift. Nothing enforces the thing you decided unless something enforces it.
Each of those is a context problem or a verification problem. Neither is solved by a better model.
Decisions#
The brief lives in the repo#
Every project carries a written brief — CLAUDE.md, AGENTS.md, whatever the
tool reads. Not a description of the code, which the code already provides, but
the things the code cannot tell you: who this is for, what the constraints are,
what is deliberately out of scope, and what the voice is supposed to sound like.
The test I use for whether something belongs in it: would a competent stranger get this wrong by default? If yes, write it down. If the answer is derivable from the repo, leave it out — a brief that repeats the codebase is a brief that goes stale.
What I turned down: keeping context in the chat. It works, right up until the session ends, and it means the context is owned by whoever happened to be typing.
Decisions get written down as decisions#
The brief holds the constraints; a decision log holds what was chosen and what
was rejected. This site has a docs/decisions/ directory for exactly that — one
file per decision, each naming the alternative that lost and why.
The rejected alternative is the load-bearing half. Without it, every future session — mine or a model's — re-proposes the thing you already considered, and you have to remember why you said no. With it, the argument is already made.
What I turned down: commit messages as the decision record. They record what changed, at the granularity of what changed. Nobody reads them to find out whether Vercel was considered.
Gates, not vibes#
Anything I claim should be checked by something that can fail.
This site's palette claims WCAG AA in both themes. That claim used to be a
comment in a stylesheet, which is a claim nobody can check. It is now
npm run check:contrast — it parses the palette out of the CSS and exits
non-zero if any text-and-surface pair drops below the threshold. It caught a
real failure the first time it ran, in a colour I had already convinced myself
was fine.
Unconfirmed facts get the same treatment: placeholders are a component, not a
convention, and npm run check:pending will list every one of them.
What I turned down: trusting review to catch it. Review catches things once. A gate catches them every time, including at 1am when nobody is reviewing.
Some things do not get handed over#
Schema design. Anything with a migration path. Any decision I would have to defend to the person paying for it.
Not because a model cannot produce them — it can, fluently. Because those are the decisions whose cost arrives months later, in a form where "it looked reasonable at the time" is not a defence. The rule I actually apply: if being wrong here is expensive and the wrongness would be invisible for a while, I do it myself.
Architecture#
Outcome#
The receipts are the conventions files themselves, in synkt (opens in a new tab) — and in this site, which was built under exactly the process described above and carries the brief and the decision log in the repo.
What it actually buys, in order of how much I value it:
- Decisions stop being relitigated. The expensive part of a session used to be re-establishing why things are the way they are.
- Claims are checkable. "Contrast passes AA" and "no unverified numbers ship" are commands, not intentions.
- Speed, which everyone leads with and which is the least of it. Faster output against a weak review process mostly gets you to the wrong place sooner.
What I'd do differently#
I would have started writing the briefs before I needed them, not after the second time I explained the same constraint.
Every one of my CLAUDE.md files exists because something went wrong twice. That
is a reasonable way to discover which conventions matter, and a bad way to run
the first month of a project — the cost of the first two mistakes is real and the
document that would have prevented them takes twenty minutes to write.
The other one: for a long time I kept the decision log in my head and the brief in the repo, which meant the model had the constraints but not the history. It would propose something reasonable, I would reject it for a reason I never wrote down, and the next session would propose it again. Writing down the rejected alternatives fixed more than writing down the decisions did.