A streamed answer can look finished before the application knows whether it finished. The final visible sentence has punctuation. A tool argument looks plausible. The connection closes, the spinner disappears and a generic cleanup handler saves the accumulated text as the answer. A missing completion event has just become an invisible success condition.
The engineering decision is when provisional output may become an accepted answer or an actionable proposal. Fast display and final acceptance are different product promises. Treating them as one state makes an interrupted response difficult to distinguish from an intentionally short one.
This guide develops the response-envelope gate in our five-gate output-validation framework. Provider documentation establishes the protocol facts; the acceptance contract, failure policy and worked example below are AIEngine's design analysis, not a report of a tested AIEngine deployment.
Three endings that must not collapse into one
First, the transport stops delivering bytes. Second, the provider reports an outcome for a message or candidate. Third, the application decides that the resulting object satisfies its own requirements and commits it. These events can happen at different times, and the first does not establish the other two.
Anthropic's streaming documentation describes indexed content blocks with start, delta and stop events, followed by message-level events and a final message_stop. It also documents errors inside an established stream. A completed block is therefore narrower evidence than a completed message, and receiving text is not proof that the request will finish successfully.
Name these distinctions in the application. An operator should be able to tell “connection lost”, “provider stopped at a limit” and “complete response rejected by our validator” apart without inspecting a raw trace. A single boolean called done cannot express that difference.
Conversely, a socket problem after every required completion and validation condition has been recorded should not automatically erase an already accepted result. Define which final metadata is required for acceptance. If usage accounting arrives separately and is non-essential to the answer, record missing accounting separately; if final safety metadata is mandatory, acceptance must wait for it.
Parse the protocol, not the network read
With server-sent events, a network read is not an application message. The WHATWG HTML Standard specifies UTF-8 decoding, line processing and event dispatch on a blank line. Pending event data is discarded at end-of-file if its terminating blank line never arrives.
Use a maintained protocol parser or the provider's supported SDK, then test the version you ship. Feeding each raw network chunk to a JSON parser confuses byte delivery with event framing. A read can end inside a multibyte character, contain several events or contain only part of one. The application should receive decoded, typed events, not guess where a message ends from punctuation.
Do not assume every provider uses the same event shape. Amazon Bedrock's ConverseStream API reference exposes distinct content-block, message-stop and metadata events, alongside streaming exceptions. An adapter that extracts only text deltas discards much of the evidence needed to classify the outcome.
Keep framing errors separate from provider failures. A malformed event envelope is a protocol problem; a correctly decoded error event is a reported request failure. Neither belongs in the answer buffer. This distinction also helps decide whether to investigate your parser, an intermediary or the upstream service.
A terminal reason is evidence, not blanket permission
Provider terminology is not interchangeable. Anthropic's stop-reason reference distinguishes natural completion, a token limit, a configured stop sequence and a tool request. In particular, tool_use is a request to continue a tool interaction, not proof that the user's overall task has been answered.
Google's GenerateContent reference places finishReason on each candidate. It distinguishes STOP, MAX_TOKENS, SAFETY and MALFORMED_FUNCTION_CALL; an empty finish reason means token generation has not stopped. STOP can also reflect a configured stop sequence. A cross-provider adapter cannot safely translate every populated reason into “successful answer”.
Preserve the original reason and map it into an application outcome appropriate to the requested output type. A complete tool proposal and a complete prose answer require different downstream handling. An empty natural completion may be legitimate for one workflow and invalid for another. A custom stop sequence is acceptable only if the task contract explains what it terminates.
Use a versioned allow-list of accepted outcomes. An unfamiliar terminal reason should become an explicit unsupported outcome, not success. Harmless extension events need not break the stream: Anthropic's streaming reference documents pings and asks clients to handle new event types gracefully. Tolerate additions at the event layer without allowing unknown outcomes to authorise a commit.
Put the acceptance contract in one place
The proposed contract has two independent checks: enough evidence has arrived to classify the response, and the classified result is acceptable for this task. Neither a closing connection nor a parseable buffer may bypass them.
Give every attempt a unique identity and retain a bounded record containing:
- the logical task ID and its separate attempt ID;
- provider request ID, endpoint and adapter version;
- candidate and content-block identities, types and completion markers;
- raw terminal reason and normalised application outcome;
- transport or provider error, if present;
- required final metadata and whether it arrived;
- validator version, result and accepted payload reference; and
- whether this attempt still owns the right to publish the answer.
Keep content in separate typed buffers. Text, tool arguments and operational events should not be concatenated into one transcript. Bound buffered bytes, elapsed time and block counts so an unfinished stream cannot consume resources indefinitely. Sensitive payloads need an explicit retention policy; outcome diagnostics do not require logging every token.
One coordinator should perform the transition from provisional to accepted. It checks the required provider evidence, validates the assembled object and atomically confirms that this is still the active attempt before storing the final answer. A stale attempt must not win simply because it finishes last. The agent concurrency and fencing guide explains the related ownership problem at shared resources.
| Observed condition | Application classification | Permitted next step |
|---|---|---|
| Text arrives, with no terminal evidence | Provisional | Display only within the provisional-content policy |
| One content block closes | Block complete | Assemble that block; wait for the required message outcome |
| Connection closes before required completion | Interrupted | Retain a labelled partial result or offer bounded recovery |
| Provider reports an output limit | Truncated | Request a fresh attempt or an explicitly supported continuation |
| Provider finishes a tool proposal | Proposal ready for validation | Validate arguments and authority before dispatch |
| Required completion and task validation both pass | Acceptable result | Commit only if this attempt still owns publication |
This table is a proposed application policy, not a claim that all providers emit an identical sequence.
Decide what provisional display is allowed to reveal
Streaming trades earlier visibility for incomplete knowledge. A drafting assistant may reasonably show provisional plain text with a visible generating state. A workflow whose output must pass a whole-response check cannot obtain that guarantee by checking only the prefix already displayed.
Write the display policy independently of the acceptance policy. Choose between immediate provisional text, buffered approved segments or full-response buffering according to the harm of premature exposure. A status label does not undo disclosure: once a user has seen confidential or unsafe material, removing it later cannot make it unseen.
Keep irreversible actions outside incremental rendering. Do not auto-submit a form, send a message or execute a generated command as a side effect of a text-update callback. Rich previews also need the ordinary protections for their destination; provisional status is not an exemption from safe rendering.
If the stream fails, replace the generating state with an honest interrupted state. Decide explicitly whether the partial answer remains available for reference and whether copying it carries a warning. Do not make the completed and interrupted states visually or semantically identical merely because both have stopped changing.
Worked example: a valid object that is still provisional
Consider a hypothetical assistant proposing a support-ticket update. It is allowed to draft a change, not to apply it without the existing ticket-service checks. During an attempt, the tool-argument buffer becomes a syntactically complete object containing a ticket ID and a proposed status. The network then disconnects before the adapter receives all required completion evidence.
The object may parse perfectly. That does not establish that the provider finished the proposal. The JSON specification, RFC 8259, published in December 2017, defines JSON syntax and permits a JSON text to be a single value. As a simple illustration, both 1 and 10 are valid JSON numbers: successful parsing of a received prefix alone cannot tell you that no more bytes belong to it. The ticket example is an application of this distinction, not a documented provider incident.
What the adapter should preserve
The attempt remains interrupted, with its payload segregated from accepted proposals. No missing field is silently defaulted, no final marker is invented and no ticket action is dispatched. The user can see that generation was interrupted, while an operator can inspect the attempt ID and missing condition.
Suppose a second, separately identified attempt returns a complete proposal. That proposal still passes structural and task checks, and only the currently authorised attempt may publish it. If the first stream later delivers queued events, they cannot overwrite the second result. Cancellation of upstream work is a related control, covered in stop the work, not just the stream; discarding stale output is not evidence that upstream work actually stopped.
Completion does not prove execution
For this example, all tool execution waits until a proposal is accepted. If another architecture dispatches tools earlier, a broken model stream cannot prove that those actions did not happen. Their outcomes require separate reconciliation. Keep “proposal complete”, “command sent” and “ticket changed” as distinct records, even when a happy-path interface presents them as one interaction.
Retry an attempt without splicing two answers
A fresh generation is not the missing suffix of the previous one. It may choose different wording, ordering or arguments. Reusing the same display buffer across retries can manufacture an answer that neither attempt actually produced.
Prefer a fresh attempt buffer and an explicit replacement relationship. If a provider supports continuation, follow that endpoint's documented contract and retain the constituent response identities. The combined product still needs validation; a continuation request is not retrospective proof that the first response was complete.
The WHATWG standard also defines EventSource reconnection and the Last-Event-ID header. Those transport mechanisms do not themselves promise that an inference endpoint retains events or resumes generation. Verify replay support before treating a reconnect as continuity. Ordinary SDK or fetch-based clients should not be assumed to inherit browser EventSource behaviour.
If replay is supported, use its documented identifiers and ordering rules. Do not deduplicate by text: repeated words or identical argument fragments can be legitimate content. Without a replay contract, classify the interruption and start a new attempt under a bounded retry policy.
Test false success, then measure accepted completion
Build deterministic fixtures around the adapter and acceptance coordinator. Test the actual SDK version as well as your event normalisation, because an iterator ending normally and a provider declaring successful completion are different observations.
Exercise at least these failure points:
- disconnect immediately after response headers, before any content;
- split an event across reads and split a UTF-8 character across byte boundaries;
- close inside an SSE event before its terminating blank line;
- deliver a provider error after several valid text deltas;
- end one block while other required blocks remain unfinished;
- report token exhaustion after the buffer has become parseable;
- deliver an unfamiliar terminal reason or a harmless extension event; and
- allow an older attempt to finish after its replacement has been accepted.
For every fixture, assert the stored outcome and the absence or presence of the final answer write. Where tools are involved, assert dispatch counts separately. Testing only the final visible text misses the unwanted commit that this design is meant to prevent.
Track time to first display alongside time to accepted completion. Report interrupted, truncated, rejected and accepted attempts separately, plus retries per accepted answer and stale-result drops. Sample accepted records for their completion evidence; the target for accepted answers missing required evidence is zero, not a low average hidden among successful calls.
Revisit the contract when an SDK, endpoint, model behaviour, final-metadata requirement or retry strategy changes. The useful performance improvement is an earlier answer that still has a defensible acceptance record. A spinner disappearing sooner is not the same result.
Evidence scope
The six linked primary sources were checked on 9 September 2026. The provider pages are living API documentation; Google's reference lists 28 August 2026 as its last update. Pin the deployed API and adapter versions and recheck their exact event contracts. The proposed coordinator, policies and illustrative ticket scenario are engineering recommendations, not provider guarantees.



