The docs sweep re-points every stale reference to the deleted seam
(architecture.md's flow diagram and ladder inventory, the lowering and
anthropic docstrings, the protocol's shared-rule docstrings that
described the pre-fold dual-assembler world). The Protocol's cancel_ref
contract is strengthened from 'before the first chunk' to 'inside the
call body, before the iterator is returned' — the instant the fold's
creation-vs-midstream classifier and health recording key on — and
three real-SDK-over-mock-transport tripwires pin it per adapter, so a
future lazily-issued generator adapter fails loudly instead of silently
reclassifying every pre-first-chunk death.
Seventeen files, ~1,300 tests, re-pointed or redesigned per the triage
ledger's recipes: wholesale turn-scripting moves to ModelTurnResult
fakes; streaming-behavior suites drive the REAL wrapper+consumer+drain
path through armed provider fakes (tests/_parity_832.arm_session — the
eager cancel_ref append every real adapter performs, exception elements
for creation-phase failures, sequential per-turn scripts, and the title
lane quieted: a provider-level fake otherwise loses its one-shot script
to best-effort title generation, which is why the old tests patched at
the session level); kwarg-capture suites assert through model_turn's
create_streaming call with system-prepend-aware index math; delegate
wrappers retired by the fold re-aim at their model_turn module twins.
Old-architecture pins are replaced by their new-world equivalents rather
than deleted: no shared cancel ref exists (pinned), the handle slot and
per-attempt refs carry the cancel surface, the retry gate reads the
serving lane's provider, and a superseded generation's death exits send
silently as cancelled — a named delta: no arbitrary exception class
escapes an orphaned thread anymore.
Full suite: 10651 passed, 10 skipped. The wire-payload goldens pass
untouched — the fold's lowering composition is byte-equivalent on every
provider's request path, as designed.
External-review round on the #937 branch; four confirmed findings fixed,
each on a failure path the retry loop itself introduced or made reachable:
- The terminal arm (retry exhaustion, non-retryable death) now finalizes
the dead attempt with the same stream_end + turn_committed pair the
retry path emits, so the last attempt's partial is flushed in every
consumer — the CLI was the exposed case (its markdown fence state
resets only in on_stream_end; the server workers emit their own after
a fatal, the CLI's direct send() does not). The finalize is gated
behind the generation check: an orphaned superseded thread must not
emit UI events over the new generation's stream.
- A Stop landing in the backoff/re-create window now preserves the dead
attempt's partial: the attempt stashes its flushed content (plus the
content-state carry tail) on a non-cancel death, and the wrapper
promotes the stash to the cancelled-partial slot before re-raising, so
send()'s cancel handler persists it with the cancellation marker —
the same disposition a cancel during the attempt gets.
- The fatal-path debug trace logs frames only (format_tb): exc_info
rendered the raw exception message, which can carry credentials
verbatim — the exact leak the sanitize floor above it exists to hold.
The recreate-failure warning drops exc_info for the same reason and
logs the exception class name instead.
- A mid-retry rebind that replaced the client re-prepares the wire
messages against the new binding before re-issuing: the system-turn
fold is capability-sensitive, and a registry reload that switched
model family would otherwise re-send the old family's wire shape.
The cross-thread close boundary pin now accepts ReadError or
RemoteProtocolError: which one surfaces is platform/timing-dependent,
and both are TransportError members of the stream-death set, which is
the property the pin exists for.
A wire death during body streaming (ReadError on a TLS record failure,
peer resets) surfaces after the request has already returned its stream
handle, so neither the SDK's request retries nor the creation-time
retry ladder ever saw it: the interactive turn died with a bare
exception string, the partial output was discarded, and no log trace
was left. Utility lanes already survived this through drain_stream's
normalization; the interactive loop now gets the same treatment.
- transport_guarded() in providers/_protocol.py: drain_stream's
transport-death conversion made reusable for consumers that keep
streaming semantics. Pre-finish deaths raise the retryable
IncompleteStreamError (drain's exact message shape); post-finish
blips end the stream cleanly, forfeiting only trailing metadata.
- The single-pass chunk consumer renames to _stream_attempt;
_stream_response is now the resilient wrapper owning ALL stream
acquisition plus a bounded mid-stream re-issue ladder
(_MID_STREAM_RETRIES, the shared _stop_retrying predicate with a
per-loop cap, cancel-aware exponential backoff). Send()'s overflow
compact-and-retry arm now wraps the whole turn and passes re-prepared
msgs explicitly.
- A dead attempt is finalized across every UI consumer before the
retry (stream_end then turn_committed then notice then spinner), so
retried text never appends onto the dead attempt's in any surface
(browser transcript, CLI markdown fences, Slack/Discord streamed
messages, SSE replay ring).
- Before re-creating, the session re-resolves its registry binding: a
concurrent ModelRegistry.reload() closes cached clients, and the
retry must not stream into the closed one. A failing re-create logs
stream.retry.recreate_failed and re-raises the ORIGINAL stream-death
error rather than masking it.
- _format_backend_error gains a stream-death branch naming the
provider, endpoint, and model, with a short identity-bearing first
sentence. _BACKEND_STREAM_EXC_NAMES joins _BACKEND_KNOWN_EXC_NAMES,
which also removes those names from _is_ctx_overflow's text-detection
eligibility (deliberate: their texts are fixed transport strings that
never carry overflow phrases).
- _record_fatal_error now logs session.fatal.recorded (INFO for
KeyboardInterrupt, ERROR otherwise) so fatal turns leave a journal
trace.
- _assistant_pending_tokens resets at stream entry so a post-finish
blip that loses the trailing usage chunk cannot append the previous
turn's completion count as this turn's estimate.
Offline SDK boundary pins (openai/anthropic mid-body death identity and
no re-request, cross-thread client close surfacing httpx.ReadError)
guard the assumptions the retry gate rests on.