Building with AI

5 August 2026

My $WORK doesn't yet use agentic development tools, but we are (finally) on the precipice. Yesterday we had a discussion of how we should use agentic tools when we are able to, and it inspired me to write this and to have ChatGPT and Claude refine it, so hopefully some of my voice still comes through.

For the most part this is all generally applicable and some friends have asked me if they can share it, so I figured I'd publish it here.

- Eric


AI changed the economics of software development. Producing code has never been cheaper. Understanding how it fits together is more valuable than ever.

We cannot get the maximum benefit, and may not get a net benefit at all, from AI unless we adapt the way we work to take advantage of the new capabilities.

Doing the same work in the same way, only with AI involved, may do little more than increase our token costs and the amount of code we produce.

We should exploit these new capabilities to reduce our existing constraints: limited time, expensive experimentation, incomplete documentation, repetitive work, disseminating understanding, and the difficulty of understanding complex systems.

AI should change more than how quickly we type.

Success isn’t measured by how much AI-generated code we merge. Success is higher throughput while reliability, maintainability, and developer experience stay the same or improve.

These norms aren’t about using AI safely. They’re about using AI effectively without sacrificing the engineering practices that make software sustainable.

Own the consequences of your contributions

AI doesn’t change who owns the result. You are responsible for understanding, reviewing, testing, and maintaining what you contribute, no matter how it got written.

When you follow established patterns, the team can collectively own and evolve the patterns, but more importantly once the code has stabilized, your code becomes our code.

When you introduce a new pattern, you’re taking on the responsibility of proving it belongs, maintaining the code that uses it, and eventually driving the adoption of that pattern.

A new dependency is a bigger commitment than a new pattern. It’s someone else’s code, on someone else’s release schedule, with someone else’s security practices, and it’s much harder to get rid of later. AI will add one without blinking. If a dependency shows up in your pull request, be prepared to defend it.

AI may help investigate production. Production changes should always be initiated by a person who accepts responsibility for the outcome.

Build your mental model

Use AI in a way that causes understanding to accrue to you.

Have a conversation with AI first to hash out the shape of the problem, possible approaches, constraints, and trade-offs. Point it at authoritative reference documentation and examples in our codebase so its reasoning is grounded in the way we work and the patterns we use.

You need to understand the system, the change you’re making, and why the code belongs where it does.

The goal is not merely to produce code that works. The goal is to leave with a stronger mental model than you started with.

A mental model allows you to predict the effect a change will have. Every prediction is an opportunity to learn when reality agrees, or disagrees, with your expectations.

Ship in small batches

Pretend every commit goes straight to master.

Don’t work in large batches and commit everything at the end.

Small changes are easier to review, test, revert, and improve.

Use stacks of pull requests and ship them to production one at a time. In manufacturing the ideal is single piece flow through the assembly line. That is the ideal through development, review, CI, and production, too.

AI makes it easy to create a large change very quickly. That’s a temptation, not a capability.

Not all changes can be small. That is why small batches are an ideal rather than an absolute rule. Strive to make them small and explain why they can’t be if not.

Explore freely, but don’t ship the prototype

Don’t be afraid to throw away code and try again.

One useful pattern is to vibe code a prototype to get a feel for the shape of the problem and then to reimplement it in small, deliberate steps. [1:41 PM]Use AI to explore both the application and the way we build software. Use it to diverge: to rapidly explore many possible approaches. Then use your judgment and the team’s judgment to converge on the one that best fits our system. This is not a one-time process. Creativity itself is iterative: diverge, converge, repeat.

Avoid shipping the prototype. If you must ship the prototype, acknowledge that clearly in the pull request. Explain why, identify the compromises, and commit to making it more robust in future pull requests.

Don’t use AI where you can’t tell whether it’s right

The greatest risk isn’t code that’s obviously wrong. You’ll catch that.

The greater risk is code that looks right in an area where you cannot tell the difference: auth, permissions, money, migrations, hard-to-test integrations, and interconnected business logic.

Be deliberate in areas where mistakes are expensive, subtle, or difficult to detect.

If you couldn’t confidently review this change coming from a stranger, you can’t confidently review it coming from AI.

Use AI to develop your understanding, consult authoritative sources like documentation and the requirements, and talk with the team about the approach before implementation.

Don’t let AI decide what correct looks like

AI-written tests tend to be written against the implementation rather than against the intent. They assert that the code does what the code does.

State the intent in your own words before you generate anything, and write the important assertions yourself.

When a test fails, the test is not the thing that’s broken until you’ve proven it is. Passing tests are evidence of correctness, not the definition of it.

Document intent, not mechanics

Document decisions that won’t be obvious six months from now.

GitHub issues do a pretty good job of explaining the requirements and how we want something to work, but we don’t do a great job of explaining the intent and why the code is the way it is.

Use an architectural decision record when introducing a significant architectural pattern, core service, or piece of infrastructure. See the Public API ADR for the level of detail we’re aiming for.

Not everything needs an ADR. If someone asks you a question in a pull request that isn’t obvious from the code, that’s an indicator that there should probably be a comment.

If we’re doing something repetitive, like creating a domain action, upgrading NodeJS, fixing a persistent bug, or manually setting up an integration, these should be runbooks.

Prompts aren’t documentation. The rationale has to stand on its own without them.

Explain the rationale behind your pull requests when the change is large, the structure is unusual, or the approach is non-obvious.

Encode rules into tooling

AI is nondeterministic. That is useful for exploration. Deterministic tooling is useful for enforcement.

Good tools reduce the amount of discipline humans must apply manually.

Encode conventions in lint rules, tests, and other sorts of tools wherever a rule can express it.

Run these tools in CI to enforce them in a way AI cannot bypass.

If it can’t be encoded into a tool, then consider writing a skill. Keep in mind that new skills have a cost. Trial them in your own dotfiles first and get buy-in from the team before adding it to the repository.

Teach AI our patterns

What AI reads before it generates anything is one of the highest-leverage things we control. If it has to choose between six years of accumulated patterns, it will copy whatever it happens to land on.

Maintain a short list of which patterns are canonical and which are deprecated, as well as GitHub issues for migrating to new patterns. [1:41 PM]“Follow established patterns” and “clean up old patterns” are both good advice and they conflict, because with a codebase this old you often can’t tell which generation you’re looking at. Nobody can. Not a new hire, not AI, sometimes not even us. A written list makes it clear. If you have to figure out which pattern is current, that’s a signal the list needs updating; do that as part of your change.

Clean up old patterns

Our codebase contains six years of evolving patterns.

AI makes it easier than ever to clean up older approaches, so there is no excuse not to follow the Boy Scout Rule:

Leave the code better than you found it.

Every outdated pattern is both debt and an example that the next contributor, or their AI, may copy.

Isolate this cleanup to its own pull request. If it’s especially precarious or embedded, use a stack.

A 400-line “while I was in there” refactor mixed with another change is extremely hard to review.

Write more software

Write more software, but not necessarily more production application code.

Write throwaway programs, migration scripts, debugging tools, benchmarks, tests, visualizations, experiments, and anything else that helps you understand the problem and produce better production code.

AI has made disposable software nearly free to write. Take advantage of that.

Review your own code first

Use AI as a second pair of eyes, not as a replacement for your first pair.

Read your own diff before you ask anyone else to review it.

Have AI challenge it. Look for omissions, edge cases, unintended behavior, unnecessary complexity, security concerns, and assumptions you did not realize you were making.

Do both before spending a teammate’s time.

A reviewer shouldn’t have to know or care which parts were generated. The entire contribution should meet the same standard.

Ask why more often

Ask why of your teammates, of AI, and of yourself.

Why does this code exist? Why is it structured this way? Why is this abstraction here? Why did AI suggest this approach? Why are we handling this case at all?

Asking why creates a dialog. It helps flesh out your mental model. It gets to the bottom of unnecessary code written by humans and AIs alike.

Answers are cheap. Spend that on better questions, not fewer of them.

Communicate with each other

Our bottleneck is already disseminating a shared mental model of the system. AI will stress that bottleneck even more. The more mistakes we don’t all have to make, the better.

Tell each other what you tried, what didn’t work, what did, and what norms we should adopt because of it.

Not having to write every line of code means there is more time to talk to each other. Try to keep it concise and add more context only when needed.

Asking questions signals interest and attention, and it allows humans to load context as they need it. These are powerful tools. Use them.

These norms are provisional. AI capabilities are changing faster than engineering practices, so some of these norms will become outdated. When they do, don’t quietly ignore them. Propose something better. Evolving these norms is part of the work.


I asked AI to make a photo for this blog post and the first thing it made was this handy one pager of these ideas, which was not quite what I was wanting, but also super handy.

Building with AI