Skip to content

fix(agent): report a failed stream as failed + release 0.3.0 - #109

Merged
maxekman merged 2 commits into
mainfrom
max/107-failed-stream-state
Sep 23, 2026
Merged

maxekman merged 2 commits into
mainfrom
max/107-failed-stream-state

Conversation

@maxekman

Copy link
Copy Markdown
Contributor

Summary

Fixes #107 — a stream that raises was stored as completed — and stamps the changelog for 0.3.0, the first release since 0.2.0 in March.

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Refactor / chore
  • Documentation

The bug

Verified before and after, driving CrashingStreamAgent through a real A2A.Plug:

before                          after
>>> WIRE says:   FAILED         >>> WIRE says:   FAILED
>>> STORE says:  COMPLETED      >>> STORE says:  FAILED

wrap_stream/3 finalized the task from Stream.transform/4's after-fun — a cleanup hook that runs however enumeration stops, so it reported success for every ending. The connection process rescued separately and told the client failed. Nothing reconciled them.

The fix

Stream.transform/5 splits the hook that runs only on normal completion (last_fun) from the one that always runs (after_fun), so enumeration can carry how it ended:

Ending Stored state
completed normally completed
raised failed
client disconnected mid-stream failed

Partial parts are kept — they're the agent's real output, and discarding them loses information — but the state no longer claims a result that was never produced.

Regression test included; it fails without the fix (15/16 passed).

Why now rather than later

The bug predates this work and shipped in 0.2.0. But #92 and #99 widened its blast radius: push notification receivers and resubscribers now read that state, so a failed stream had started actively telling them it succeeded. Releasing that amplification without the fix seemed wrong.

Release: 0.3.0

Second commit stamps ## [0.3.0] - 2026-09-23. mix.exs was already at 0.3.0 — bumped after 0.2.0 but never published — so it needs no change.

Also merges two duplicate ### Changed sections that had accumulated in [Unreleased].

Pre-1.0, so the breaking wire changes (StreamResponse, ErrorInfo, streaming gate) land in a minor bump.

Note for a2ui-elixir: it depends on {:a2a, "~> 0.2", optional: true}, which resolves to >= 0.2.0, < 1.0.0 — so mix deps.update a2a picks 0.3.0 up with no constraint change. The SSE wire format changed, so anything consuming stream events there will need checking.

Closes #107.

A {:stream, …} reply finalizes the task from the cleanup hook that fires
when enumeration stops, and that hook cannot tell why it stopped — so it
reported success for every ending. A stream that raised was stored as
completed, and its partial output kept as the result. The connection
process meanwhile rescued and told the client the task had failed, so
the two never agreed and nothing reconciled them.

Enumeration now carries how it ended, by splitting the hook that runs
only on normal completion from the one that always runs. A stream that
raised, or that was abandoned when the client disconnected, marks the
task failed. The parts it managed to emit are still kept — they are the
agent's output, and discarding them would lose information — but the
state no longer claims a result that was never produced.

Recently widened rather than introduced: push notification delivery and
resubscribe both read that state, so a failed stream had started
actively telling webhook receivers and subscribers it had succeeded.

Also merges two duplicate Changed sections in the unreleased changelog.

Closes #107.
Stamps the changelog for release. The version in mix.exs was bumped
after 0.2.0 but never published, so everything since March ships here:
the v1.0 wire format, push notification delivery, task resubscribe, and
the compliance work that drove the TCK baseline to empty.

Pre-1.0, so the breaking wire changes land in a minor bump.
@github-actions

Copy link
Copy Markdown

TCK Compliance Results

Result: passed - failures match the tracked baseline.

TCK run complete — 0 known failure(s), matching test/tck/expected-failures.txt.

             A2A TCK Compatibility Report              
═══════════════════════════════════════════════════════
SUT: http://localhost:9999
Timestamp: 2026-09-23T08:40:03.764848+00:00

OVERALL COMPATIBILITY: 76.8%

┌─────────────┬────────┬────────┬─────────┬───────┐
│ Level       │ Passed │ Failed │ Skipped │ Total │
├─────────────┼────────┼────────┼─────────┼───────┤
│ MUST        │     75 │     22 │      17 │   114 │
│ SHOULD      │      7 │      4 │       0 │    11 │
│ MAY         │      4 │      0 │       0 │     4 │
└─────────────┴────────┴────────┴─────────┴───────┘

BY TRANSPORT:
  agent_card:    10/10 ✓
  grpc:          0/72 (72 skipped) ✓
  jsonrpc:       93/100 (7 skipped) ✓
  http_json:     3/83 (80 skipped) ✓

═══════════════════════════════════════════════════════


- Generated html report: file:///home/runner/work/a2a-elixir/a2a-elixir/.tck/reports/tck_report.html -
99 passed, 166 skipped in 31.57s
Command: /home/runner/work/a2a-elixir/a2a-elixir/.tck/.venv/bin/python -m pytest tests/compatibility/ --sut-host=http://localhost:9999 --tb=short --transport=jsonrpc -q --compatibility-report=reports/compatibility --html=reports/tck_report.html --self-contained-html --junitxml=reports/junitreport.xml


========================================
TCK run complete — 0 known failure(s), matching test/tck/expected-failures.txt.
Stopping server (PID 2748)...

@claude

claude Bot commented Sep 23, 2026

Copy link
Copy Markdown

Reviewed — no high-confidence issues found. LGTM.

@maxekman
maxekman merged commit 794842d into main Sep 23, 2026
8 checks passed
@maxekman
maxekman deleted the max/107-failed-stream-state branch September 23, 2026 08:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Failed stream is stored as completed

1 participant