The code is the cheap part now. The machine writes it, and it writes it fine. What it can't do - what I don't think it will do for a while - is know when an answer is actually an answer. That's the part I get paid for, and after 35 years I'm more sure of it than I've ever been. It was always the job. Agents just made it obvious.

Something happened this week that made the whole thing concrete.

A bug that wouldn't reproduce

We had a mobile bug that only showed up on the client's device. Consistently. Every time, on their end. And never once on ours - same iPhone 17, same iOS, same build. Not an old handset we forgot to test, not some Android fragmentation story. The exact phone sitting on my desk.

If you've done mobile you know this one. You can't watch it fail. You can't attach to the moment it breaks. Every fix is a guess you ship and then wait to hear whether it held. And because it runs perfectly on your own machine, every comfortable explanation is right there for the taking - must be their network, must be their state, must be something on their end - and every one of them is wrong, because their phone is the same phone.

That's the kind of thing that eats effort. Not because anyone's bad at their job - because the problem hides the one thing you actually need, which is a failure you can see.

I sat down with a coding agent and we had it cracked and validated in a day. Four codebases - backend, web, iOS, Android - two real calls running end to end, four PRs open. The agent wrote almost all of the code. It decided none of it. That gap is the whole story.

The agent will believe itself. That's the danger.

Give a capable model a hard problem and it does a genuinely good job. Then it does the thing every single model does: it accepts its own reasoning. Not out of laziness - it's just what one pass is. There's no second opinion built in. So it hands you something fluent and confident and well-structured, and none of that confidence tells you whether it's right.

Which means the whole job moves to the one place the machine can't stand: not believing it too early.

Everything below is that, applied.

"Pre-existing" is a hypothesis, not a finding

The integration suite came back 85 out of 90. The agent had an explanation ready - the five failures were pre-existing, nothing to do with our branch. Tidy. Plausible. The exact point where a stuck team moves on, because it sounds like it's already been looked into.

I asked it to clear them anyway.

None of the five were application bugs. They were stale test harnesses - helpers building objects without a flag the code now required, a mock missing two functions the code had started calling, two tests still asserting behaviour we'd deliberately reversed. We finished at 90/90. And now the team understands its own test suite instead of routing around a red number it had learned to ignore.

This is the move, and it's the one that gets stuck problems un-stuck more than any other: an agent can tell you what is happening, but it doesn't get to tell you why until it's traced it to a file and a line you could go read yourself. "Pre-existing", "flaky", "unrelated", "environmental" - those are the start of an investigation. A team circles a bug for weeks because it kept accepting one of those words as an answer.

Don't attribute it. Trace it. Show me the line.

Make it put a number on its own doubt

"It's done" and "looks good" are binary and easy to fake. So I don't ask that. I ask for a confidence level, per codebase, as a number - and then I ask what's stopping that number from being higher.

The answer to the second question is the actual to-do list.

Early on the honest number was mediocre, and the reason was exactly our bug: the fixes worked in isolation, but nobody had watched the four codebases cooperate through a real call. That's the gap the number exposed. So we stopped trying to reproduce the failure on our own clean devices - the devices that lie to us - and stood up a production-shaped stack where we could force it. Two real two-browser calls. One clean. One where we killed a context mid-call on purpose to trigger the death-and-rejoin path. Both behaved exactly once.

Every point below 100 had a name. CallKit on real hardware. A foreground-service path under a live call. Payment journeys behind an expired test key. Each one was either something to go close or something we decided stays human. A 90% with three named gaps beats any "it works", because the three gaps are the truth and "it works" is a hope.

Show me it, don't tell me it

The fixes are trustworthy because the risky claims became things we watched happen, not because the diffs read well.

The billing clock starts on a webhook - so we watched a real event write the start time in live traffic. There's a guard against a room being closed twice - so we watched two clients race that path 80 ms apart and the room close once. The rejoin path - we killed a context and watched the rejoin land 35 ms after the server reported the orphan.

"I added a test" is fine. A log line from something production-shaped is what actually moves the number. When your own environment is the thing lying to you, observed-in-production-shape isn't optional - it's the only evidence that counts.

A merge conflict is a code review, not a coin flip

Two PRs conflicted late. The move an agent reaches for by default is picking a side per marker, and it's almost always wrong. On iOS, our branch and main had each added fields to the same struct - ours a reconnect budget, theirs some cost-display fields. Take either side and half the work vanishes silently.

The rule: prove which symbols are still used, keep every one that is. Grep each field's consumers before you combine anything. Then the full suite has to be green on the merged tree before it commits - not on either side, on the thing you just made by hand.

Same muscle. Don't accept the convenient resolution. Check what's actually true.

Say plainly what you couldn't verify

The agent didn't get to claim work it couldn't do. The payment key was expired, so those journeys stayed out of scope and got labelled that way instead of faked green. The real-hardware checks that need a person got written down with the exact command each one needs.

"What I could not verify" is a required section of every handoff. That list is where the risk actually lives, and an agent left to its own instincts will quietly let it disappear.

The part that's mine, and the part that's the machine's

I'll be straight: I'm a better engineer than the agent, and probably better than most of them put together. But not because I remember more. It has all the syntax I have and then some. I'm better because of how I come at the problem - I don't accept the first explanation, I make doubt get counted, I trust a forced failure over a clean run, I don't trust my own green environment when a client's identical phone says otherwise.

None of that is new. It's how I've reviewed a junior's PR for decades. What's new is that agents made it legible - every judgment call is now a message I can point at, which is the only reason there's a post here at all.

And that's the good news, because judgment is teachable in a way that raw recall never mattered. The syntax took a career to accumulate and it's the part that just got commoditised. What's left is the part that was always doing the real work.

The moves, if you want the short version

  1. Match the model to the stakes. Big model for hard problems; two independent models reviewing each other for the hardest.
  2. "Pre-existing / flaky / unrelated" is the start of an investigation. Trace it to a line.
  3. Ask for confidence per codebase, as a number.
  4. Then ask what's keeping that number down. That's your to-do list.
  5. When your own environment won't reproduce it, stop trusting your environment. Build a production-shaped one and force the failure.
  6. Require observed evidence - a log line, a real run - not assertions.
  7. Confusing output is a defect. Make it re-present until it's clear.
  8. Treat docs and PR bodies like code. Sweep for stale numbers before you call it done.
  9. Merge with the team's work, not past it. Fold in, reconcile, don't rewrite.
  10. Make "what I couldn't verify" a required section. That's where the risk is.

The agent is fast and it never gets tired. It will also believe itself every time. That's why the judgment stays with us: we decide what done means, and we ask for the proof.