fix: retain initial state for resumable streams - #1
Conversation
|
Review · round 2 · comment · d39a38d Merge as is — resumable state lifecycle is hardened; test-count documentation is staleThis PR should merge as written: it addresses a real missing resumable-stream invariant, commits the run identity and snapshot only after a usable backend body exists, clears replaced-run state on setup failure, and accounts for snapshot bytes during eviction. The new endpoint and run-ID guard are routed through both layers, and the added scenarios cover successful, failed, replacement, and missing-thread paths. Worth noting, not blocking
Prior findings
Review threads
Merge state: The PR is open, non-draft, and reported mergeable with an unstable merge state. CI is queued. Two review threads are marked unresolved and outdated; human review-request state is unknown.; blockers: Two review threads from @rupic-app are marked unresolved and outdated in the current review state. Evidence · 3 verified · 2 not verifiedVerified
Not verified
Read the full diff, context pack, all six changed files, surrounding routes, package scripts, base history, prior reviews, threads, issue comments, and PR status. No project tests or PR code were executed. CI is queued; three prior findings and three prior comments were re-verified against the current head.
Comment Review by Rupic. |
There was a problem hiding this comment.
2 inline comments below, on packages/sync-server/src/ThreadSync.ts (2).
Full review: #1 (comment)
| ): Promise<Response> { | ||
| this.generation++; | ||
| const currentGeneration = this.generation; | ||
| this.runId = randomUUID(); |
There was a problem hiding this comment.
runId and initialState are published before fetch confirms a usable backend body. On a connection error or non-OK/no-body response, getInitialState at :193-199 still returns the attempted run; on a failed replacement, the old body is not cleared before the new attempt, so a matching resume can replay old bytes under the new snapshot identity. Please commit the new identity only after stream setup succeeds, or clear all replaced-run state on every failure path.
| this.generation++; | ||
| const currentGeneration = this.generation; | ||
| this.runId = randomUUID(); | ||
| this.initialState = requestBody.state ?? null; |
There was a problem hiding this comment.
The new retained object is not represented in the memory-eviction estimate. With the existing 50 MB JSON limit, multiple active requests can retain large snapshots while getBufferedBytesEstimate returns zero when no stream bytes are buffered, so the eviction selector cannot relieve this new memory pressure. Please bound or account for snapshot memory in the same policy.
There was a problem hiding this comment.
1 inline comment below, on README.md.
Full review: #1 (comment)
| ``` | ||
|
|
||
| This starts redis, 2 sync servers, a scaler, and a fake AI backend (test-server). The test client runs 4 integration tests: | ||
| This starts redis, 2 sync servers, a scaler, and a fake AI backend (test-server). The test client runs 5 integration tests: |
There was a problem hiding this comment.
The README says the test client runs five integration tests and lists only the first five (README.md:141-147), but main() now invokes eight scenarios, including failed-start, failed-replacement, and missing-thread checks (packages/test-client/src/index.ts:384-393). The documentation should match the current suite.
|
the client half merged as assistant-ui/assistant-ui#5564 with the contract settled on "the server is authoritative": a resume request now carries |
Summary
POST /api/initial-state/api/resumewhen its run ID no longer matches the retained snapshotWhy
While testing resume after a refresh or local history mutation, I found that the sync server replayed the correct delta bytes but did not retain the state those deltas were generated against. A resuming client could therefore apply the run onto a different local base and silently reconstruct incorrect conversation state.
This is the server half of assistant-ui/assistant-ui#5562. The companion client change is assistant-ui/assistant-ui#5564, which fetches this snapshot before attaching to the unchanged delta stream. The server change should merge and deploy first.
Compatibility
Existing clients can continue calling
/api/resumewithout arunId. The replay stream format is unchanged. Clients using the new snapshot preflight include itsrunId, which makes a replaced-run race fail with409andX-Stream-Status: run_mismatchinstead of applying incompatible deltas.Verification
pnpm build