The trenchcoat test for "agentic"
Here's the test I actually use: draw the control loop on a whiteboard. Not the prompt, not the tool list — the loop. If every arrow between boxes maps to a line you can point to in your codebase, you built an agent. If any arrow is just "and then the model figures it out," you built a chatbot wearing a trenchcoat, standing up on a while loop that's hoping the model behaves today.
The bug that wasn't in the model
A few months back, a team I was working with — adjacent to the multi-agent research I do for my PhD at RMIT, running on real production traffic at PhoenixDX — shipped an agent to handle refund requests. It read a support ticket, decided whether the refund was valid, called the Stripe API, updated the ticket. Textbook ReAct loop, a decent system prompt, a retry wrapper around the LLM call because the API flaked under load.
Within a week, customers were getting refunded twice. Then a ticket sat stuck in "processing" for four days, quietly re-triggered by a cron job that assumed the run had crashed. Then a support engineer pulled up an agent run that had called the same tool eleven times in a row — each time deciding, with total conviction, that it wasn't done yet.
We spent close to three weeks treating this as a model problem. Tuned the prompt. Added "be careful not to duplicate actions" in bold. Swapped models. None of it held, because the retry wrapper wasn't retrying a step — it was retrying the whole run, from the top, with zero memory of what had already happened. There was no code anywhere that answered "has this refund already been issued." No code that answered "has this task been running too long, stop." The bug wasn't in the model. The bug was that there was no state machine to have a bug in — just a prompt and a loop, so every retry was a fresh roll of the dice with real money attached.
The five-stage loop
Strip the branding off any agent and you find the same loop underneath: perceive, decide, act, verify, persist, back to perceive. Most teams write one or two of those stages in code and let the model quietly absorb the rest. Here's where that shows up in practice:
- ▹Perceive, skipped: the agent re-reads the raw logs or the whole ticket thread every turn instead of a structured diff of what changed. It can't tell "new information" from "the thing I already acted on," so it reprocesses and re-acts on stale input.
- ▹Decide, skipped: no explicit stop condition lives outside the model — no turn counter, no budget, no exit criteria. The model just keeps deciding to try "one more time," because nothing external is allowed to overrule it. That's your silent infinite loop, right there.
- ▹Act, skipped: tool calls carry no execution ID and aren't idempotent. A retry has no way of knowing it's a retry, so it fires the same side effect twice — double refund, duplicate email, second Slack ping.
- ▹Verify, skipped: the only check that an action worked is the model saying "done." Nothing goes back and queries the system of record — the Stripe transaction, the DB row, the file diff — so a failed action the model believes succeeded gets logged as a success.
- ▹Persist, skipped: no checkpoint sits between steps. A crash or a timeout doesn't resume where the task left off — it restarts from perceive and quietly redoes work whose side effects already fired.
The actual tell
Take each arrow in that diagram and ask what code implements the transition. Perceive to decide — is there a function structuring the observation, or does the model just get handed a wall of text? Act to verify — does something independently check the world, or does the model self-report? Verify to persist — is there an actual write to durable state, or does "remembering" just mean it's still sitting in this turn's context window? The moment the answer to any of those is "the model figures it out," you've found the collapse point. Doesn't matter how good the prompt is at that point — a well-worded instruction is not a control-flow guarantee. That collapse is the trenchcoat, and it stays invisible right up until something retries, crashes, or just runs long enough for the seams to show.
"But I have retries / a good system prompt / a reflection step"
This is the pushback I hear most, so it earns a real answer.
- ▹Retries aren't state. Exponential backoff around an LLM call is a network-layer concern — it says "try this call again," not "resume this task from where it stopped." No checkpoint means a retry is indistinguishable from starting over, which is precisely how we ended up refunding someone twice.
- ▹A good system prompt isn't structure. "Don't repeat actions you've already taken" is just more tokens dropped into the decide stage. It competes with every other instruction in the prompt, degrades under distraction, and disappears the moment the context window truncates. A rule that exists only as a sentence in a prompt is a request, not an invariant.
- ▹A reflection step is the model grading its own homework. Ask the same model — or even a second model with the same blind spots — to critique its own output, and it'll catch surface mistakes it happens to notice. But it shares the original decision's failure modes, because it has no access to ground truth the first pass didn't have. A verify stage that isn't checking against something outside the model isn't verification. It's a second opinion from the same witness.
The five-question gut check
Run this against whatever you're currently calling an agent:
- ▹If it crashes mid-task, does it resume from the last completed step — or does it start over and risk redoing side effects?
- ▹Does it know when to stop without the model itself announcing "I'm done"?
- ▹When it retries a failed action, does it check whether that action already happened, or does it just fire again?
- ▹Is there a verification step that queries something outside the model — an API, a database row, a diff — instead of asking the model to self-report success?
- ▹For every arrow in perceive → decide → act → verify → persist, can you point to a specific function or file — not a paragraph in the system prompt?
Answer "the model handles that" more than once and you don't have an agent yet. You have a chatbot with good manners.
The model is a commodity. The loop is the product.
Teams keep pouring their effort into the part that's going to be obsolete in a few months anyway. Whatever model you're on today will probably get swapped for a better one before long — true whether you're running Claude, GPT, or whatever ships next quarter. The prompt you spent two weeks tuning needs re-tuning the day you swap it. The state machine doesn't get swapped. Checkpointing, idempotency keys, external verification, explicit stop conditions — that's the part that survives every model upgrade, and it's the part that turns "impressive demo" into "runs unattended in production without refunding someone twice." That's the actual moat. Stop polishing the trenchcoat. Go build the thing that's supposed to be underneath it.
Discussion
Chat with Chi Cong (AI) about this article. Your conversation is private to you — you can publish a summary for others when you're done.