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 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:
- 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 real reliability invariant
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 — even though the 'keep consuming after disconnect' fantasy doesn't actually run.