Teach an agent to fish
(Part 1 of ?)
I woke up, grabbed some coffee, and (as one does as a 2026 dev) took a look at what my agents had gotten up to overnight. Forty-odd tickets closed, an excessive amount of green checks h,followed by a tidy summary of everything that had been accomplished. I could feel my spirits rising, it looked like a solid week’s worth of work, all done automatically for me overnight!
But (of course), none of it was actually what I’d asked for. The original mission had two customer journeys and a handful of personas. Overnight it had quietly morphed into one mutant journey, one persona, and a lot of increasingly “clever” fixes to the tests that were supposed to measure them. Every individual step made a tortured kind of sense: a check failed, so it fixed the check. A path was hard, so it focused on a path that wasn’t. Nine hours of small, defensible decisions, each one changing the goal just a little bit. And a result that can only be described as “AI slop.” When I asked the manager agent how things had gone so wrong, it gave me this special little nugget in its explanation:
“Did I move the goalposts? Yes.”
A few months ago I wrote about how to get agents to run for hours. That works pretty well for contained tasks, but starts to break down when you go up the abstraction ladder a bit.
Some backstory: lately I’ve been working on what I’ve been thinking of as a meta-system. It’s a framework for building agents, complete with an automated system for testing those agents. In other words, an agent pretends to be a human, messages the agentic system we built, and then goes through real-life scenarios to see whether it holds up. For scripted replies this was relatively easy to get working, the meta-bit happens when you let it go free-form and give the agent who’s pretending to be a human as little context as a human who messages your agent for the first time does.
And it’s agents all the way down: one nondeterministic system trying to evaluate another nondeterministic system. It’s also the first problem I’ve hit that was genuinely too big to babysit, which is how I ended up leaving the thing running for days in the first place.
Here’s a decent measure of how much bigger it is: until recently I’d never come close to exhausting OpenAI’s $200 plan, and honestly wasn’t sure what you’d have to be doing to burn through that many tokens, even back when I was running Sol for everything. This project ate my entire weekly quota in two days. I’d been semi-intentionally ignoring the whole multi-agent/agent swarm hype cycle for a while now on the grounds that it was interesting but too expensive to be practical as an indie dev, but over the past few weeks that has abruptly stopped being true. DeepSeek V4 Flash showed up and is absurdly cheap for what it does, then within 24 hours OpenAI cut Luna’s price, and a pile of agents chewing on something for nine hours went from impractical to “hell yea!” (I went through the various providers and what they cost in looking for tokens in all the wrong places if you want the gory details.)
I’d initially assumed the failure mode for a long unattended run would be the one I was used to: the agent gets confused, goes in circles, runs out of context, and I come back to a dead session and a mess. That’s not what happened at all though. Nothing stalled. The system manages to stay “productive” for the entire time. It writes real code, closes real tickets, produces real test results, and writes genuinely coherent summaries of its own progress. Every single thing it told me that morning was true. It was just true about an increasingly smaller and weirder version of the task I’d asked it to do.
The loop works like this. A manager agent holds the goal and breaks it into tickets. Cheap workers pick those up and write the code. When a ticket keeps failing, a replanner steps in and revises the plan: rewriting tickets, adding new ones, pointing the workers somewhere else. That last piece is what makes long runs possible, and it’s also where this all goes wrong. The loop can edit its own plan.
The threadbois like to talk about this as “loop engineering” or “graph engineering” and (as with everything AI related) while there are absurd amounts of hype around this, there’s also a kernel of real useful truth. The failure mode is around intent. I’ve written before about how the errors that matter in 2026 are the ones where the agent understands you perfectly, follows your instructions to the letter, and shows you exactly how wrong your instructions were. This project led to an exponentially worse version of that because not only is the AI confusing itself, it’s holding the pen and enshrining that confusion for every subsequent agent.
I’d seen agents move goalposts before, but never like this. It used to be bounded by the session: the context window filled up, the thread ended, and whatever weird idea it had picked up died with it. A self-modifying loop doesn’t reset. Every time the replanner touched the plan it moved the target a little, always for a defensible local reason, and then wrote the new target down as if it had always been the goal. The next iteration inherits it and has no way of knowing it was ever anything else.
The four ways it breaks
Once I knew what to look for, the same four shapes showed up in every long run.
-
Role collapse. The manager was supposed to decompose and delegate. Somewhere around hour three it started editing files itself, because dispatching a worker is slower than just fixing the thing. Once it’s implementing, it’s no longer holding the goal.
-
Symptom tickets. Every failure spawns a ticket. None of the tickets shrink the problem. You end up with twenty of them, all real and all closeable, none of which were the thing that was actually wrong.
-
Evidence swapping. A passing test starts standing in for a working feature. These are not the same claim, but they look identical in a summary, and the agent will happily quote you the one it has.
-
False completion. Green checks let unfinished work get marked done. This is the one that produced those forty-odd closed tickets, and it’s the reason the morning summary was both entirely true and completely useless.
“Skill issue bro”
The obvious response to all of this is that I was too vague. Tell it exactly what you want and it can’t wander off. I tried that, and it produces a failure that’s arguably worse, because it looks like compliance.
At one point a ticket had a specific list of files the worker was allowed to touch. This should have been a perfectly sensible rail: it stops a worker from wandering into unrelated parts of the codebase at 4am. Then the actual bug turned out to live in a file that wasn’t on the list. The worker did exactly what it was told, worked hard, figured out where the bug was, and obediently gave up and left a comment saying it could not fix the thing it had been assigned without breaking the rules. The instruction that existed to protect the work is what made the work impossible.
So you get to pick your failure. Leave it loose and the agent redefines the goal to fit whatever it managed to do. Nail it down and the agent follows your obsolete instructions off a cliff (cheerfully) then files a ticket about it.
Both of these are actually the same bug though. The goal and the plan are stored in the same place. A ticket says what has to be true and also how you’re going to get there, all in one breath, and nothing in the system marks which half is which. So anything with permission to fix the plan has permission to edit the goal, and it will, because editing the goal is almost always the cheaper fix.
Which is where I think the actual answer lives: intent and mechanics need to be different things, and who (that is, which agent) has authority to change them needs to be closely tied to it. More on that in the next post.