ADR 0050: Bounded provider stream recovery and diagnostics
- Status: Accepted
- Date: 2026-08-04
Context
The provider retry layer covers failures while establishing a request, but a provider can also terminate after the assistant stream has started. The previous runtime classified the common terminated message as a generic PROVIDER_ERROR, ended the turn, and left regenerate to create a new runtime and reseed the transcript. This made transient stream failures expensive and made a long provider wait difficult to attribute from logs.
Repeated mutation failures had a related cost: the model could keep repairing an obsolete patch or retrying an Edit with stale context instead of taking a fresh snapshot of the target file.
Decision
PI-Desktop applies the following bounded recovery strategy:
- Classify
terminated, premature stream closure, and equivalent incomplete stream messages as retryableSTREAM_FAILEDerrors. Preserve only safe, low-cardinality provider status/code fields inAppError.details. - Configure pi-ai request setup with one provider-level retry and an interruptible delay capped at 8 seconds. Forward the durable session id so provider adapters that support request affinity can reuse it.
- When a transient
STREAM_FAILED,NETWORK_ERROR, orTIMEOUToccurs after a stream has started, retry once in the same turn after a 750 ms abortable backoff. Remove the failed assistant from the next model context, reuse its visible assistant message id, and replace the partial content instead of creating a duplicate bubble. A second failure is terminal. - Attach
phase,providerWaitMs,streamMs, provider status/code, andretryAttemptwhen available. These fields are diagnostic only; messages remain redacted and bounded and never contain credentials or raw provider bodies beyond the existing capped summary. - Mutation instructions select
Editfor one small unique replacement andWritefor a coherent whole-file rewrite. After an edit mismatch, the agent gets one fresh read/regeneration attempt. A second failedEditfor the same path, or a second failed shell patch command, in one prompt returns pi-agent-core's terminating tool hint, so the agent stops with the exact mismatch. Shellapply_patch,git apply, andpatchare explicitly treated as patch commands; the prompt directs the agent to useEditorWriteinstead. It must not hand-edit unified-diff artifacts or issue concurrent mutations for one path.
Consequences
- A single transient stream termination no longer forces regenerate or a new transcript/runtime boundary.
- The renderer sees one assistant bubble and one terminal lifecycle for a recovered turn.
- Provider errors remain diagnosable from agent timing and app session logs without exposing secrets.
- The retry budget is finite: persistent outages, authentication errors, context failures, and the second stream failure remain visible and actionable instead of entering an unbounded loop.
- Mutation recovery is deterministic and bounded. A genuinely stale file or invalid shell patch requires a new user/model decision after the second failure rather than another automatic tool turn.
Alternatives
Retry every provider error in the same turn
Rejected because authentication, model-selection, malformed-request, and context errors are not fixed by replaying the same request.
Retry stream failures indefinitely
Rejected because it hides persistent outages, consumes provider quota, and prevents the user from regaining control of the session.
Create a new assistant bubble for every stream attempt
Rejected because partial and failed attempts would look like multiple answers and would complicate durable transcript reconciliation.
Keep patch repair entirely prompt-driven
Rejected because wording alone does not reliably bound a model's retry loop. The host's unique-context checks and per-session mutation serialization remain the execution boundary, while the runtime's terminating tool hint enforces the second Edit or shell-patch failure stop in pi-agent-core.
References
docs/spec/03-runtime/02-agent-runtime.mddocs/spec/03-runtime/03-tools-and-permissions.mddocs/spec/03-runtime/08-error-codes.mddocs/spec/06-delivery/04-e2e-test-plan.md- Decision D186