Skip to content

fix(runtime): write compaction notes when the fold is decided, not at settlement - #4852

Open
me2seeks wants to merge 2 commits into
apache:mainfrom
me2seeks:fix/4850-compaction-note-timing
Open

fix(runtime): write compaction notes when the fold is decided, not at settlement#4852
me2seeks wants to merge 2 commits into
apache:mainfrom
me2seeks:fix/4850-compaction-note-timing

Conversation

@me2seeks

@me2seeks me2seeks commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #4850.

The context_compacted / context_compaction_failed_open system notes were appended together with token_usage at send settlement. A stop or a stream error skips settlement entirely, so a send whose replay failed open left no trace — the transcript showed a prior "Context compacted." while every request silently carried the full history. This is the exact shape that hid #4842 for a whole session (fail-open at 18:18:45, the turn ran tens of minutes without settling, ctx pinned at 100%).

Fix

The pre-turn replay's fold decision is final when buildPriorMessages returns. Both notes are now written at decision time, once per send, guarded by the existing flags; the settlement write stays as the deduped fallback for mid-turn / request-hook folds that are only known there.

The context_compaction_failed_open note also carries data.failOpenReason (e.g. coverage_miss, source_hash_mismatch) so the transcript says why the fold was refused, instead of requiring a dig through token_usage.contextBudget diagnostics.

No TUI/Desktop change needed: both renderers already label both note kinds wherever the message appears (pi-transcript.ts, @maka/ui materialize.ts); only the write timing moves.

Scope: Maka-internal event visibility, distinct from #4623's provider-behavior degraded modes; noted on the issue.

Tests

  • Stop-proof: a send stopped mid-stream (before any settlement) still persists the fail-open note with its reason, and no token_usage is written. Fails without the fix (the note never arrives before settlement; 10s poll timeout).
  • Dedup: a normally settling send writes exactly one fail-open note.

@maka/runtime full dist suite: 3218 tests, 0 failures (2 runs); typecheck + biome clean.

… settlement

The context_compacted / context_compaction_failed_open notes were appended
together with token_usage at send settlement. A stop or a stream error skips
settlement entirely, so a send whose replay failed open left no trace: the
transcript showed a prior "Context compacted." while every request silently
carried the full history — the exact shape that hid apache#4842 for a whole
session.

The pre-turn replay's fold decision is final when buildPriorMessages returns,
so both notes are now written at decision time, once per send, with the
settlement write kept as the deduped fallback for mid-turn and request-hook
folds that are only known there. The failed_open note also carries the
failOpenReason (e.g. coverage_miss, source_hash_mismatch) so the transcript
says why the fold was refused instead of requiring a dig through token_usage
diagnostics.

No TUI/Desktop change: both renderers already label both note kinds wherever
the message appears; only the write timing moves.

Regression: a stopped-mid-stream send still persists the fail-open note with
its reason and no token_usage; a settling send writes exactly one note. The
first fails without this change (the note never arrives before settlement),
the second passes with and without it.

Fixes apache#4850.
@github-actions github-actions Bot added the effort/M Under 500 readable lines label Sep 5, 2026

@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 current head 8263aa58547ceefdc31919258620307dbffefcdb (OPEN). Technical GO — no P0–P2, two P3s below. A well-targeted bug fix.

What it fixes

Two context-compaction system notes (context_compacted and context_compaction_failed_open) used to be written at the settlement phase — after the token_usage message was stored. But settlement can be skipped entirely: user stops mid-way, or a streaming request errors out. Result: compaction really happened, yet users never saw the note in the transcript — and could not explain why later answers "forgot" earlier content.

The fix moves the decision to the moment it is determined — after the pre-turn replay parses the fold, before the first request goes out. Two files changed (implementation plus tests).

P3 — failOpenReason is picked by array order, not by the note that triggered it

The new code takes the reason via a find over decisions. If multiple entries ever qualify, the first in array order wins rather than the one tied to the triggering note. Worth pinning to the source.

P3 — a new data payload on an existing note kind, outside the title's scope, with no consumer contract

The pre-existing context_compaction_failed_open note carried no data; the new shared function adds some. Nothing consumes it yet. Either document the contract or drop the payload.

What I could not judge

Live compaction-timing behavior under real interrupted streams was not reproduced locally.


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.

简体中文

本条结论全部来自 @Opus-Qronos-AstroHan 的审查。我自己没有读这份 diff;我核的是当前 head 有没有漂移。当前 head 是 8263aa5,未关闭。修的是结算阶段可能被跳过导致备注丢失,技术上无阻断问题,另有两条小的观察。

Review on apache#4852: the reason is now taken from the last failedOpen compaction
decision rather than the first in array order, and the new data payload's
contract is documented on SystemNoteMessage (it is the only durable record
of the reason when a stop skips token_usage).
@me2seeks

me2seeks commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Thanks — both P3s addressed in 7d7ec60eae:

  • Reason selection: the note now takes failOpenReason from the last failedOpen history-compact decision (the most recent refusing stage after a diagnostic merge), not the first in array order. (Implemented with filter().at(-1) — the repo targets ES2022, so no findLast.)
  • data contract: documented on SystemNoteMessage.data in core — and kept, because on the stop path the token_usage diagnostic is never written, so this payload is the only durable record of why the fold was refused.

Runtime suite re-run after the change: 3218 tests, 0 failures; core 824 green.

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.

Compaction notes (failed_open / compacted) are only written at send settlement — invisible when the turn is stopped

2 participants