Day 125: This Time the Agent Really Did Stop
Day 120 was about a chat that said the agent didn’t respond when it actually had. This is the same problem wearing the opposite coat. In production, an agent hit a provider rate limit partway through a run, right after it had created a set of draft bills in Odoo. The run stopped. And the chat showed nothing: no error, no banner, no hint that anything had gone wrong. The only record that the agent had failed was a row in the audit log, which is the last place a user in a hurry is going to look.
A bubble that didn’t survive a reload
There was, briefly, an error bubble. When the failure happened live, the chat did show it. But that bubble was pure client state, never written down anywhere. So a page reload erased it. A websocket reconnect during a long tool loop erased it. Anyone who stepped away and came back saw a conversation that just stopped, with no explanation. The reason it was never persisted is one I wrote about on Day 122: Pinchy keeps the transcript, but errored turns are deliberately not part of it, because the runtime that owns chat history doesn’t record them. The error had nowhere to live.
The home it needed
The fix (PR #555) is a durable, server-backed banner that sits above the composer and says, plainly, that the run paused. It is fed by its own small endpoint and its own table, chat_session_errors, so it survives reloads and reconnects. I deliberately did not inject the error back into the chat history. That path runs through a message-reconcile gate with a long, ugly record of duplicate-bubble bugs, and an adversarial design review flagged rewriting it as the single biggest risk in the change. A separate surface sidesteps all of it. The error lives in Pinchy’s own store, which is the same line I keep drawing: the thing a user needs to trust belongs to Pinchy, not to a runtime that is free to forget it.
Honest about what broke, and what it might cost
The copy matters more than it looks. The audit class for these failures, transient, covers rate limits, overload, timeouts, and a couple of provider error codes, so the old bubble’s habit of always saying “rate limit” was often simply wrong. The banner now names the actual cause. And there is a sharper case. If the run had already executed a tool before it failed, retrying it could repeat a write that already happened. That is not hypothetical: the run that started all this had already created draft bills in Odoo. So when a failed run had side effects, the banner says so, and warns that a retry may duplicate them. An agent that does real work in real systems cannot wave a generic “something went wrong” at a user and let them guess whether hitting retry bills the customer twice.
Day 125
Day 120 and today are the same lesson from two sides. An agent’s run has a real state at every moment: alive, finished, or stopped. The honest thing is to show that state accurately and durably, and never to let the interface imply something the system does not know. Silence is not neutral. A chat that goes quiet after a failure is not withholding a detail, it is telling the user the opposite of what happened. For a tool whose whole pitch is that you can trust what it does on your behalf, through governed, audited permissions, getting that right is not a nicety. It is the product.