Skip to content

Articles

Practical engineering articles, newest first. Each article lists its publication date, author, categories, and estimated reading time. You can also browse by tag or subscribe to the RSS feed.

New here?

Start with Why I'm Finally Starting This Blog — the first post, on why this publication exists and how it's written.

Better Questions, Not Better Prompts

The advice for getting more out of AI has been remarkably consistent for a few years now. Be specific. Give it context. Define the role. State the constraints. Say how you want the answer formatted. All of that genuinely helps, and I still do most of it.

But when I look back at the sessions where an AI coding assistant was actually useful — where it found the real problem rather than a plausible one — almost none of them turned on the opening prompt. They turned on what I asked next.

I'm starting to think we've been naming the wrong skill.

From Prompt to Platform: Why Modern AI Code Review Needs an Architecture

When most people imagine an AI code reviewer, they picture a single prompt: hand the model a pull request, ask for a review, read what comes back. For small changes, that works surprisingly well — which is what makes the failure on larger ones interesting. As a pull request grows, the model starts losing context. Behavioral defects become harder to find. Security, correctness, architecture, and testing all compete for the same limited attention, and the review quietly becomes shallower even though the model hasn't changed.

I wrote recently about the correctness problem in AI code review, and ended with a suspicion: that the next generation of reviewers would be defined less by larger models than by harnesses built around them. This article is my attempt to describe what I think that harness looks like. Because the limitation I keep running into isn't model intelligence.

It's architecture.

AI Code Review Has a Correctness Problem

Over the past two years, AI code review has gone from novelty to something many developers use every day, and the feedback can be genuinely impressive. Ask a modern model to review a pull request and you'll often get thoughtful comments about readability, naming, architecture, test coverage — the kind of observations that would have taken a human reviewer considerably longer to make.

I've spent the past while benchmarking several leading models against real pull requests, and the longer I looked at the results, the more one observation kept surfacing.

The reviews were often excellent.

The software wasn't.

When "Duplicate Users" Stopped Being a Problem

There was a period in my career when seeing the same business concept appear in multiple systems made me deeply uncomfortable. If I found a User table in one system and another User table somewhere else, my instinct was to assume that something had gone wrong. The same was true for Accounts, Employees, Customers, Sites, and Organizations. Surely there should be a single authoritative service for each of these concepts. If there wasn't, hadn't we duplicated the data? Weren't we creating conflicting sources of truth?

That intuition drove many of my architectural decisions. Whenever a new system was being designed, one of the first questions I would ask was: who owns Users? It felt like the right question, and for a long time I believed it was.

Distributed Systems Are an Ownership Problem

I've been trying to understand why distributed systems felt so much harder than monoliths when I first started building them.

Not technically. I expected them to be technically harder. What surprised me was how much harder they were to reason about. I'd read about eventual consistency, retries, idempotency, message ordering, and distributed transactions, and none of those ideas were beyond me individually — if someone explained a pattern or a technology, I could usually follow along without much trouble. But when I sat down to design a distributed system myself, something felt different. Two experienced developers could look at exactly the same problem and arrive at completely different architectures. Both could sound reasonable. Both could work. Yet somehow one solution would almost always prove easier to evolve a year later, and for a long time I couldn't explain why.

I thought I just needed to learn more technology. Now I think I was trying to solve the wrong problem.

The Day It Felt Like AI Had a Conscience

I've been thinking about a conversation I had with an AI recently. Not because it generated brilliant code. Not because it hallucinated. And not even because it refused one of my requests. I already knew AI systems could refuse things.

What surprised me was something else. For the first time, I found myself arguing with it. Not about code. Not about facts. About right and wrong. Somewhere in the middle of that conversation, I stopped feeling like I was talking to a tool. It felt like I was talking to something that believed it had a moral obligation. That realization has stayed with me far longer than the request that started it.

The Work Happens Before the Prompt

Every week I see another discussion about becoming better at AI.

People compare models. They trade prompt libraries. They debate whether Claude writes cleaner code than GPT, or whether a particular workflow saves a few more minutes.

Those conversations are useful, but after spending the past year working with AI on real software projects, I keep coming back to the same conclusion:

I don't think the biggest productivity gains come from prompting.

I think they happen long before the first prompt is ever written.

Why I'm Finally Starting This Blog

I've wanted to write for a long time. Not because I wanted to become a blogger, build an audience, or establish myself as a thought leader — quite the opposite, actually.

Over the past twenty-plus years, I've had the privilege of working on some fascinating engineering problems. I've learned from incredible mentors, worked alongside talented developers, made more mistakes than I can count, and slowly developed opinions about software architecture, engineering culture, leadership, DevOps, AI, and what it means to build systems that last.

I've always wanted to share those experiences.

Semantic Versioning for APIs and Libraries: Why CI/CD Should Produce Versioned Releases

Ask most teams what their CI/CD pipeline does and you will hear some variation of "it runs the tests and deploys to production." That answer describes deployment automation, and deployment automation is genuinely valuable — but it is only half of what a pipeline should do. The other half is producing releases: repeatable, traceable, versioned units of software that you can point to, reason about, promote, and roll back. For APIs, libraries, and anything else with downstream consumers, the difference between "we deploy" and "we release" is the difference between a pipeline that moves code and a pipeline that communicates intent.