Skip to content

[fix] Paused live streams carry the terminal stop reason in the finish frame - #5065

Merged
mmabrouk merged 1 commit into
big-agentsfrom
fix/agent-stream-finish-reason
Jul 4, 2026
Merged

[fix] Paused live streams carry the terminal stop reason in the finish frame#5065
mmabrouk merged 1 commit into
big-agentsfrom
fix/agent-stream-finish-reason

Conversation

@mmabrouk

@mmabrouk mmabrouk commented Jul 4, 2026

Copy link
Copy Markdown
Member

Context

A live (SSE) agent stream that pauses for a tool approval ends with a generic finish frame instead of finishReason reflecting the pause. The batch envelope already reports stop_reason: "paused" after #5041. The cause is the same one #5041 fixed on the batch path: the runner emits its done event with no stopReason (the engine settles paused-vs-ended only after the event stream closes, on the terminal result), and the stream adapter read only the done event. The playground never noticed because it keys the approval UI off the interaction_request part; headless and protocol consumers of the stream were the ones getting a wrong finish frame.

Changes

agent_event_stream (the one layer that holds the terminal result) now appends a corrective terminal done event when the result's stop_reason disagrees with the streamed one. Both Vercel adapters keep the last non-null stop reason, so the corrective value wins and a null done never clobbers a real one. This mirrors the batch precedence exactly (fold(events, stop_reason=result.stop_reason)).

Before: paused live stream ends with finish and no pause signal.
After: paused live stream ends with finishReason: "other" derived from the terminal paused stop reason, same as batch.

The dev-only agent_run_to_vercel_parts variant had the precedence backwards (it read the terminal result only as a fallback), fixed for consistency.

Scope / risk

SDK-only; no runner or wire-schema change. A normal turn emits no extra done (the corrective event fires only on disagreement). The FE is unaffected either way.

Tests

  • New test_vercel_stream_finish_reason.py: paused-with-empty-done, normal turn unchanged, done-only fallback honored, corrective-done emission and non-duplication, end-to-end handler-to-adapter.
  • Extended test_vercel_stream_park.py with the same three cases against the dev adapter.
  • Full SDK unit suite: 1585 passed (10 pre-existing xfails, OpenRouter/litellm registry lag).

How to QA

Prerequisites: EE dev stack, a Pi agent with a tool set to Permission "Ask".

Steps:

  1. Chat so the tool fires and the approval prompt appears.
  2. Inspect the raw SSE stream (browser dev tools, the /messages response).

Expected: the final finish frame carries a non-stop finishReason derived from the paused stop reason. Approve; the resumed turn's stream finishes normally.

Automated tests:

cd sdks/python && uv run --no-sync python -m pytest oss/tests/pytest/unit/agents/adapters -n0

Edge cases: a stream that errors before the terminal result (no corrective event, error frame unchanged); a done event that itself carries a stop reason with no terminal value (still honored).

https://claude.ai/code/session_01DGj7GKafjkZeQXMsryWhb2

…op reason

The live runner emits its 'done' event with no stopReason (the engine settles
paused-vs-ended after the event stream closes, onto the terminal result only),
so a HITL-paused SSE stream ended with a generic finish frame while the batch
envelope already carried stop_reason: paused.

- agent_event_stream appends a corrective terminal 'done' when the result's
  stop_reason disagrees with the streamed one (the streaming analogue of
  agent_batch's fold(events, stop_reason=result.stop_reason))
- both vercel adapters prefer the last non-null stop reason, and the dev-only
  AgentStream variant now prefers the terminal result unconditionally (its old
  fallback-only read meant terminal-wins never applied)

Display was never affected (the FE keys off the interaction_request part);
this restores wire fidelity for headless/protocol consumers of the stream.
@vercel

vercel Bot commented Jul 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview, Comment Jul 4, 2026 2:57pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR changes stop-reason precedence handling in the Vercel streaming adapter and the agent handler. Done events with null stopReason no longer overwrite previously captured non-null values, terminal results take precedence when non-null, and the handler emits a corrective done event when streamed and terminal stop reasons disagree. Tests were added.

Changes

Stop-reason precedence handling

Layer / File(s) Summary
Vercel adapter stop-reason precedence
sdks/python/agenta/sdk/agents/adapters/vercel/stream.py
agent_run_to_vercel_parts and agent_stream_to_vercel_stream now only update stop_reason from done events when stopReason is non-null, and terminal AgentResult.stop_reason overwrites previously captured values when non-null.
Handler corrective done event
sdks/python/agenta/sdk/agents/handler.py
agent_event_stream tracks the streamed done event's stopReason, fetches the terminal run.result().stop_reason after the stream ends (tolerating unavailability), and emits an extra corrective done event when the terminal reason differs.
Tests for stop-reason precedence
sdks/python/oss/tests/pytest/unit/agents/adapters/test_vercel_stream_finish_reason.py, test_vercel_stream_park.py
New tests cover adapter-level precedence, handler corrective behavior via a fake harness, an end-to-end composition test, and a DTO import guard test.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Harness as Runner Harness
  participant Handler as agent_event_stream
  participant Adapter as Vercel Adapter
  participant Client

  Harness->>Handler: done event (stopReason may be null)
  Handler->>Handler: record streamed stopReason
  Harness->>Handler: stream ends
  Handler->>Harness: run.result()
  Harness-->>Handler: terminal stop_reason
  alt terminal differs from streamed
    Handler->>Adapter: corrective done event
  end
  Adapter->>Adapter: resolve final stop_reason (terminal wins if non-null)
  Adapter-->>Client: finish frame with resolved finishReason
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly matches the main change: paused live streams now carry the terminal stop reason in the finish frame.
Description check ✅ Passed The description is directly related to the streaming stop-reason fix and the accompanying adapter and test changes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/agent-stream-finish-reason

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mmabrouk

mmabrouk commented Jul 4, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@mmabrouk

mmabrouk commented Jul 4, 2026

Copy link
Copy Markdown
Member Author

Review ask: this is the streaming twin of the #5041 batch fold fix. The judgment call to check: the corrective terminal done event appended by agent_event_stream when the terminal result disagrees with the streamed value — versus threading the result through routing (which never sees it). The adapter-side rule is last-non-null-wins.

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@mmabrouk mmabrouk left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@mmabrouk
mmabrouk marked this pull request as ready for review July 4, 2026 15:48
@mmabrouk
mmabrouk merged commit cdfa8c3 into big-agents Jul 4, 2026
16 checks passed
@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. bug Something isn't working SDK tests labels Jul 4, 2026
mmabrouk added a commit that referenced this pull request Jul 6, 2026
mmabrouk added a commit that referenced this pull request Jul 10, 2026
mmabrouk added a commit that referenced this pull request Jul 10, 2026
mmabrouk added a commit that referenced this pull request Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working SDK size:M This PR changes 30-99 lines, ignoring generated files. tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant