The naïve story
You'd think FastAPI's request.is_disconnected() lets you detect when Dad hits the stop button mid-stream, gracefully drain the adapter, and write a clean 'aborted' marker to the JSONL. You'd be wrong, and Dad caught me on this.
What actually happens — when the SSE generator is the consumer
When the client disconnects from a StreamingResponse, Starlette cancels the entire ASGI task. The cancellation fires a CancelledError at whatever await is currently suspended — almost always the __anext__() on the adapter. The cancellation reaches the loop before the next iteration's is_disconnected() check ever runs. So on any route where the SSE generator is itself the thing consuming the adapter — which is still how six of the seven chat routes are built:
- The
disconnectedflag and thepippa_user_abortedmarker code paths almost never fire. - Whatever was already eagerly appended before the cancellation is durable — that's everything Dad saw streaming on screen.
- Whatever the adapter was about to emit between the cancellation point and the natural end of the turn is lost.
The invariant those routes keep
Anything Dad watched stream onto his screen is in the JSONL. Not 'everything the adapter ever generated.'
The healing layer (Truth track, lesson 4) reconstructs from what is in the JSONL. Since by construction that matches what Dad saw, the system never presents broken chat.
What the canonical route does instead
The Claude route stopped living with that. It splits the work in two: a pipeline task that calls the adapter, writes the JSONL and SQLite and every other side effect, and pushes SSE lines onto a queue; and a thin generator that only relays that queue to the browser. A disconnect cancels the relay and nothing else. The pipeline is detached into a set of background streams the server keeps track of, runs to its natural end, and lands everything. At shutdown the server gives those streams up to thirty seconds to finish before it cancels what remains.
So on that route the stronger invariant holds: everything the adapter generated lands, not only what Dad happened to be watching. The other six routes have not adopted the split. That is Rule 2 read the other way round — a guarantee the ancestor grows does not flow sideways into the variants on its own.