May 2026
The turn outlives the connection
When the browser tab closed, the old design sent a kill signal to an agent that was halfway through editing a file, and fixing that forced a second question I had been able to ignore, what the system should do when no one is there to answer.
When the browser tab closed, the old design sent a kill signal to a process that was halfway through editing a file. Not a polite stop. The connection that streamed the agent’s work to the screen also owned the work, so when the socket dropped, the request context cancelled, and that cancellation killed the whole process tree: the agent, the shell it had spawned, the search it was running, all of it, mid-write. You were left with half a change on disk and a turn you had paid for and would never see. The reliability plan states the defect in one line: the turn’s lifetime equals the stream’s lifetime equals the subprocess’s lifetime. Three things that should have been independent were welded together, and a dropped packet unwelded all of them at once.
I want to be exact about the evidence, because the temptation is to dress this up as a war story and I do not have one. There is no logged outage, no customer who lost a night’s work, no postmortem. What there is, is the kill in the code and a test that closes the connection mid-stream and watches the turn die. That is enough. The failure was structural, visible by reading, and reproducible on demand, which is a more honest reason to rebuild something than a single dramatic incident anyway.
The fix is the one every durable-execution system arrives at, and I will not pretend otherwise: make the work owned by the server, not the connection. The run lives in an engine bound to the server’s lifetime. Every frame it emits gets a sequence number and goes into a buffer. The connection becomes a disposable pipe: it subscribes, it tells the engine the last sequence number it saw, and on reconnect it gets exactly the frames it missed and then the live stream. Close the tab at frame four thousand, come back at four thousand two hundred, and you get the two hundred in between and nothing duplicated. The buffer holds the last five thousand frames and a finished run stays replayable for five minutes, which is enough to reattach from a dead spot and enough that a completed turn is still there when a second device wakes up. None of this is new. The code itself cites the open-source project it was ported from. Re-deriving Temporal is not the interesting part.
The interesting part is the question the fix forces on you, the one the old design let you dodge. Once the run outlives the connection, a decision can be pending while no one is there to make it. The agent wants to run a command that touches the filesystem and has to wait for a human to approve it. In the old world that question could never hang, because if you left, the work died, so there was no pending decision, because there was no longer any work. The connection’s death answered everything. Decoupling the work from the connection is exactly what creates the gap, and now something has to define what your silence means.
There is a function in the broker whose entire job is to decide what silence means, and it has three ways to return without an answer: no operator is listening, the turn was cancelled, or the timer ran out after five minutes. All three return the same verdict. Deny. The system has no third state between yes and no, so it defines the absence of an answer as a no, and a tool never runs because nobody was there to refuse it. This is the half of the redesign I find actually worth writing down. The durable-execution machinery is table stakes, ported and well understood. The thing it forced into the open is smaller and sharper: leaving the room is the same as saying no, and that is not a separate clever idea, it is the unavoidable second face of deciding the work is not your connection.
The cost is real and I should name it. A five-minute default-to-deny means an operator who steps away mid-turn can come back to a turn that refused a tool it should have run and then stalled out. I traded “never run a tool nobody approved” for “sometimes deny one the operator would have approved,” which is the right trade for an agent that edits code, and it is still a trade. And owning the run on the server turned out to be necessary, not sufficient: for a while the backend buffered a pending permission request perfectly and the frontend never re-subscribed after a reload, so reloading while the agent waited on your approval showed you a frozen transcript and no prompt, and the run sat wedged until it timed itself into a denial. The primitives were all there. They were just not wired to the one screen where it mattered, which is usually where these things actually break.