fix(server): assistant streaming no longer rescans thread history - #5855
fix(server): assistant streaming no longer rescans thread history#5855cheruvian wants to merge 2 commits into
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
ApprovabilityVerdict: Needs human review This PR significantly refactors the projection pipeline architecture, introducing event-to-projector subscription routing, targeted shell summary refresh, transaction model changes, and batched cursor updates. While it includes extensive tests, changes to core infrastructure processing behavior warrant human review. You can customize Macroscope's approvability policy. Learn more. |
Refresh only shell fields invalidated by each event so assistant deltas and ordinary activities stay independent of thread history. Recompute approval, user-input, plan, and revert summaries from their owning projections, with SQL-cost and replay-equivalence coverage.
Route events only to projectors that can mutate their projections while advancing every live cursor atomically after successful projection. Preserve first ownership bindings for projected identities, order bootstrap dependencies, and prune approvals owned by reverted turns so live projection and replay stay equivalent.
2d7f0e4 to
baa977a
Compare
|
We investigated a Windows T3 instance where agent responses were delayed by minutes even though the provider CLI responded normally outside T3. Server traces showed thread-shell summary refreshes repeatedly rereading growing thread histories. During the incident, a single refresh took as long as 4.97 seconds, creating a backlog that delayed provider events and UI updates. After applying this PR:
This confirms the PR addresses a major bottleneck we observed. We are not claiming it resolves every possible source of T3 latency. |
Fixes #5719.
Problem
The threads projector handled
thread.message-sent,thread.activity-appendedand four other event types with one blanketrefreshThreadShellSummary, which reloaded the thread's entire messages, proposed plans, activities and pending approvals to recompute four summary columns. With assistant streaming enabled every provider text delta is a durablethread.message-sentevent, so projection work scaled with streamed chunks times accumulated thread history.Fix
Each of the four shell-summary fields is owned by exactly one projection, and assistant text can change none of them. The projector now refreshes only the fields an event can actually invalidate:
latestUserMessageAt— user messages only, advanced in place as a running maximum, since messages are append-only outside revertpendingApprovalCount— approval activities andthread.approval-response-requestedpendingUserInputCount— user-input activities andthread.user-input-response-requestedhasActionableProposedPlan— plan upserts and the events that movelatestTurnIdthread.revertedstill re-derives everything, since it is the only path that removes projected rowsFields are recomputed from the owning projection rather than incremented. That matters for rebuilds: each projector keeps its own cursor and bootstraps over the whole stream in turn, so the threads projector sees the upstream projections already fully caught up — recomputation converges to the correct value where counter arithmetic would drift.
Streaming, event volume and event ordering are unchanged. Only the cost of projecting each event drops.
Measurements
The new test installs a SQL client decorator that records every statement the pipeline issues. For 8 streamed deltas:
The same test pins history-independence: identical statement counts against a 4-activity and a 200-activity thread. A third test appends events without projecting them, runs
bootstrap, and asserts the replayed summary matches live projection.Worth noting for expectations: buffered delivery is the default now, so the ~36 events per message in the issue report only apply to
enableLegacyTokenStreaming. This change still helps every setup, becausethread.activity-appendedfires constantly in both modes and was paying the same full-thread scans.Verification
vp test run apps/server/src/orchestration apps/server/src/persistence apps/server/src/relay— 45 files, 298 tests passedvp run --filter t3 typecheck— cleanRisks
thread.session-set; that is gone, but every writer of the underlying rows now triggers its own field refresh, and only this pipeline writes these columns.activityIdwere re-upserted with a differentkind, from a user-input kind to a non-user-input one, the counter would not be revisited. No provider path does that today.Model and harness: Claude Opus 5 (1M context) in Claude Code.
🤖 Generated with Claude Code
Note
Medium Risk
Changes core orchestration projection semantics (transactions, cursor invariants, summary derivation) that affect every live event; risk is mitigated by extensive new cost/rebuild/revert tests and stricter upsert guards.
Overview
Stops assistant streaming deltas from reloading whole thread collections when updating
projection_threadsshell fields. Shell refresh is now field-scoped viathreadShellSummaryFieldsForEvent/refreshThreadShellSummary(..., fields)so assistantthread.message-sentevents only bumpupdatedAt(user messages still advancelatestUserMessageAtincrementally); approvals, user-input, plans, and revert paths refresh only the summaries they can invalidate.Reworks live
projectEventinto one SQL transaction: enforce all projector cursors atsequence - 1, run only subscriber projectors per event (projectorNamesForEvent), then advance every cursor in a singleprojection_statebatch (upsertMany). Bootstrap/replay still uses per-projector transactions.Hardens projection upserts when IDs collide across threads: conditional
ON CONFLICTupdates for messages, activities, plans, and pending approvals; message/approval apply paths ignore conflicting thread or role;thread.revertedprunes pending approvals tied to removed turns.Adds
ProjectionPipeline.summaryCost.test.ts(SQL statement recording, history-independence, targeted summary scans, bootstrap/revert parity) and extendsProjectionPipeline.test.tsfor rollback cursor invariants and bootstrap resume after partial failure.Reviewed by Cursor Bugbot for commit baa977a. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Fix assistant streaming to avoid rescanning thread history on each delta event
projectorNamesForEventrouting so only projectors subscribed to a given event type runapply; non-subscriber projectors still advance their cursor.assertLiveProjectorCursorsto enforce that all projector cursors are atsequence - 1before applying a live event, failing fast on out-of-order projection.threadShellSummaryFieldsForEvent, so events like assistant streaming deltas no longer trigger full thread-collection scans.thread_id,role, orkindconflict with a different owner.ProjectionStateRepositorygains aupsertManymethod for batching all projector cursor advances in one SQL statement.projectEventnow fails before applying if any projector cursor is not at the expected prior sequence, which is a new hard precondition not previously enforced.Macroscope summarized baa977a.