Two Kinds of Stuck

There’s a version of “my AI works while I sleep” that’s pure thread bait — a screenshot, a rocket emoji, a claim that the future arrived overnight. And there’s the real version, which is mostly plumbing, and which lives or dies on a single distinction I didn’t appreciate until it bit me.

Here’s the setup. I run a small fleet of background coding agents — roughly one per repository, a couple dozen of them, taking turns. While I’m away from the desk they pick up work, make changes, run tests, commit. Nothing exotic; it’s the same agent I’d drive by hand, just left running on a schedule with a cap on how many go at once.

The interesting part isn’t that they run. It’s what happens when they stop.

An agent stops for one of two reasons

Spend any time watching a fleet like this and you notice every halt falls into one of two buckets, and the two could not be more different.

The first kind is machine-stuck. The agent hit the plan’s usage ceiling and the platform told it to come back later. Or it caught a transient server hiccup — a momentary “too many requests,” the kind of thing that clears on its own in a minute. In both cases the agent did nothing wrong. The world said wait, so it’s waiting. There’s nothing to decide here. Left alone it would sit there forever, holding a slot, doing nothing, for no good reason.

The second kind is human-stuck. The agent walked up to a line I drew and refused to cross it without me. Push this branch to a remote. Send this email. Spend money. Make something go live. These are the few irreversible, outward-facing moves I never let an agent make alone, so when one arrives at the edge it stops and asks — confirm: this, pick: one of these.

That stop is not a failure. That stop is the entire safety model working exactly as designed.

And that’s the trap. Both buckets look identical from a distance: an agent that isn’t moving. If you write a dumb script that just pokes every stalled agent with “keep going,” it will faithfully wait out a usage limit and faithfully click past the one gate that exists to stop it from doing something you can’t take back. The same nudge that’s obviously correct for the first kind is the single worst thing you can do to the second.

The watchdog only unblocks one kind

So I built a small thing — a watchdog — whose whole job is to honor that distinction and nothing else.

It’s about as dumb as a useful program can be. Once a minute it asks the agent runner who’s stalled and why. For a usage-limit stall, it reads the time the limit resets, waits for that moment to actually pass, then sends a single “continue” — exactly what I’d do if I were typing into the fleet view myself. For a transient rate-limit, where there’s no reset clock to read, it backs off and retries on its own schedule, and it staggers those retries across the fleet — because a rate limit usually hits everyone at once, and the last thing you want is two dozen agents all lunging back through the same door the instant it cracks open and slamming it shut again.

And then the part that matters most, the part that’s defined by what it won’t do: it never, under any circumstances, touches an agent that’s stopped at a human gate. A confirm: or a pick: is invisible to it by construction. Those wait for me, every time, no exceptions, even at 3am when I’m asleep and the slot is idle and it would be so easy to just let it through. There’s also a single file I can drop to freeze the whole thing if I ever stop trusting it. The kill switch is more important than the feature.

That’s the rule, stated plainly: automatically recover from the machine telling you to wait; never automatically answer a question that was meant for a human.

The night it ate itself

I promised on this site to show the seams, so here’s one.

For a while it worked beautifully, and then one morning I found the entire fleet flatlined. Zero agents working. Two dozen of them, all parked.

The cause was a perfect little knot. One agent had been nudged back to life after a usage reset — but instead of returning cleanly to working, it got wedged in a limbo state: the process was alive, but it never actually resumed. The keeper that staffs the fleet didn’t count a ghost like that as a working agent, so it kept trying to fill the slot. But the slot wasn’t empty. So it stalled. So it tried again. One stuck agent, multiplied by a refill loop, quietly deadlocked everything around it. The recovery mechanism had become the outage.

The fix is embarrassingly boring, which is the point: now anything that’s been sitting in that limbo state for more than a few minutes gets reaped, and the slot genuinely frees up. No demo will ever show you this. It’s exactly the kind of unglamorous, load-bearing detail that decides whether “it runs overnight” is a real claim or a lie you tell on a good day.

Why the boring rule is the whole game

The thesis I keep coming back to on this site is that the magic is downstream of the boring config. This is the cleanest example I have.

“My agents work while I sleep” sounds like a story about capability. It isn’t. It’s almost entirely a story about restraint — about deciding, in advance and in cold blood, the short list of things a machine may never do without me, and then building something small enough to be trustworthy that holds that line when I’m not watching. The watchdog has no judgment and I want it to have none. Judgment is the part I keep.

It also has to earn its keep cheaply, because the loop underneath all of this puts maintenance cost in the denominator — anything that needs babysitting scores low and prunes itself. A watchdog that needed its own watchdog would defeat its own reason to exist. So it’s deliberately tiny and stupid: one distinction, drawn sharply, defended completely.

Get that one line right and the fleet quietly hands you back your mornings. Get it wrong and you wake up to a branch you didn’t push. Two kinds of stuck. Only one of them is yours to fix while you sleep.


← all writing