Skip to content

fix(runtime): re-fold when a manual-compaction checkpoint is stale - #5931

Merged
Colafornia merged 1 commit into
apache:mainfrom
Colafornia:fix/manual-compaction-stale-checkpoint
Oct 2, 2026
Merged

Colafornia merged 1 commit into
apache:mainfrom
Colafornia:fix/manual-compaction-stale-checkpoint

Conversation

@Colafornia

Copy link
Copy Markdown
Member
English

Summary

When a user asks for manual compaction, the runtime could answer already_compacted and do nothing, even though the stored checkpoint was stale. The send path already rejects the same checkpoint with effective_history_changed, so the stale checkpoint stayed in place.

The cause is a missing check in the reuse fast path. compactHistory only matched the raw event prefix (matchHistoryCompactCheckpointPrefix). A projection transition committed after the fold — for example, an archived tool result — changes the effective view without touching the raw ledger, so the raw match still passed.

Now the fast path folds the covered events through the current projection transitions and checks coverage.effectiveSourceDigest via checkpointEffectiveCoverageMatches, the same gate prepareContextBudgetPolicy applies before a send. When the digest no longer matches, the call falls through to planHistoryCompaction, whose existing roll-forward gate discards the stale checkpoint and re-summarizes the current effective view.

Behavior change: after the covered history drifts, manual compaction returns compacted and writes a new checkpoint. With unchanged effective history it stays a no-op with no model call. Checkpoint schema, provider fit, and trigger policy are unchanged.

Fixes #5929

Verification

  • New regression test manual compactHistory re-folds when a transition drifted the covered effective history (#5929) in ai-sdk-backend.test.ts fails without the fix (returns unchanged instead of compacted) and passes with it. It also asserts that the summarizer reads the folded effective view, and that a repeat compaction with a matching digest stays a no-op with no summarizer call.
  • node --test dist/__tests__/ai-sdk-backend.test.js: 254/254 pass.
  • Compaction-related suites (history-compaction, history-compact-checkpoint, history-compact-summarizer, history-compact-mid-turn-checkpoint, history-compact-ledger, model-projection-transition-ledger, conversation-copy, memory-extraction, overflow-reactive-recovery, mid-turn-capacity-backend): 303/303 pass.
  • tsc -p tsconfig.json for @maka/core, @maka/storage, @maka/runtime: clean. biome check on the two touched files: clean.
  • Not run: repo-wide npm test, lint/format:check beyond the touched files, knip, desktop e2e, and live-provider manual compaction (the issue notes there is no live-session reproduction).

AI use

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: Devin (Cognition) wrote the fix and the regression test; the commit carries a Generated-by: Devin trailer.

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No
中文

Summary

用户手动触发压缩时,runtime 可能回答 already_compacted 然后什么都不做,尽管已存的 checkpoint 已经过时。发送路径对同一个 checkpoint 会以 effective_history_changed 拒绝,所以过时的 checkpoint 一直留着。

根因是复用快路径漏了一项检查。compactHistory 只匹配原始事件前缀(matchHistoryCompactCheckpointPrefix)。压缩之后提交的投影 transition——比如归档一个工具结果——会改变有效视图,但不改原始账本,所以原始匹配照样通过。

现在快路径先用当前的投影 transition 折算被覆盖的事件,再通过 checkpointEffectiveCoverageMatches 校验 coverage.effectiveSourceDigest,和 prepareContextBudgetPolicy 在发送前做的检查一样。digest 不匹配时,调用落入 planHistoryCompaction,由它现有的滚动续接检查丢弃过时的 checkpoint,并基于当前有效视图重新摘要。

行为变化:覆盖范围内的有效历史漂移后,手动压缩返回 compacted 并写入新 checkpoint。有效历史没变时仍是无操作,不调用模型。checkpoint 结构、供应商判定和触发策略不变。

Fixes #5929

Verification

  • ai-sdk-backend.test.ts 新增回归测试 manual compactHistory re-folds when a transition drifted the covered effective history (#5929):不修复时失败(返回 unchanged),修复后通过。还断言摘要器读到的是折算后的有效视图,以及 digest 匹配时重复压缩不调用摘要器。
  • node --test dist/__tests__/ai-sdk-backend.test.js:254/254 通过。
  • 压缩相关套件(history-compaction、history-compact-checkpoint、history-compact-summarizer、history-compact-mid-turn-checkpoint、history-compact-ledger、model-projection-transition-ledger、conversation-copy、memory-extraction、overflow-reactive-recovery、mid-turn-capacity-backend):303/303 通过。
  • tsc -p tsconfig.json(@maka/core、@maka/storage、@maka/runtime):干净。biome check(两个改动文件):干净。
  • 未运行:仓库级 npm test、改动文件以外的 lint/format:check、knip、desktop e2e、真实供应商下的手动压缩(issue 注明没有真实会话复现)。

AI use

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: Devin (Cognition) wrote the fix and the regression test; the commit carries a Generated-by: Devin trailer.

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No

…euse

The compactHistory fast path returned already_compacted on a raw-prefix
match alone, while the pre-send gate rejects the same checkpoint with
effective_history_changed. Fold the covered span through the current
projection transitions and require the pinned effectiveSourceDigest to
still match; on drift, fall through to the planner's roll-forward gate,
which discards the stale checkpoint and re-summarizes the current
effective view (apache#5929).

Generated-by: Devin
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@github-actions github-actions Bot added the effort/M Under 500 readable lines label Oct 2, 2026
@Colafornia
Colafornia marked this pull request as ready for review October 2, 2026 10:41

@jackwener jackwener left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No findings.

At exact head 752cff908ad3503778bb698a18b5dbe6a25d77e3, the manual-compaction reuse path now folds the checkpoint's covered events through the current projection-transition ledger and validates coverage.effectiveSourceDigest before returning already_compacted. When the effective view has drifted, it falls through to the existing planner, which discards the stale checkpoint and summarizes the current effective view. When the digest still matches, the path remains a no-op and makes no summarizer call.

I verified the regression in both directions: the new test passes with the gate, while removing the gate reproduces the old unchanged result instead of compacted. The exact head passed the Runtime build, the complete ai-sdk-backend suite (254/254), and the affected compaction/projection suites (303/303), plus Biome, ASF-header, and whitespace checks. The fresh synthetic merge into current main 1a66e4d5e22067afd986d43fc634159e307a8e31 was clean (tree ca796f7186e4d572d4407cf7560d4443db8eb060); the two PR files were byte-identical to the tested head, and the merge result passed the same 254/254 and 303/303 suites. The exact-head hosted test check is successful.

The PR's Devin disclosure matches the substantive commit, which carries Generated-by: Devin. I did not run a live-provider manual-compaction session; the regression uses the real Runtime backend compaction and projection seams with deterministic injected ledger state.


Automated review notice: This comment was posted by an automated review agent operated by jackwener. It is not an independent human review and does not replace one.

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 752cff908ad3503778bb698a18b5dbe6a25d77e3 (2 files, +130/−2; the production change itself is +8/−1).

No P0–P3 findings.

What the change does. The manual-compaction reuse fast path no longer decides on raw prefix identity alone. Before returning already_compacted it now folds the checkpoint's covered events through the current projection transitions and checks the pinned effective digest:

const effectiveCovered = await this.foldEffectiveModelHistory(match.coveredRuntimeEvents);
if (this.checkpointEffectiveCoverageMatches(previousCheckpoint, effectiveCovered)) {

On a mismatch the reuse branch is skipped and control reaches the planner, whose roll-forward currency check discards the stale checkpoint and re-summarizes the current effective view.

The part I verified directly, because it is the substance of the claim: the gate is genuinely the same one the pre-send path applies, not a lookalike. checkpointEffectiveCoverageMatches is a single private method in this file (defined once, at ai-sdk-compaction.ts:576) and the new call site at :326 joins the existing one at :728. So the two paths cannot drift apart in what they consider "effective history" without someone editing the one implementation — which is the property that makes this fix right rather than merely plausible. The wrapping structure also matches the body's stated behaviour: the no-op path is untouched when the digest still matches (no summarizer call), and drift falls through rather than being answered as already_compacted.

The root cause the description gives is consistent with the code: a projection transition committed after the fold (an archived tool result, for instance) rewrites the effective view of the covered span without touching the raw ledger, so a raw-prefix match could still succeed while the effective digest no longer did.

Gate on this head: test is green (label likewise); mergeable is true and the state is blocked.

What I could not judge, and what rests on the existing review

  • I did not run the suites. The both-direction evidence — the new test asserting compacted with the gate, and reproducing the old unchanged result without it — is reported by the existing current-head review rather than reproduced by me; I verified the gate-sharing structure and the regression test's presence, not its outcome.
  • No live-provider manual-compaction session, so the end-to-end behaviour after drift is not exercised here.

I did not approve, request changes, or merge.


Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.

@jackwener jackwener left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved at exact head 752cff908ad3503778bb698a18b5dbe6a25d77e3. I found no unresolved P0–P2 issues.

The manual-compaction fast path now checks both raw-prefix identity and the current effective-source digest before reusing a checkpoint. If projection state has changed, the existing planner discards the stale checkpoint and summarizes the current effective view. The regression passes with this gate and reproduces the old unchanged result when the gate is removed.

The PR is open, non-draft, mergeable, and its exact-head test check is successful. The exact head passed the Runtime build, the complete ai-sdk-backend suite (254/254), and the affected compaction/projection suites (303/303). It also merges cleanly with current main 887924e14aa3fe78b746fd88a43548d7335b9573 as tree 00b27ba5ba9e244798698369790916624c9baf8c; the advance since the tested merge touches only UI files, and the complete Runtime subtree is byte-identical to the tested merge result.


Review notice: This review was prepared by an automated review agent operated by jackwener and is published at the direction of colafornia, who has read these findings and is the human accountable for them.

@Colafornia
Colafornia merged commit ab5996b into apache:main Oct 2, 2026
3 checks passed
@Colafornia
Colafornia deleted the fix/manual-compaction-stale-checkpoint branch October 2, 2026 13:33
Astro-Han pushed a commit that referenced this pull request Oct 11, 2026
…sult pruning (#6058)

Manual /compact (AiSdkCompaction.compactHistory) summarized the covered
span without running tool-result pruning first, so an oversized but
unarchived function response appeared in the summarizer's effective view
at full length and the checkpoint pinned coverage.effectiveSourceDigest
over that view. On the next Turn, prepareContextBudgetPolicy runs
pruneToolResults before it validates the checkpoint; the fresh archive
transition replaces the covered body with its placeholder, the pinned
digest no longer matches, and the just-written summary was silently
discarded as effective_history_changed while the raw history replayed.

Fix: compactHistory now runs the same pruneToolResults pass over its
planning pool before planning. The archives the next Turn would commit
anyway are committed first, so projectEffectiveCoverage folds them into
both the summarizer's view and the pinned digest - the same effective
view the pre-send validator folds. The planning pool stays the raw
events, so the checkpoint's coverage identity, the #5929 reuse gate, and
the #5931 fast path are untouched; no checkpoint schema changes.

Fixes #6048

Generated-by: GLM-5.3-Flash (ZCode)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/M Under 500 readable lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(runtime): manual compaction reports "already compacted" when the checkpoint's effective history is stale

3 participants