Running Production Agents Steadily: SSE Streaming, HITL Pause/Resume, and the State That Holds It Together
Long-running Agent tasks in production fail in predictable ways: no feedback, lost context, duplicate dangerous operations. This design gives teams a concrete protocol for streaming progress, persisting run state, and resuming after human confirmation, which is the difference between a chat demo and a deployable system.
Once an Agent moves past demo chat into a real business, the hard part isn't the model's answers. It's that a task runs for minutes with no progress shown, a user says "continue investigating it" and the system doesn't know what "it" is, or a restart action sits waiting for approval and the backend has no idea how to continue after the click. The article builds the missing pieces from scratch: distinguishing Conversation, Message, and Run as separate data records; pushing progress over SSE with idempotent, sequence-carrying business events; and treating human confirmation as a normal `interrupt` state rather than an error.
For the HITL path, the key discipline is that resuming a Run must not be a fresh chat. The resume API takes an `interruptId`, an idempotency key, and a decision, re-validates permissions and reloads the target resource before executing, and relies on a conditional database update to let only one of two concurrent clicks start the dangerous operation. SSE disconnects are also separated from task cancellation: query tasks can die with the connection, but publish/approve/restart tasks keep running server-side and write results to the database so a refresh just re-reads state.
The article also maps its design to LangGraph concepts, notes that framework state save doesn't make external tools idempotent, and recommends a rollout order: POST + SSE with a frontend buffer first, then Run/checkpoint/resume, then event replay and multi-instance sync. The closing tests list includes split events, Chinese characters crossing chunk boundaries, double-click confirm, expired interrupts, and checking whether a failed tool call pollutes the confirmed `state`.
The hardest part of production Agents isn't the agent loop itself, it's the boundaries: what happens when a human steps away, a connection drops, or two clicks race the same operation.
A framework like LangGraph can manage graph state, but the external contract (SSE events, resume API, permission checks) still has to be built by the application team.
Basing resume on reloading the resource and re-checking permissions is a strong pattern: it treats the confirmation interval as a potentially stale window, not a fixed moment.
The conditional UPDATE trick is a reminder that idempotency keys alone don't stop racing requests; you need a database-enforced state transition.
Simplifying SSE over WebSocket for Agent progress is pragmatic; many teams over-engineer real-time communication before the backend even has a stable event protocol.
Drop the repo link so we can check it out.
That's right, not publishing the code leaves us with only theory and no way to see the entire runtime process.
Hang on, still putting together a demo.