Originally published on robatdasorvi.com
On the morning of July 25th I opened Telegram to a column of approval cards. Each one was a reply my Twitter agent had drafted and queued up for me to sign off on. Quote tweets, comment threads. There was a first line in there I remember actually liking.
The account had been suspended since July 21st.
So: four days of drafts that could never post. And somewhere in that stack was an alert from the bot itself, saying in plain English that it had detected a lockout. Then it carried on. That gap, between a system knowing something and the system behaving like it knows it, is the part of agent state management nobody puts in the tutorial. Retrieval, memory, vector stores, all covered at length. The chapter where an observation fails to become a fact the loop obeys doesn't exist.
Three and a half days. Here's what was underneath.
Why Did My Agent Keep Running After It Was Locked Out?
Because "we are locked out" only ever existed as a log line and a Telegram message. It was never a value the control loop read. The function that detected the suspension notified me and returned, and the loop it returned into had no field to check.
In spirit the code went: check_for_lockout() scraped the profile, matched the suspension banner, called notify(), returned None. The scheduler that called it moved on to due("reply"), which read a timestamp file, decided enough minutes had passed, generated a reply, and pushed it into the approval queue. Every step correct in isolation. Nothing in the chain asked whether the account was alive, because nothing in the chain had anywhere to put that answer if it got one. The detection was flawless and completely inert.
Isn't This Just a Missing Return Statement?
That was my first fix, and it wasn't wrong so much as absurdly undersized. I made check_for_lockout() call sys.exit(1), watched it work, and figured that was that.
It was that, for exactly that one instance. Over the following week the same shape kept turning up in different clothes. A rate-limit response the HTTP layer logged and swallowed. A Telegram callback whose skip branch never wrote its decision anywhere, so the scheduler counted the item as timed out and requeued it. A daily action cap dutifully incrementing a counter in memory while the restart loop killed the process every few hours.
Four separate bugs, one structure underneath all of them: something true about the world got observed, got mentioned out loud, then got discarded before it could constrain any behavior. I still ship the sys.exit(1) and it's still correct. But treating it as the fix is how you spend a month patching one defect wearing four costumes.
The wrong turn was in my head, not in the code. I'd been reading these as bugs in error handling. They're bugs in state design.
What Is Hidden State in an AI Agent, Exactly?
Hidden state is any fact your agent has established that lives somewhere the control flow can't read. A log line. A chat message. A paragraph in the context window. A variable in a function that already returned. It's the difference between an agent that observed something and an agent that knows it.
The specific trap in LLM systems is that the context window looks like state. It's right there, it's readable, it accumulates over time. But a context window is a transcript, and a transcript is not a state machine. It records that something was observed. It never records that anything became true. When the model reads "ERROR: account suspended" on turn 14, that string is competing for attention with thirteen other turns and whatever arrives on turn 15. Nothing has changed structurally. The agent is exactly as free to draft a reply on turn 15 as it was on turn 1.
Which explains the direction agentic reliability tends to fail in. Sierra's τ-bench, published in June 2024, measured not whether an agent completes a task but whether it completes the same task repeatedly. The best GPT-4o configuration cleared 60% on pass^1 in the retail domain and fell below 25% on pass^8, roughly a 60% relative collapse once consistency is the thing being scored (arXiv:2406.12045). Capability was never the binding constraint. Reproducibility was, and reproducibility is a state problem.
Doesn't a Bigger Context Window Solve AI Agent State Management?
No, and the evidence here is unusually clean. The NoLiMa benchmark, presented at ICML 2025, tested retrieval where the question and the target share minimal literal overlap. That's what real agent state looks like, since "the account is suspended" rarely shows up verbatim in the phrasing of the next decision.
Eleven of the twelve models evaluated dropped below 50% of their own short-context baselines at 32K tokens. GPT-4o, one of the strongest performers in the set, fell from 99.3% at short context to 69.7% (arXiv:2502.05167). These are models advertising 128K windows and up. The window is real. The reliable attention inside it isn't.
So the obvious counterargument, just put the suspension notice in context and let the model handle it, fails twice over. It fails empirically, because the model's ability to act on a buried fact decays with distance long before the window fills. And it fails structurally, because even at 100% retrieval you've made a safety-critical guarantee contingent on a probabilistic read. My bot didn't need to remember the suspension. It needed to be incapable of attempting a post. Those are different requirements, and a longer prompt only satisfies one of them.
Why Do Multi-Agent Systems Make Hidden State Worse?
Because every agent boundary is a place where implicit decisions get dropped. The parent's context doesn't cross into the child. Only the instruction does.
Walden Yan's Don't Build Multi-Agents, published by Cognition in June 2025, has the cleanest illustration I know of. Ask a system to build a Flappy Bird clone. It spawns one subagent for the background and one for the bird. The first misreads the brief and produces a Super Mario Bros. background. The second produces a bird that doesn't move like Flappy Bird and doesn't match the art it's flying over. Neither subagent did anything wrong given what it was actually told. Yan's phrasing: "actions carry implicit decisions, and conflicting decisions carry bad results."
The Berkeley taxonomy paper Why Do Multi-Agent LLM Systems Fail? (arXiv:2503.13657, 2025) put numbers behind the intuition, deriving 14 distinct failure modes from 150 hand-annotated traces across seven popular frameworks, validated at inter-annotator agreement κ = 0.88 and then extended to a corpus of over 1,600 traces. Those 14 modes cluster into three families, and two of the three, inter-agent misalignment and task verification, are state problems rather than reasoning problems. Agents drop information across handoffs, or nobody is holding the state you'd need to check the result against.
Anthropic's own multi-agent research system write-up, also June 2025, reports a 90.2% improvement over single-agent Claude Opus 4 on research tasks at roughly 15× the token cost of chat, with token usage alone explaining about 80% of performance variance. Worth noticing where that works: parallel research, where the subtasks are genuinely independent of each other. Anthropic says outright that the pattern is a poor fit for tightly coupled work like coding, which is the same boundary Yan draws from the other side. Parallelism is affordable precisely when there's no shared state to lose.
So What Do You Actually Write Down?
Not everything. That was my second wrong turn. I started designing a serialization layer to persist the full agent trajectory, which is expensive, slow, and solves a problem I did not have.
The rule I landed on is much narrower, and I didn't expect it going in. Every observation that changes what the agent is permitted to do next has to become a typed value the control loop reads before it acts. Not what the agent knows. What the agent is allowed to do.
My bot's memory was fine, honestly. It could recall its own posts, its target list, its performance history. What it didn't have was a permission layer: a small, boring, explicitly-read set of gates that every outbound action passes through and that any detector can write to. Account alive. Under the daily cap. Outside quiet hours. Not rate-limited. The suspension check doesn't notify anymore, it flips a gate and halts the process. The rate limiter doesn't log anymore, it writes a cooldown timestamp the scheduler reads on the next tick.
I went into this assuming state management meant memory. It doesn't. Memory is the easy half, and it's the half every framework already ships for you. The dangerous state in an agentic system is what the model is still allowed to do, and that lives in your code rather than your prompt. An LLM can't hold a permission on your behalf. It can only be told about one, and being told is not the same as being bound.
What I Still Don't Know How to Fix
The gates work for the conditions I anticipated. For the ones I haven't, I have no method at all.
Every gate in my system exists because something already went wrong. The suspension, the rate limit, the cap, the quiet hours. Four gates, each paid for in an incident. The whole design is reactive by construction, and I obviously can't tell you what the fifth failure is, because if I could I'd have already written the gate. A real completeness argument for a permission layer would need something like a model of every state the platform can put me in, which is a specification of X's moderation system, which nobody outside X has.
The honest position: I've converted a class of silent failures into a class of loud ones, and I don't know what fraction of the class I've covered. The bot no longer runs 3.5 days into a wall. Whether it runs 3.5 days into some wall I haven't met yet is open, and every quiet day is weak evidence at best.
Top comments (1)
That gap between "the system knows" and "the system behaves like it knows" is exactly where my agent failures live. The pattern that fixed it for me: health checks write state, they don't notify. check_for_lockout() should set account.alive=false in the same store the scheduler reads, and due() should to run any task whose dependencies are dead. Alerts go to humans; state goes to the loop. The other half is failing closed on the queue side - a that can never post should die loudly in a dead-letter pile instead of accumu Four days of unpostable drafts is a quiet queue; a loud one pages you on day one.