perf(serve): Restore large sessions selectively - #9055
Conversation
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
E2E and benchmark reportValidated the final bundled CLI at commit
Both daemons advertised Scope note: these fixtures intentionally have no compression record. They demonstrate bounded initial UI replay, pagination metadata, healthy outer-daemon shutdown, and registered sibling continuity. They do not establish a universal memory reduction for every uncompressed transcript, and wall time remains benchmark evidence rather than a CI threshold. Verification summary
|
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
🩺 serve daemon A/BBuilt the PR base vs this PR head ✅ No response changes against the PR base across 4 scenario(s). — Qwen Code · serve A/B |
|
Thanks for the PR — re-running the gate after the lease-gating fix ( Template still complete ✓. Problem: unchanged — observed, not theoretical. #8678 (P1, open) documents a real restore-deadline incident on a 76.69 MiB transcript; the design was reviewed and merged as #8743 and all six prerequisites are in. Direction: aligned, unchanged from prior passes. Bounded selective restore is squarely within the session-management work the project is actively investing in. Size: the new commit adds 1 production line and 43 test lines — now ~5,023 production logic lines (+3,966 / −1,057 across 29 production files, including the design doc) vs ~3,697 test lines. Per the core-module gate this is not size-blocked ( Approach: Risk: Stage 1e match unchanged — 10 files under Moving on to code review. 🔍 中文说明感谢贡献——这是 lease-gating 修复( 模板依然完整 ✓。 问题: 不变——已观测到,不是理论问题。#8678(P1,open)记录了 76.69 MiB transcript 上真实的恢复超时事故;设计已在 #8743 评审合入,六个前置均已合入。 方向: 对齐,与前几轮一致。有界选择性恢复属于项目持续投入的 session-management 工作。 规模: 新提交仅增加 1 行生产代码与 43 行测试——目前约 5,023 行生产逻辑(29 个生产文件含设计文档,+3,966 / −1,057),测试约 3,697 行。按核心模块门禁不被规模阻断( 方案: 风险: Stage 1e 命中不变—— 进入代码审查。🔍 — Qwen Code · qwen3.8-max Reviewed at |
Code reviewRe-run at The lease-gating fix ( The other six threads from the maintainer's round — I checked the author's counter-claims against the code rather than taking the disposition table at face value:
sequenceDiagram
participant P1 as Client
participant P2 as QwenAgent loadSession
participant P3 as Config
participant P4 as Writer lease
participant P5 as TranscriptReader
participant P6 as Session publication
P1->>P2: load with historyPageSize
P2->>P3: initialize config with projection source
P3->>P4: acquire writer lease
P4-->>P3: ownership established
P3->>P5: readRestoreProjection, fresh index
P5->>P5: one scan, per-record hints
P5->>P5: selective reads, runtime plus replay page
P5-->>P3: projection with reduced runtime state
P3->>P2: hydrate history, recorder, goal prepare
P2->>P2: build replay envelope, enforce 32 MiB and 10000 update caps
P2->>P6: create Session only after validation
P6-->>P1: response with bounded replay envelope
P2->>P3: finalizeSessionRestore, goal and file-history activation
Files changed (30 of 44 shown)
TestingCI run — no PR code was built or executed by this gate; the evidence below is quoted from the PR's own CI checks on the reviewed commit, fetched via the API. All three
Sandboxed verification: the first Not verified by this gate: live daemon/TUI behavior — per the static-review rule the gate never executes PR code. Live-behavior evidence comes from the maintainer's local A/B report (independent, real daemons) and the sandboxed 中文说明代码审查:全量 diff 结论延续前几轮(架构与独立方案一致、reducer parity 由构造保证、writer-lease 时序、隐藏 Goal 隔离、fail-closed 上限、goal prepare/activate 均已核实)。本轮审查唯一新提交,并独立核对了作者对其余 thread 的处置:
测试:CI 运行,门禁未构建或执行任何 PR 代码。该 head 上三个 沙箱验证:第一次 门禁未验证:实机 daemon/TUI 行为(静态审查规则禁止执行 PR 代码);实机证据来自 maintainer 的本地 A/B 报告与沙箱 — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 3/5 — the lease-gating batch is correct and CI is fully green on this head; the cap is still the core-size escalation policy plus open maintainer calls, not doubt about the code. What changed since the last pass: @wenshao's second review round asked for seven things; the author fixed the one with a concrete silent-failure path (the lease predicate mismatch) and pushed back on the other six with reasons. I reviewed the fix and stress-checked every pushback against the code rather than the disposition table:
The picture around the code is as strong as it gets for a PR this size: all three What keeps this at 3/5 is policy and open calls, unchanged: ~5,000 production lines across core session restore, the goal runtime, and the ACP integration layer is the class of change the gate hands to a human architect — approval here was never the gate's to give. ⏸️ Deferring to @wenshao — three things for the maintainer's call:
No approval marker on this run — the verdict is defer, and the core-size escalation stands until a maintainer signs off. 中文说明置信度:3/5 —— lease-gating 批次正确、该 head 的 CI 全绿;上限仍来自核心规模升级策略与待定的 maintainer 决策,而非对代码的疑虑。 自上一轮以来的变化:@wenshao 的第二轮评审提出七点;作者修复了其中唯一有具体静默失败路径的一项(lease 谓词不一致),并对其余六点给出了理由。我审查了修复本身,并逐项对照代码核实了每个反驳,而不是只看处置表:
代码外围证据对这个规模的 PR 来说已相当充分:三个 维持 3/5 的仍是策略与未决项:约 5,000 行生产代码横跨核心会话恢复、goal 运行时与 ACP 集成层,属于门禁应交人类架构师的变更类别——批准从来不在门禁权限之内。 ⏸️ 转交 @wenshao —— 三项 maintainer 决策:
本轮不附批准标记——裁决为 defer,核心规模升级在 maintainer 签字前持续有效。 — Qwen Code · qwen3.8-max Reviewed at |
Local verification report (maintainer)I built this PR locally and ran a base-vs-head A/B against real daemons with synthetic large transcripts. The functional contract holds up well end to end — bounded replay, pagination metadata, full-replay compatibility, typed limits, resume path, and sibling continuity all verified. One measured tradeoff maintainers should weigh before merging: cold bounded restore got ~3.3× slower than base on this machine, while the warm/live refresh path got ~11× faster (details below). Method. Two isolated worktrees — base = merge-base What I verified (test plan items 1–7)
Measured tradeoff for maintainers to weigh
Peak RSS during the 253 MB bounded cold load was only ~8 % lower on head (+432/+439 MB vs +465/+480 MB) — consistent with the PR's own disclaimer that this is not a universal memory reduction, since the full model-facing history is still hydrated. The cold-load slowdown scales linearly with transcript size and stays far below the 60 s restore budget, and on machines where base's synchronous full materialization is the bottleneck (the #8678 scenario) head's cooperative index scan should behave much better than these numbers suggest — but on fast hardware the selective path currently pays ~3× the wall clock of the old full parse. Worth a look at the index-scan hot path (or at least a conscious sign-off) before merge. One more pre-existing bound worth knowing (not a regression): full visible replay of the 92 MB session fails on both sides with 500 Screenshots (real stack: bundled head CLI, real daemon, web-shell, mock model)30,000-record (92 MB) session restored and opened in web-shell, only recent records rendered: Prompt round-trip on a bounded-restored session (mock reply Environment: macOS 26.6 arm64, Node v24.18.1; head 中文版本本地验证报告(维护者)我在本地构建了此 PR,用合成大 transcript 对真实 daemon 做了 base/head A/B 对比。功能契约端到端全部成立——有界回放、分页元数据、全量回放兼容性、typed 上限、resume 路径、sibling 连续性均已验证。合并前需要维护者权衡的一个实测代价:冷有界恢复在本机比 base 慢约 3.3×,同时 warm/live refresh 路径快了约 11×(下详)。 方法:两个隔离 worktree——base = merge-base 验证结论(对应测试计划 1–7)
需要维护者权衡的实测代价
253 MB 有界冷加载期间的 RSS 峰值 head 仅低约 8%(+432/+439 MB vs +465/+480 MB)——与 PR 自述一致:由于模型侧历史仍需全量水合,这不是普适的内存下降。冷加载变慢随 transcript 大小线性扩展,仍远低于 60 s 恢复预算;在 base 的同步全量物化真正成为瓶颈的机器上(即 #8678 场景),head 的协作式索引扫描应明显好于这些数字——但在快速硬件上,选择性路径目前要付出旧全量解析约 3× 的墙钟时间。建议合并前看一眼索引扫描热路径(或至少明确签字认可)。 另一个值得了解的既有边界(非本 PR 回归):92 MB 会话的全量可见回放在两侧都以 500 环境:macOS 26.6 arm64,Node v24.18.1;head |
samuelhsin
left a comment
There was a problem hiding this comment.
One non-blocking pagination-policy finding.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
Addressed the current unresolved review batch at fb3ace2.
Verification on the pushed head: targeted Core/CLI tests, complete affected test files (111 Core + 745 CLI before the final best-effort guard, plus its targeted regression), npm run build, npm run typecheck, npm run lint, and two consecutive clean broad diff audits. The 256 MiB transcript cap, explicit recent 32 MiB/10,000-update cap, omitted historyPageSize full visible replay compatibility, and scoped benchmark claims remain unchanged. |
|
Sandboxed verification: ❌ not passed — findings reported (agent verdict) - workflow run Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 34 passed · 1 failed · 35 total 中文 — 判定:❌ 不通过 · 报告了发现(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:34 通过 · 1 失败 · 35 总计 Verification reportPR 9055 — perf(serve): Restore large sessions selectivelyVerdict: 中文摘要
Central claim + A/BCentral claim: cold Harness:
Witnesses: Scripted A/B results: page shape/metadata correct (A3–A5), FindingsF1 (medium) — the perf benefit is conditional on compression; uncompressed cold restore regresses ~3× at the core seamOn the uncompressed fixture the selective path costs index-build + a second full read, while base reads once. Measured with the reader's own With a Direction (unmeasured — not a shipped fix)When no compression checkpoint exists, the records parsed during the index scan are exactly the records the runtime reconstruction re-reads; the reader already threads a Secondary claims (all verified)
Targeted gates (all green, 3,223 tests)core Not covered
MethodologyEnvironment: CI merge-ref checkout (depth 2) in Evidence imagesHarness scripts and raw logs are in the workflow run artifacts (7-day retention). — Qwen Code · sandboxed verification |
|
Triage re-run completed without a new review.
The stage comments above were updated with the latest result. View workflow run. 上方各阶段评论已更新为最新结果。查看工作流运行。 |
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
Review round summary for
Verification: CLI config tests 334/334, repository build, repository typecheck, |
|
@qwen-code /triage |
|
Sandboxed verification: ❌ not passed — findings reported (agent verdict) - workflow run Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 37 passed · 1 failed · 38 total 中文 — 判定:❌ 不通过 · 报告了发现(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:37 通过 · 1 失败 · 38 总计 Verification reportPR 9055 — perf(serve): Restore large sessions selectively (follow-up round)Verdict: 中文摘要
Previous-finding status (follow-up round)
Central claim + A/B (re-measured at new head and new base)Central claim: cold restore with an explicit Harness:
Witness: Delta commit verification (
|
| guard the PR introduces | suite | mutant | result |
|---|---|---|---|
isAcpMode === true clause (cli gate) |
new test in config.test.ts |
remove clause | RED at the intended assertion — expected "spy" to be called once, but got 0 times (projection deferred and never consumed); restored → green |
bounded-recent selection (selectRestoreReplayUuids) |
session-transcript-reader.test.ts (111) |
recent→all | 4 RED on page-shape (anchorRecordId/hasMore/2-record page vs full list), 107 unaffected; restored → green |
Survivor classification (completeness reporting, not merge conditions): no test pins the defer=true branch — neither the CLI's "ACP + lease → do not preload" path nor the core post-lease consumption (config.ts:3217–3224) is referenced by any *.test.ts (grep across the repo for after_writer_lease, projection_acquisition, sessionRestoreProjectionSource in tests = 0 hits). Coverage gap, not dead code: that branch is the daemon's production path. See F2.
Findings
F1 (medium, stands) — the perf benefit remains conditional on compression; uncompressed cold restore regresses ~2.9–3× at the core seam
Re-measured at the new head against the new base. On the uncompressed fixture the selective path pays index-build + a second full read, while base reads once. Stage timing via the reader's own ForTest hooks (f1-stages.mjs, two runs): index build 229–329 ms (the index scan already parses every line — base's entire cost), then post-index 720–770 ms with selectedLineReads = 30,001 (84.8 MiB re-read and re-parsed) because runtime.apiHistory requires every record when no compression checkpoint exists; the selective read bounds only the 2-record replay page. With the chat_compression checkpoint the mechanism engages: post-index reads collapse to 202 records (0.6 MiB) and head beats base on both axes. Reproduce: node harness/run-ab.mjs and node harness/f1-stages.mjs.
Consistent with the author's disclosed caveat ("fixtures contain no compression record … not a universal memory reduction"), but #8678's deadline scenario does not promise compressed sessions, so reviewers should weigh the uncompressed wall-time regression against the bounded-replay benefit. Not a correctness defect: parity holds in both regimes (P1–P3, D1–D3). The previous round's unmeasured direction (reuse index-scan parses via the existing preloadedRecords context, gated on snapshot size) remains the natural fix path; not implemented or measured here.
F2 (low, new) — the defer branch has no test on either end
The mutation matrix shows the new test pins only the non-ACP preload side. The defer=true path (ACP + lease: projection withheld at config time, fetched after SessionWriterLease.acquire) is exercised by no unit test on the CLI side and no core test references sessionRestoreProjectionSource at all. Behavior verified here by static trace only (both ends align, gate ↔ consumer). A test asserting projectionSource is NOT called at config time under {acp:true} + sessionWriterLease:true, and one asserting core consumes the deferred projection after lease acquisition, would close the gap. Not a merge blocker: this is completeness, and the path is the PR's own daemon production flow.
Not covered
- Daemon-level behavior: sibling-session continuity during restore, writer-lease timing, the 60 s budget under load — seams verified here are
SessionTranscriptReader/SessionService/loadCliConfig, not a live daemon. hideInheritedHistoryhidden-Goal fence: covered only by the PR's own tests, not independently probed (both rounds).goalCheckpointWindow(absent from my fixtures) and a cross-build oracle forresumeTokenCounts(base exposes no core seam for it).- Malformed-record parity: carried forward from the previous round on the proven-unchanged input closure (delta touches only cli config; lockfile untouched), not re-driven.
- Export/fork/TUI/non-interactive full-content readers; Windows/Linux E2E.
- Per-commit attribution for commits 1–4: shallow depth-2 checkout makes them unreachable (
git rev-list HEAD^1..HEAD^2returns 1 vs 5 in the snapshot); they were individually exercised by the previous round atfb3ace2e, and this round verifies the aggregateHEAD^1..HEADdiff plus the exact deltafb3ace2e..HEAD^2. - Repo-wide lint/bundle (the PR's own CI covers these); head typecheck is implied by the pre-run
npm run build(tsc) at HEAD; base core typechecked clean (exit 0) after the environment fix below.
Methodology
Environment: CI merge-ref checkout (depth 2) in node:22-bookworm; npm ci + npm run build pre-run at head; artifact dir tmp/pr9055-verify-20260813-120944/. Base control: git worktree add tmp/base-tree HEAD^1 (9d55fab5f8), rebuilt packages/core there. Two symlinks were needed (and are quoted as the isolation control): root node_modules (the core tsconfig's paths maps @lydell/node-pty to ../../node_modules/…/node-pty.d.ts, absent in a worktree — without it base typecheck fails with TS7016) and packages/core/node_modules (npm-hoisted version-conflicting deps). Realpath assertion: readlink -f on both resolves into the head-installed tree, which is a clean control because the PR changes no package.json/lockfile (verified: neither appears in git diff HEAD^1..HEAD --name-only); base core has zero @qwen-code/* dependencies (dependency list enumerated), so base cells importing base dist by absolute path cannot load head code. Harnesses (gen-fixtures.mjs, prep.mjs, cell.mjs, run-ab.mjs, stage-timer.mjs, f1-stages.mjs, envelope-limits.mjs, mutations.mjs) are mock-free: real compiled dist/, real JSONL fixtures (84.1 MiB uncompressed + compressed variants, rich parity fixture with fork/interrupted-tool-pair/goal/notification/title, 259.99 MiB sparse), one fresh process per cell, peak RSS from /proc/self/status VmHWM. All 38 assertions are scripted exit/oracle comparisons; raw logs in logs/ (ab-results.json, gate-*.log, m1/m3-*-mutant.log, f1-stages.log, envelope-limits.log), captures in evidence/. Gate liveness proven by the two mutations (both suites went red under a planted violation, green after restore). Mutation sources were restored sha256-byte-identical (e210b151… config.ts, 3e90adc8… reader) and git status is clean.
Evidence images
Harness scripts and raw logs are in the workflow run artifacts (7-day retention).
— Qwen Code · sandboxed verification
|
Triage re-run completed without a new review.
The stage comments above were updated with the latest result. View workflow run. 上方各阶段评论已更新为最新结果。查看工作流运行。 |
|
Detailed review follow-up for current head I found no remaining correctness, security, data-loss, or restore-scope blocker. Two non-blocking follow-ups remain worth recording:
Given the number of review rounds and the project rule to land only Critical fixes at this stage, I recommend deferring both items to follow-up work. They do not block approval. |
ytahdn
left a comment
There was a problem hiding this comment.
Approved at 941c54b5368101807c58cadf6665764bd7b5e0e8. I found no remaining blocking correctness, security, data-loss, or session-scope issue. The two non-blocking follow-ups are recorded in the PR discussion.
|
Thanks — deferring both advisory findings. The uncompressed cold-restore wall-time trade-off is already disclosed and does not affect restore correctness, bounded replay, or the validated daemon-level behavior; avoiding the second pass is follow-up performance work outside this PR. The focused deferred-projection unit coverage is also a non-blocking completeness improvement, with the production ACP + writer-lease path covered by daemon E2E. After the existing review rounds, the repository policy limits this stage to Critical fixes, and the latest maintainer review confirms neither item blocks approval. No branch change is needed. |
yiliang114
left a comment
There was a problem hiding this comment.
Fresh independent review of head 941c54b (all 44 files, focused on the restore path). No P0/P1 found.
What I verified:
- Index build + selective seek: the index retains only hints (no payloads), the 256MiB index cap is enforced before indexing, and materialization seeks selected (offset,length) segments with cooperative yielding and a single-line cache. A post-read snapshot-signature check fails the restore closed if the transcript changes mid-restore. Torn/partial JSONL still goes through the tolerant line parser.
- Replay shapes: all/recent/none all behave per design. none restores full runtime state with no replay side effects. recent uses bounded backward selection, and small sessions still absorb the leading metadata prefix (no-earlier-turn branch); the leading-metadata test pins hasMore=false.
- Bounds fail closed: 32MiB / 10k-update replay envelope limits are enforced inside sendUpdate and mapped to typed errors (-32010/-32011/-32012); limit normalization rejects out-of-range input.
- Concurrency/cache: index cache re-keys on (fileIdentity, size, mtime); cold restore deliberately builds fresh for writer-lease freshness and offers non-clobberingly to the cache; same-shape restores coalesce at bridge admission.
- No event loss/dup: the api-history, resume-token, artifact, and file-history accumulators are behavior-preserving extractions of the legacy loader (parity test present); restore is read-only and does not touch the live writer path; bridge event-epoch/early-event buffering is unchanged.
- Back-compat: the legacy sessionData/restoreSessionState path is intact, bridge fields are additive, recoverGoalFromRecords/buildApiHistoryFromConversation/getResumeTokenCounts wrappers are preserved, and the channel/keepalive load->resume switch is consistent (resume = replay:none, still returns the event cursor).
Status of wenshao's findings at this head:
- Checkpoint-window hard-fail: fixed. Accumulator construction and finish() now catch EvidenceSourceUnavailableError/InvalidGoalEvidenceReferenceError and omit the window, falling back to runtime degradation (design doc updated to match).
- Unrestorable-Goal cleared card: fixed. renderPreparedGoalUpdate now receives replayedRecords and emits on both streamed and bulk paths.
- --fork-session projection binding: fixed. The projection source resolves the forked id and the deferred path binds the final session id.
- writer-lease predicate mismatch (config.ts:2009): fixed on this head. Deferral now requires ACP mode, matching core's effective predicate; non-ACP hosts preload instead of silently restoring empty. I re-derived both predicates to confirm parity.
- realtime_message turn-boundary divergence: not present at head; rebuildTurnBoundaries and getSessionTurnRecordHint both exclude it.
- Deferred/intentional remainders (non-blocking): userDisplayTextsForTitle not projected, hideInheritedHistory continuation-paging contract, foreign-sessionId fail-closed boundary, hidden-Goal update fence, cold index-build dedup. I agree none are P0/P1.
P2/P3 nits (non-blocking):
- restoreProjectedState leaves userDisplayTextsForTitle empty; until the promised follow-up lands, a resumed session with no custom title may auto-title from the post-restore prompt and rewindRecording's projectionStart math clamps to 0. Worth tracking the follow-up issue.
- readPage resolves beforeRecordId against the unfiltered replayUuids while the anchor can come from a hideInheritedHistory-filtered list; safe today since no daemon entrypoint combines the two, but a short comment would prevent a future foot-gun.
CI on 941c54b: green (only the review-pr automation check still running).
|
Released in v0.21.12. |













What this PR does
This PR implements the selective daemon session restore contract designed in #8743. Cold
loadSessionandresumeSessionnow build one transcript index and read only the records needed to reconstruct model/runtime state and the requested replay projection, instead of materializing the complete JSONL conversation before applying replay pagination.The projection preserves active-chain semantics for compressed and legacy model history, recording ancestry, interrupted turns, FileHistory, artifacts, Goals and checkpoint evidence, attribution, telemetry, cumulative usage, source metadata, and background notifications. Writer-lease restores acquire the authoritative projection after ownership is established, while live-session refreshes reuse a narrower cached projection under the existing recorder barrier. Runtime hydration, response construction, Session publication, rollback, and delayed Goal/FileHistory/attribution activation remain behind the existing transaction boundaries.
Explicit response-mode loads with
historyPageSizenow select a bounded recent replay before payload reads, return pagination metadata, and reject a transformed replay envelope above 32 MiB or 10,000 updates before publishing the Session. Loads that omithistoryPageSizepreserve full visible replay compatibility. Resume-only channel and scheduled-task callers now use the no-history resume path. Hidden inherited Goal state is restored for runtime correctness but fenced from client presentation until a visible replacement is created.The cold index has a 256 MiB source-byte ceiling and cooperative record-boundary scheduling. This PR does not add durable resume checkpoints, worker-thread parsing, or streaming JSON parsing, so a legacy transcript still requires one complete index scan and a single large JSON record remains an indivisible parse interval.
Why it's needed
Issue #8678 showed that a large persisted session could exceed the restore deadline even when the client requested only recent history. The old cold path fully materialized and reconstructed the JSONL transcript before applying
historyPageSize, and writer-lease activation could perform another full read. That made restore latency and peak allocation scale with the whole transcript and could delay sibling sessions sharing the ACP child.The timeout safety, transactional WebUI switching, attachment fencing, and exact request-shape prerequisites have already landed. This PR supplies the performance layer: one selective projection becomes the authoritative handoff from persistence to Config and Session setup, while preserving existing failure isolation and public compatibility.
Reviewer Test Plan
How to verify
loadwith a small explicithistoryPageSize. Confirm the response contains only the aligned recent page, reportshistoryHasMoreandhistoryAnchorRecordIdwhen older records exist, and the restored Session remains promptable.historyPageSize. Confirm full visible replay is preserved for compatibility and is not coalesced with the bounded request shape.hideInheritedHistory, place a newer inherited Goal behind an older visible Goal. Confirm runtime recovery remains correct while the client sees only the visible Goal; active recovery broadcasts for the hidden Goal must remain fenced until a different Goal replaces it.Evidence (Before & After)
Before: replay pagination was applied only after the full persisted JSONL had been materialized and reconstructed, so a recent-page request still paid the full cold-load cost and could exceed the restore deadline described in #8678.
After: the final bundled CLI restored two 30,000-record fixtures of 86.18–86.21 MB with
historyPageSize: 2. The empty-channel run returned 200 in 1,350 ms withhistoryHasMore=true; the shared-channel run returned 200 in 651 ms, and its registered sibling returned 200 both during and after the restore. Both daemons advertised the 60,000 ms restore budget and exited cleanly. These fixtures contain no compression record, so this evidence demonstrates bounded UI replay and sibling continuity, not a universal memory reduction for every uncompressed transcript.Tested on
Environment (optional)
macOS Darwin 25.4.0 arm64; Node.js v26.0.0; npm 11.12.1; final commit
a8bde6832c; bundled CLI SHA-256f5818498a2716ffd9d70839ebc7a9e3be7bff3252b541c2c20d1eb4f06a625dd.Risk & Scope
historyPageSizekeep full visible replay. Explicit recent loads can now fail with the documented transcript/replay limit errors rather than allocating an unbounded response. The internal cold transcript index rejects snapshots above 256 MiB pending explicit maintainer sign-off.Linked Issues
References #8678
Implements the design approved in #8743. Prerequisites #8691, #8833, #8882, #8933, #8939, and #8990 are merged.
中文说明
本 PR 做了什么
本 PR 实现 #8743 设计的 daemon 会话选择性恢复契约。冷
loadSession和resumeSession现在只构建一次 transcript 索引,并仅读取重建模型/运行态与所请求 replay projection 所需的记录,不再先物化完整 JSONL 会话再应用 replay 分页。projection 保留 compressed 与 legacy 模型历史、recording 祖先关系、中断 turn、FileHistory、artifact、Goal 与 checkpoint evidence、attribution、telemetry、累计 usage、source metadata 和后台通知的 active-chain 语义。使用 writer lease 的恢复会在所有权建立后获取权威 projection,live-session refresh 则在既有 recorder barrier 下复用更窄的缓存 projection。运行态 hydration、响应构建、Session 发布、回滚以及延迟的 Goal/FileHistory/attribution 激活继续受既有事务边界保护。
显式携带
historyPageSize的 response-mode load 现在会在读取 payload 前选择有界 recent replay,返回分页 metadata,并在发布 Session 前拒绝超过 32 MiB 或 10,000 条 update 的转换后 replay envelope。省略historyPageSize的 load 继续保留完整可见 replay 兼容性。只需要恢复运行态的 channel 和 scheduled-task 调用方改用无历史 replay 的 resume 路径。隐藏的 inherited Goal 状态仍会为运行时正确性恢复,但会被隔离于客户端展示之外,直到创建新的可见替代 Goal。冷索引设置 256 MiB source-byte 上限,并在记录边界协作式让出调度。本 PR 不增加持久 resume checkpoint、worker-thread 解析或 streaming JSON 解析,因此 legacy transcript 仍需执行一次完整索引扫描,单条大型 JSON 记录也仍是不可分割的解析区间。
为什么需要
Issue #8678 表明,即使客户端只请求最近历史,大型持久会话仍可能超过恢复截止时间。旧冷路径会在应用
historyPageSize前完整物化并重建 JSONL transcript,而 writer-lease 激活还可能再次执行全量读取。这使恢复时延和峰值分配随整个 transcript 增长,并可能延迟共享 ACP child 的 sibling session。超时安全、事务式 WebUI 切换、attachment fencing 和精确 request-shape 等前置工作已经合入。本 PR 补齐性能层:一个选择性 projection 成为持久层到 Config 与 Session setup 的权威交接,同时保留既有失败隔离和公开兼容性。
Reviewer 测试计划
验证方式
load和较小的显式historyPageSize恢复一个持久会话。确认响应只包含 turn 对齐的最近页面;存在更早记录时报告historyHasMore和historyAnchorRecordId;恢复后的 Session 仍可 prompt。historyPageSize恢复同一会话。确认继续保留完整可见 replay 兼容性,且不会与有界请求 shape 合并。hideInheritedHistory下,让一个较新的 inherited Goal 位于较旧的可见 Goal 之后。确认运行态恢复仍正确,而客户端只看到可见 Goal;隐藏 Goal 的 active recovery broadcast 必须持续被隔离,直到不同的 Goal 替代它。证据(Before & After)
Before:replay 分页只在完整持久 JSONL 已经被物化和重建后应用,因此 recent-page 请求仍需承担完整冷加载成本,并可能超过 #8678 描述的恢复截止时间。
After:最终 bundled CLI 使用
historyPageSize: 2恢复了两份 30,000 records、86.18–86.21 MB 的 fixture。空通道运行在 1,350 ms 内返回 200 且historyHasMore=true;共享通道运行在 651 ms 内返回 200,其已注册 sibling 在恢复期间和恢复后都返回 200。两个 daemon 都公布 60,000 ms 恢复预算并正常退出。这些 fixture 不包含 compression record,因此该证据只证明有界 UI replay 与 sibling 连续性,不代表所有 uncompressed transcript 都能获得通用内存下降。测试平台
环境(可选)
macOS Darwin 25.4.0 arm64;Node.js v26.0.0;npm 11.12.1;最终 commit
a8bde6832c;bundled CLI SHA-256f5818498a2716ffd9d70839ebc7a9e3be7bff3252b541c2c20d1eb4f06a625dd。风险与范围
historyPageSize的客户端继续获得完整可见 replay。显式 recent load 现在可能返回已记录的 transcript/replay limit error,而不是分配无上限响应。内部冷 transcript 索引会拒绝超过 256 MiB 的 snapshot,等待 maintainer 明确确认。关联 Issue
References #8678
实现 #8743 批准的设计。前置 #8691、#8833、#8882、#8933、#8939 和 #8990 均已合入。