Chapter 6 of 15

Making Long-Running Work Observable

A silent agent that takes 90 seconds feels broken even when it is working. I learned that long-running agent work needs to explain itself while it is happening. A technically healthy 90-second run can still feel broken if the interface gives the user no evidence that useful work is continuing.

  • WebSocket event architecture
  • Thinking, activity, progress, and failure states
  • Why observability is part of the user experience

The situation

Generation, dependency installs, validation, and repair could together take long enough that a blank loading screen felt like a hang or a crash. In Making Long-Running Work Observable, I wanted to capture the real operating situation rather than describe the problem in abstract terms. What mattered to me was the moment the existing approach stopped being sufficient in normal use, because that was usually the point where the next architectural requirement became obvious.

The naive solution

Show a single spinner until the whole pipeline finishes. The naive option was attractive precisely because it was faster and easier to explain. I could have pushed further in that direction and probably improved the demo, but it would have optimised the visible symptom rather than the underlying system constraint.

The hidden constraint

Users could not tell the difference between "working normally" and "stuck," so they refreshed, retried, or abandoned the session mid-run. This constraint was easy to miss early because it did not show up in the first successful run. It appeared once the workflow had to survive repetition, failure, another user, another provider, or a return visit, which is why I now look for these second-order requirements much earlier.

The decision

Stream structured events for every meaningful state change: assistant messages, thinking, tool starts and results, code updates, asset readiness, token/cost usage, and errors. I treated this as an architectural or product decision rather than a prompt tweak. The aim was to create a boundary I could reason about later, instead of relying on model behaviour to remain consistent as providers, templates, and user requests changed.

The implementation

Built a WebSocket event model consumed by the frontend to render live activity, per-variant progress, and explicit error states instead of a single opaque progress bar. I also tried to make the implementation concrete enough that the decision could be inspected in code and runtime behaviour. That meant explicit state, explicit policy, and observable transitions rather than a collection of hidden assumptions spread across prompts and handlers.

The tradeoff

A materially more complex frontend state model to keep in sync with a multi-event backend stream. I did not treat the decision as free. In most cases I was deliberately accepting more engineering, state management, or operational complexity in exchange for a smaller failure surface and a product that could survive realistic use.

The evidence

Long-running builds could be watched turn-by-turn, and cancellation could be issued against a specific in-flight run rather than the whole session. I use this evidence as a practical signal, not as a claim that the architecture is universally correct. The useful question was whether the new design changed what the system could reliably do under the conditions that had previously broken it.

The reusable lesson

For any process longer than a couple of seconds, observability of progress is not a nice-to-have UI layer - it is part of the correctness of the user experience. The part I would carry into another build is the decision rule behind the implementation, not the exact technology choice. Vendors and frameworks will change; the underlying question about state, control, isolation, feedback, or user trust is much more reusable.

The consultant's questions

  • Can a user tell the difference between "still working" and "silently failed" right now?
  • What is your plan for a user who disconnects mid-run - does the work continue?
  • What assumption in this part of the system would become dangerous if usage or tenant count increased?
  • What evidence would tell me this decision needs to be revisited rather than defended indefinitely?