Or: How I Learned to Stop Worrying and Embrace Probabilistic Done


There was a rhythm to it.

A customer needed something. We’d sit down, think about it for a bit, maybe squeeze a refactor or a dependency update alongside — the kind of housekeeping you can only do when you have breathing room. Two weeks of development. One week of QA, bug fixes, regression. Then a release. Two or three features at most, sometimes one big one, wrapped in a short set of release notes that someone actually read.

It sounds slow when I write it out like that. It wasn’t. It was deliberate. You’d write code, then wait for review, and in that waiting — that apparently wasted time — you’d think. Did I miss something? Is there a cleaner way to do this? You’d open the PR one more time, hunting for forgotten debug logs or that comment you left to yourself that reads like a cry for help. You’d walk a tester through the feature, which forced you to explain what you’d built, which occasionally revealed that what you’d built made no sense whatsoever. Then you’d release, watch the metrics, and actually have time to respond if something caught fire.

That was the process. It had slack built into it. Turns out, slack is load-bearing.


The moment the game changed

I resisted AI coding tools longer than most. Not out of principle — out of experience. I’d seen too many “game changers” that changed mostly the way we described things in stand-ups.

Then I tried the planning stage workflow. Not asking the AI to build something. Not “write me a function that does X.” Instead: give it the problem, ask it to plan, review the plan, push back on it, and only then implement.

That was different. That wasn’t autocomplete at scale. That was something that could hold context across an architecture decision, challenge its own assumptions, and produce something you’d actually read before running. It felt less like a tool and more like a very fast, somewhat overconfident colleague who’d read everything and slept never.

Velocity started climbing. Then it kept climbing.


Continuous delivery without the continuous delivery

At some point, teams stop counting by sprints. The cadence just stops making sense. You’ve turned into CD — continuous delivery — without actually having the CD pipeline to support it. Multiple features, across multiple products, moving in parallel, each one “done” on its own schedule, all of them heading toward a release process designed for monthly cycles being used weekly.

The old process had a name for each stage. Now the stages blur together. A feature is done on Tuesday. Or is it? It needs QA. QA has a queue. The queue has four other features in it. Two of those features touch the same service. One of them has a dependency on a third service that hasn’t finished yet. The release process for that third service was named and owned by a different team, with a different cadence, and nobody updated the runbook when the pace changed.

Nobody broke the pipeline. It got outgrown so fast it didn’t notice until things started backing up.


Do I own this code?

Here’s the question I didn’t expect to be asking myself.

I look at a PR and I recognize the design patterns. I recognize the architectural decisions. I set those standards — they’re mine. But the style is different. The way the code breathes, the shape of the solution, the particular way it handles the edge cases. I know it came from me, in the sense that I wrote the prompt and reviewed the output. But did I write it?

Yes and no.

Code review hasn’t disappeared. It’s gotten harder. Before, you knew your colleagues’ styles. You knew who wrote defensive code and who wrote optimistic code. You knew whose tests were thorough and whose were ceremonial. That context made review fast and useful. Now you’re reviewing code that doesn’t have a human fingerprint, and your reviewers are developing review fatigue doing it at scale.

The solution some teams have landed on: use another agent to review the agent’s code. This works, technically. What I didn’t expect is that agents are more judgy than people. A human reviewer, burned out after four PRs, starts letting things through. The agent never gets tired. It will find every pattern deviation, every missing type hint, every docstring that could be more descriptive. Code review went from a sometimes-painful human ritual to a relentless automated tribunal.

There are worse problems. But it’s strange.


The human bottleneck didn’t go anywhere

Speed up the developers and you immediately find the next constraint. It turns out the next constraint is every human in the process who isn’t a developer.

Before, a stakeholder would write a feature request. Maybe a short document, a few bullet points, enough for an architect and a developer to take it from there. That was fine because the feature arrived roughly when they were ready for it. Now features arrive faster than the organizational machinery can process them. Someone has to manage delivery expectations. Someone has to track what’s in progress, what’s done but unreleased, what’s released but not communicated to the customer yet. Someone has to write the release notes. Someone has to keep the test cases current.

None of that got 10x faster.

What’s interesting — and slightly unsettling — is what happened to the non-developers in response. Managers started using AI to design features. To write specification documents. Some started writing code. The boundary between “person who decides what gets built” and “person who builds it” is blurring in ways nobody planned for. It’s not a bad thing necessarily. But it’s creating role anxiety on both sides. Developers wonder if they’re becoming prompt engineers. Managers wonder if their value still exists when anyone can prototype something in an afternoon.

Nobody has answered that one yet.


Feature flags stopped being safety nets

Feature flags used to mean one thing: release something, and if it breaks, close it while you fix it. A circuit breaker. A coward’s rollback. I say that with affection.

Now feature flags are how you ship at all. You guard everything behind a flag until all the dependent services are ready, until QA has signed off, until the stars align. Then you open the flag, test in production like it’s 2015, and plan to remove the flag in the next cycle.

Except you don’t always remove the flag. Because the next cycle is already here and it brought friends. So the flags accumulate. And now you need to track which flags exist, which are permanent, which are temporary, which are tied to which release, who owns them, and — here’s the part that will make you question your life choices — some teams are still using environment variables for this. Environment variables that require a service restart to take effect. In 2026. Managing what used to be a monthly release process, now running weekly, on infrastructure that predates the problem.

We told ourselves feature flags were protecting the product. They are, somewhat. But honestly? They’re covering the pace. They’re covering the fact that we’re generating code faster than we can ship it cleanly, and flags are the duct tape that holds the deployment process together while we figure out what a new process should look like.


What we actually do

I’ll be honest about what the adaptation looks like in practice, because the honest answer is: not much of a system yet.

Some people write everything down. Every feature in motion, every dependency, every flag that’s open, every communication that hasn’t gone out yet. Some people run it in their head, which works as long as you stay sufficiently anxious. Anxiety, it turns out, is a form of state management. Not a recommended one, but it ships.

The teams that are handling this best have done two things. They’ve accepted that deployment and release are now completely separate events — something gets deployed when it’s ready, and released when the organization is ready — and they’ve built tooling around that distinction rather than working around the absence of it. And they’ve been ruthless about flag lifecycle. Flag goes in, flag has an owner, flag has a ticket to remove it, flag gets removed. The ones who skipped the last step are drowning in flags.

Everything else is still in flux. The process for this hasn’t been written yet. We’re all writing it simultaneously, in production.


Done is a probability now

A year ago, done meant done. Feature was complete, tested, reviewed, signed off, released, monitored. A deterministic state. You knew when you were there because everything that needed to happen had happened.

Now done means: I am certain enough that this can be delivered.

Not certain. Certain enough. It’s a different claim. It’s probabilistic. There’s a confidence interval somewhere in the sentence even if you don’t say it out loud. The code is good enough. The tests cover the cases we thought of. The flag is set correctly for the environments we tested. The dependent service is probably ready. The QA team will probably not find a blocker.

This isn’t a failure of rigor. It’s an honest description of what we’re operating with. The pace is real. The output is real. The trade-off — from deterministic delivery to probabilistic delivery — is also real, and the sooner we stop pretending otherwise, the sooner we can build processes that actually account for it.

We changed from certain to certain enough. From deterministic to probabilities. That’s not a step backward. It might even be more honest than what came before.

But someone needs to write the new runbook.

We’re still waiting on that. The AI offered to draft it. We’re reviewing the plan.


Between Systems is a blog about what distributed systems look like after many years of building them — including the parts nobody puts in the architecture diagram.