Skip to content

fix(api): supersede streamed fragment rows with their assistant snapshot - #388

Merged
Yevanchen merged 2 commits into
mainfrom
fix/api-fold-snapshot-supersede
Jul 24, 2026
Merged

fix(api): supersede streamed fragment rows with their assistant snapshot#388
Yevanchen merged 2 commits into
mainfrom
fix/api-fold-snapshot-supersede

Conversation

@Yevanchen

Copy link
Copy Markdown
Collaborator

Fill what changed. Use N/A for irrelevant or maintainer-only items. See CONTRIBUTING.md for branch, CLA, generated file, and CI rules.

Summary

  • Track fragment rows that closed without a snapshot per stream key inside foldStreamedSessionEventRows, and let a message.added snapshot whose text prefix-matches the concatenated fragments replace those rows in place (at the first fragment's timeline position) instead of rendering as an extra "Assistant message" row.
  • Covers both broken shapes from YEF-884: the normal streamed order (message.completed lands before the aggregated message.added, which used to render the reply twice) and the fractured order (a lost message_start splits one reply into per-fragment messages, rendering "P" / "ong. What would you like to work on?" / full text as three rows).

Why

  • YEF-884: a ping reply to three agents rendered in the session event log as three separate "Assistant message" rows — two fragments plus a duplicated full text. Rows carry no message identity, so the read-time fold could not tell that the trailing snapshot was the authoritative copy of fragments it had already emitted. The fold is the only layer that can also repair rows already persisted by deployed drivers, so the supersede belongs here in addition to the driver-side fix (fix(claude): keep a streamed reply on one message when message_start is lost mosoo-agent-driver#77).

Verification

  • Commands: bun test tests/session-event-stream-fold.test.ts (16 pass, incl. new cases for the exact YEF-884 row sequence), bun test tests in apps/api (1044 pass), bun run lint, bun run tc.
  • Manual steps: reproduced the reported wire shape with the real Claude Code CLI (--output-format stream-json --include-partial-messages): the reply streams as 'P' + 'ong. What would you like to work on?' deltas with per-frame envelope uuids, matching the fractured rows in the screenshot.
  • Not run: N/A

Impact

  • User/API/contract changes: session process event timelines (agent Logs tab, public thread events) now show one row per streamed assistant reply; no schema or API shape changes.
  • Generated files / GraphQL / DB / lockfile: N/A
  • Env or config changes: N/A
  • Risk and rollback: supersede only fires on exact prefix/equality matches between a snapshot and the concatenated fragment rows of the same run and stream key, so unrelated messages pass through verbatim; revert the single commit to roll back.

Review

  • Closest review areas: apps/api/src/modules/sessions/domain/session-event-stream-fold.ts (closeGroupWithSnapshot), interaction with flushOpenStreams carry-over.
  • Known trade-offs: a snapshot whose text does not prefix-match a truncated fragment stream (for example a repaired long final output with a different prefix) still renders as its own row, same as before this change.

Streamed replies persist message.completed before the aggregated
message.added snapshot arrives, so the timeline folded the stream and
then rendered the snapshot again as a separate assistant message. When
the driver additionally loses the message_start frame, per-envelope
uuids fracture one reply into per-fragment messages and the log shows
'P' / 'ong. ...' / full-text rows (YEF-884).

Track fragment rows closed without a snapshot per stream key and let a
snapshot whose text prefix-matches the concatenated fragments replace
them in place, keeping the first fragment's timeline position. Repairs
existing rows at read time, including data written by older drivers.
@Yevanchen
Yevanchen force-pushed the fix/api-fold-snapshot-supersede branch from ba99c97 to fa26bf3 Compare July 21, 2026 16:07
@Yevanchen

Copy link
Copy Markdown
Collaborator Author

Clean-context review (delegated): APPROVE-WITH-NOTES, merge recommended.

  • Correctly placed: read-time fold is the only layer that also heals rows already persisted by deployed drivers; single importer (session-process-events.service.ts); no schema/migration/contract files; CI fully green.
  • Public thread events contract preserved: the surviving entry keeps the FIRST fragment's occurredAt/seq, so first-delta TTFT derivation is unaffected; what disappears is only the duplicate/fractured entries after completion (their ids were already unstable across reads pre-PR).
  • Not related to the Preview-lags-behind-Logs symptom: the live AG-UI WebSocket path is untouched — that investigation continues separately (see fix(web): unthrottle session stream rendering #395).

Fast-follow suggested (non-blocking), session-event-stream-fold.ts:211-214: on the eager-completed fractured ordering (final full-text fragment closed by its own completed before the aggregate added), fragments.startsWith(snapshot) passes and the merged row keeps the doubled fragments concat — one row rendering the reply duplicated inside itself. Guard: when fragments both start AND end with the full snapshot (duplicated-concat signature), prefer the snapshot text; add that ordering variant as a test. Also worth a one-line comment on the deliberate per-snapshot fragment consumption at :190-191.

@Yevanchen
Yevanchen merged commit 8939fed into main Jul 24, 2026
10 checks passed
@Yevanchen
Yevanchen deleted the fix/api-fold-snapshot-supersede branch July 24, 2026 10:57
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.

1 participant