Found by using it: the agent planted 18 beans, the client's stream dropped, and those 18 plantings ended up in the garden with no change set behind them — real changes with no way to undo them, which is the one guarantee this whole design rests on. The earlier fix detached the FAILURE path and reasoned that a cancelled context is why fn failed. That missed the commoner case. An agent turn whose client disconnects can still COMPLETE — the model finishes, the tools have already written — and then the success path committed with a dead context, the write failed, WithChangeSet returned an error, and the work was orphaned. commitScope now detaches from cancellation itself, so every caller gets it. That is the right home for the rule: by the time a commit runs, the data it describes has already been written, so cancelling it cannot undo anything — it can only lose the record of what happened. There is no path on which that is the behaviour anyone wants. Also added an SSE keep-alive, since the dropped connection is worth not having in the first place: a model thinking between tool calls sends nothing for a while, and an idle proxy will cut a quiet stream. A comment frame every 20s keeps it open, and SSE ignores comment frames so the client is unaffected. That introduced a second goroutine writing the response, so the event stream now serializes writes behind a mutex — step events come from the agent's run goroutine while the ticker writes from its own, and two goroutines writing a ResponseWriter concurrently corrupts frames long before it crashes anything. Verified under -race. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ