How I work with AI

I use AI tools every day. The commits are still mine. This page is how that works: the split, the flow, the checks, and the stops.

Before I buildbriefread firstplanattack the planstopI decideWith mebuildattack the changecold reviewrun it for realcheckscommitBefore I buildbriefread firstplanattack the planstopI decideWith mebuildattack the changecold reviewrun it for realcheckscommit

The split

What stays with me: the architecture, the scope, whether a thing should exist at all, reading every change, testing on the real thing, anything irreversible, and every word that goes out under my name.

What the tools do: read and gather, reproduce a bug, review a plan or a change from several angles at once, run the mechanical checks, drive a device, and write the first draft of almost anything, which I then edit or throw away.

The agents I use for review are read-only. Not one of them can edit a file. That is a setting, not a hope.

How a piece of work moves

  1. It starts as a written brief. Even for myself. Writing it is where I find out what I want.
  2. Read first. Before anything changes, the tool reports back what is there. If reality contradicts the brief, reality wins and the brief gets corrected.
  3. A plan, with the files it will touch, what it will not do, the edge cases and the way back.
  4. The plan gets attacked. Two separate readers, one asking “is this true?” and one asking “should this exist?”, neither shown my reasoning.
  5. A stop. Nothing is built until I have read the plan and the attacks and said go. “Sounds good” is not go.
  6. Build on a branch.
  7. The change gets attacked the same way, plus a cold reviewer who sees only the diff and one line of intent.
  8. Run it on the real thing. A device, a browser, the actual data. Not a summary of what should happen.
  9. Mechanical checks before anything can be pushed: the linters, the formatters, the boundaries the architecture forbids crossing.
  10. A report: the cause, the edges handled and skipped, the review rounds, what was run and what was not. I read it. I commit. I push.

The one-line version: the tools produce plans, reviews and drafts. I produce commits.

The brief

The shape is nearly the same every time, because the shape is what keeps me honest.

  1. Where we are. List these files. If any is missing, stop and say so.
  2. Read every file before changing it. If the brief is wrong about the code, follow the code and say so.
  3. Why this exists, with real numbers from a real run.
  4. Read first: what to report back before changing anything.
  5. Numbered tasks, each with its own gate.
  6. Verification. Paste real output. State plainly what you did not run. Do not describe expected output as observed.
  7. Tripwires. Run it twice. If the second run does the same work as the first, something is broken, and that is a defect to fix before saying done.
  8. A summary that separates what was verified by running from what was reasoned about.
  9. Out of scope. Ten to fourteen things not to do. Every brief has this list.
  10. The last line, almost always: do not commit.

I mark my own uncertainty inside the brief. A section will say this part was inferred from logs and should be corrected if the code disagrees. Another will say a path is a guess. A brief that pretends to certainty gets certainty back, and that is how mistakes get confident.

What I have learned about review

Reviewers who are told the author’s reasoning return nits. The one who is told nothing finds the defect. I measured this on real changes. So at least one reviewer per change gets only the diff and the intent.

Correctness rounds only ever add guards. Nobody is assigned to readability unless you assign them. So correctness and simplicity are separate reviewers with separate mandates. I added the simplicity one after noticing that three rounds had wrapped a hundred and seventy lines of guards around one line of feature, and the right question was why the feature existed. Deleting it removed all hundred and seventy.

Agreement between two agents is not evidence. Two independent reviewers once reported the same blocking bug, one claiming to have executed it. Both were wrong. They shared a wrong model.

Rules phrased as questions clear themselves. “Did you consider bad input?” is answered by the same model that wrote the code. So my pre-push check prints facts about the change instead: the function that parses outside input and has no boundary test. It never blocks. Judgment belongs to the reader.

A green test suite is not proof. A test written from what you believe the code does agrees with the code whether or not either is right. Run it, look at real output, then write the assertion.

Verify the dismissals too. Reporting a finding costs a full trace. Dismissing one costs a glance. So every miss is an unverified negative until someone traces it.

The stops

Some things stop the work until I have looked, every time, no matter how small.

  • Anything irreversible: a push, a deploy, a migration, a deletion, a message sent.
  • Anything under my name: a commit message, a review reply, a support answer, a post.
  • A change to the scope, or a discovery that the plan was built on a wrong premise.
  • A defect found while doing something else. Report it and stop. I decide what gets fixed.
  • A go-ahead that is about to be stretched. Any go I give expires with the session it was given in.

Six times I said no

Offered a feature that would answer my open questions for me and only escalate the irreversible ones, I wrote: so it will answer the questions for me? No, I think I should leave it as it is. The questions are the job.

In a brief, before any work started: do not fix any defect you discover. Report it and stop. I decide what gets fixed.

Someone using a tool I built flagged that its generated output had invented a detail about a person that was never true. I traced it rather than reprompting. Under rate-limit pressure the intermediate summaries had been truncated, so the last step had nothing to work from and made something up. The fix was structural: a mandatory summary format with named entities, quoted text and an explicit “uncertain” section.

The tool measured which parts of a system affected an output by tracing what ran. I rejected the method. Dynamic tracing under-approximates: a branch not taken is invisible, so an empty result is ambiguous by construction. I replaced it with a static import closure and a diff, because over-inclusion costs an occasional reset and under-inclusion costs a contamination nobody can detect.

After several review passes the tool concluded static review had hit diminishing returns. I ran the build on a phone and found the opposite: a list that stayed empty for about seven and a half minutes, then filled a second after a reload. I asked for logging at each step so we could watch the flow instead of arguing about it.

Given a broad go-ahead early in a session, an agent pushed a branch to a shared repository to get CI feedback. I noticed from outside the session and asked in one line why anything had been pushed. Two minutes later the line was drawn: commits allowed, pushes not. The next morning I changed my rules so that any go-ahead expires with the session it was given in.

When a model stalls

A generation step kept hanging: thirty minutes of silence, then a timeout. My first instinct was to reach for a bigger model with more effort. The diagnosis was that the input was larger than the context window, so no setting could fix it, and a bigger model would hit the same wall at five times the cost. I chose the refactor: split the work, summarise per batch, synthesise once. Wall time halved and the output was better grounded.

When a model stops making progress I change the shape of the task, not the size of the model. Usually the model was never the problem.

What this page does not say

It does not say AI writes my production code. Every change goes through the process above and through human review, and I commit it. It does not say AI built this site. It says I use the tools every day, under rules I wrote, and that the parts that matter stay with me.