Smells of code, facts, numbers, and dreams.

It was a routine dependency update.

You know the kind. Lock file refresh, a few minor versions bumping, the sort of thing that takes twenty minutes and earns you exactly zero praise because nothing visibly changed. You do it anyway because unpinned dependencies are a debt with compound interest and one day the bill arrives at 3am.

This time the bill arrived at 10am. Which is almost worse because there is no heroic story in it.


The Excavation

FastAPI 0.137.0 shipped June 14, 2026. The release notes called it a refactor. Internally, include_router() stopped copying child routes into a flat list and started storing them as a single _IncludedRouter wrapper instead. Architectural improvement. Cleaner. More flexible. And silently catastrophic for every piece of code that walked app.routes and expected to find .path, .name, or .endpoint on each entry. Because _IncludedRouter has none of those. It just sits there, opaque and unhelpful, like a coworker who technically attended the meeting.

The minimal reproduction was three lines:

from fastapi import FastAPI, APIRouter

sub = APIRouter()

@sub.get("/items")
def items():
    return {}

app = FastAPI()
app.include_router(sub, prefix="/v1")

[route.path for route in app.routes]
# AttributeError: '_IncludedRouter' object has no attribute 'path'

OpenTelemetry went down. Prometheus went down. DataDog went down. ElasticAPM went down. vLLM went down. All of them throwing the same AttributeError: '_IncludedRouter' object has no attribute 'path' within days of each other, filed in separate repositories by people who had no idea they were all standing in the same hole.

We were in the hole too. ElasticAPM stopped seeing routes on the next image rebuild. No warning. No graceful degradation. Just silence where observability used to be.

So I asked the AI to fix it.

What came back looked serious. Helper functions. Type hints. A recursive iterator that walked nested routers with the posture of someone who had definitely read the documentation. Unit tests — always a good sign that something is either very right or very wrong. Exhaustive for loops scanning route types, checking attributes, falling back, retrying.

It was archaeology. The AI was excavating, layer by layer, through a structure it didn’t understand, looking for artifacts that weren’t where it expected them. And every time a dig site came up empty it didn’t reconsider the map. It just dug a new hole next to the old one.

Because nobody had told it that the team who moved the treasure had also published the new map.

FastAPI 0.137.2 shipped iter_route_contexts() — a public API that replaced every reason to walk router.routes directly. The solution existed, documented, tested, announced. The people who introduced the breaking change left a ladder out of the hole, as they should, because in software you don’t remove things. You change them and you leave an escape hatch.

from fastapi.routing import iter_route_contexts

def _build_route_handler_map(app: FastAPI) -> dict:
    return {
        route.endpoint: route
        for route, _ctx in iter_route_contexts(app.router)
        if hasattr(route, "endpoint")
    }

The AI hadn’t found the ladder. It had the throttle pinned and it kept going — generating, iterating, producing. Checking speed. Never checking direction. The bomb stays quiet as long as you keep moving and the bus never slows down long enough to ask whether it is still on the road.


The Complainer

I stopped the excavation. Cleared the context. And wrote a completely different kind of prompt.

Not “fix this.” Not “here is the error, here is the stack trace, here is the relevant code.” Something closer to an intervention. Stop. Before you touch anything, assume you know nothing about this version. Your training data is a photograph. This error exists because something changed after the photograph was taken. The people who changed it also wrote about it. Go find what they wrote. Read the changelog. Read the migration guide. Read the GitHub issues. If a credible developer published something about it, read that too. The source of truth is not your memory. It never was.

What happened next was immediate. Instead of generating it searched. Instead of producing code it came back with sources. The iter_route_contexts() API. The 0.137.2 patch notes. The GitHub discussion thread where the maintainers of five different observability libraries had already compared notes and documented the fix in public.

The solution, with the right information, took minutes.

I liked what had just happened enough to give it a name. I built a reusable prompt skill — something I could invoke whenever the AI was touching version-sensitive code, a library update, anything where a breaking change might have rewritten the rules after the model’s knowledge was frozen. I called it The Complainer.

The Complainer is not polite. It does not accept “this should work” as a conclusion. It asks for proof. It demands sources — official documentation, changelogs, developer blogs, GitHub issues filed by people who already bled on the same problem. It challenges the solution before the solution exists. It is the skeptic in the room that every engineering team needs and most AI sessions never have.

It worked. Consistently and well.

Until the codebase grew. Until the conversation grew. Until the tasks got layered enough that the context window started filling up with code and plans and history, and The Complainer instruction — sitting somewhere above all of it — became a whisper in a stadium.

The model didn’t forget it. That’s the part that matters. It knew the rule was there. But every new prompt rings the bell. Pavlov understood what happens next — the dog doesn’t deliberate, it salivates. The bell rings and the response is already on its way before any decision was made. For an LLM the bell is a token sequence that matches strongly against training data, and the saliva is a confident, well-formatted answer that arrived before anyone stopped to ask whether it was right. The model cannot not respond. Probability is not a preference. It is the engine, and the engine runs on every single prompt, whether you have written seventeen directives above it or none.

The Complainer told it to go check. The bell told it it already knew. The bell rang first. It always rings first.


Schrödinger’s Review

At some point you decide the problem is not the code generation. The problem is the absence of a second opinion. So you add a reviewer.

The coding agent writes. The review agent reads the diff, flags issues, suggests improvements. You fix the legitimate ones, argue with the false positives, ship. In theory this is quality control. In practice it is a subscription service for second-guessing.

The review agent operates with a smaller context window than the coding agent. Not by accident — by design. It sees the diff. The changed files. The surrounding context. Not the entire codebase, because that would be expensive and slow and in theory redundant, because a good reviewer works from what changed.

A good human reviewer, faced with something unfamiliar in a diff, opens the file. Reads the class. Checks the defaults. Forms an opinion after looking at the evidence.

The review agent found a class with a boolean flag that wasn’t explicitly set in the initializer. It filed a bug. Confident, well-formatted, complete with a suggested fix. The flag, it said, defaulted to False — meaning the feature was silently disabled unless you remembered to pass Enabled=True explicitly.

The default was True. Had been True since the class was written. Documented as True. The review agent had not checked the class definition because the class definition was not in the diff. It assumed — based on the entirely reasonable general principle that unset boolean flags tend to default to off — and produced a bug report for a bug that did not exist.

The bell had rung. “Unset boolean flag” matched a high-probability pattern. The answer arrived before anyone went to look.

This is Schrödinger’s code review. The bug exists and does not exist simultaneously — until someone opens the box and checks the actual class definition. The AI didn’t open the box. You now have to. Which is fine, except that you hired a reviewer precisely so that you wouldn’t have to open every box yourself.

The ratio, across enough review cycles, settles at roughly sixty percent false positives or overstated concerns, forty percent legitimate issues. The forty percent is real and worth catching. The sixty percent costs a round of back-and-forth each time — a prompt, a response, a clarification, a resolution. Tokens. Time. Money. Multiply by the number of review rounds and you are no longer doing code review. You are playing a game that neither side can win because one side is never embarrassed about being wrong and the other side is paying per move.

So you try the obvious next step. Before the review agent sees the diff, you ask the coding agent — the one with the full codebase, the architecture document, the complete context — to review its own code. Pre-flight check. Catch your own mistakes before the reviewer does.

It catches some things. Fixes them. The PR goes up.

The reviewer still finds bugs.

The coding agent, with everything available, reviewed its own work and missed things. And then — somewhere in the same codebase, two hundred lines above where the new code landed — the exact same problem had already been solved. Same pattern. Same edge cases. Same logic, sitting quietly in the same file the coding agent had access to the entire time.

It hadn’t looked. Why look when you can generate? The bell rang the moment the prompt arrived. The answer was already forming. The existing solution, two hundred lines away, was never going to compete with that gravity.

The Indiana Jones whip doesn’t fight the abyss. It catches you before you fall into it. And the only reason you catch any of this — the false bug, the redundant solution, the self-review that missed its own mistakes — is that you are the one holding the whip. Not because you are faster. Not because you generate better. Because you are the one who still goes to look.


The Architecture of Lazy

This is not going to get better in the way people mean when they say things are going to get better.

Every model release comes with benchmarks and the benchmarks always go up and the narrative is always that the problems of last year are solved and the problems of this year will be solved by next year. And on many dimensions that is true. But the laziness is not a benchmark problem. It is not a prompt engineering problem you can solve permanently with the right system message. It is not something more training will eventually correct.

It is the architecture.

Transformers generate the most probable next token given the context. That is not a flaw in the design. That is the design. And the most probable next token is almost never “let me go check if this has already been solved” — because that thought does not look like any training signal the model was ever rewarded for producing.

You can build a Complainer. It works. Until the context gets heavy enough that the instruction drowns in probability. You can add a reviewer. It catches things. Until the diff is too narrow and the bell rings on a pattern that isn’t there. You can ask the coder to review its own work. It finds some issues. Until the same engine that wrote the code reviews it, and the bell rings again at review time, and the most probable self-assessment is that the code is probably fine.

You cannot fix this. You manage it.

What a senior engineer actually does in this workflow is not write faster. Not find solutions faster. The AI has both. What the senior engineer does is watch the speedometer.

In Speed, the bus cannot drop below 50 miles per hour or the bomb goes off. The AI has the same problem in reverse — it cannot stop generating long enough to ask whether it should. The throttle is always pinned. Your job is not to slow it down. Your job is to keep it below the tipping point. To know when the road curves before the bus does. To hold the reins — not to stop it, but to stop it just before the edge.

You set the tone before the session starts. You define the architecture before the code is written. You review the plan before the plan becomes code. And then you read the output with the attention that comes from having been responsible for things that broke — not in demo conditions, not in a test suite, but at 3am when the logs are lying and the traces are missing and the customer can’t do the thing they pay you to let them do.

That attention has no token representation. It lives somewhere the context window doesn’t reach.


The Bus

Lloyd and Harry solved the kidnapping.

If you haven’t watched Dumb and Dumber you have lived a sheltered life and I respect that, but here is what matters: against all probability, with judgment that would struggle to navigate a revolving door, they cracked the case. They got there. They showed up.

And then, standing on the side of the road somewhere in Colorado, a bus pulled up. Full of bikini models. The driver leaned out. They needed two oil boys for their upcoming tour. It was, objectively, the single greatest opportunity ever extended to Lloyd Christmas and Harry Dunne.

Lloyd thought about it for approximately one second.

“You’ll have better luck in the next town,” he said.

The bus pulled away.

The AI ships the feature. It works. Tests pass, reviewer signed off, sprint closes green. And then production arrives — real load, real users, real edge cases nobody thought to test — and the AI has already moved on to the next prompt. It doesn’t recognize what just pulled up. It solved the thing. That’s the end of its relationship with the thing.

You recognize it. You know what the bus looks like because you have watched buses pull away before and you know exactly what it costs when you wave them to the next town.

That is not a skill that fits in a system prompt. That is not something The Complainer can teach. It is the residue of every production incident, every 3am page, every postmortem you wrote where the root cause was something nobody thought to verify because the AI said it was fine.

In an industry that has spent three years arguing about whether engineers will be replaced by the thing that just waved away the bus, knowing what the bus looks like is the only thing that actually matters.

Not because you write more. Because you know when to stop.

And because when the abyss opens up between what the AI confidently produced and what production will actually do to it, you are the one holding the whip.