Use maxTokens as the provider generation cap and remove the confusing stored-summary max config.
Strip reasoning blocks before storing compaction summaries, reject non-shrinking summaries, and retry bounded re-compaction when the surface remains over threshold.
Add config validation for numeric and type-shaped knobs plus unit and real-API e2e coverage for reasoning-capable summarization.
Honor cancellation and disposal around async pre-step setup before the loop can open a step or call the model.
Route compaction summarization through agent/request so router agents can select the model, and remove the stale model argument from agent/pre-step.
Document serial events and the approximate convergence bound, regenerate the Cordis catalog, and add regression coverage for router compaction, HMR cleanup, and assembly/pre-step interruption.
Manual review round, two non-blocking findings:
- CBR-005: _extractText's JSDoc claimed it "walks events in log order",
but it walks the seqs in surface order (the inline comment already said
so) — the exact distinction CBR-001 paid for, since after a replace a
high-seq checkpoint heads the surface before lower-seq retained nodes.
Corrected the JSDoc to match.
- CBR-006: the "HMR safety" suite only asserted registration; the actual
dispose-and-confirm-cleanup test lived under "llm inject", so a reader
searching by name could miss it. Added a disposal test to the HMR-safety
suite (mount via the real plugin fiber with LlmService present so inject
resolves, dispose, assert ctx.get('compact') is undefined) and reframed
the llm-inject test's trailing teardown to point at it.
Codex round 1 CBR-002: `resolveConfig` rejected only
`summarizationMaxTokens + retainTokens > threshold` (allowing equality),
but `compactIfNeeded` declines only when the estimate is `< threshold`.
At exact equality the post-compaction history sits at the threshold and
re-triggers on the very next check.
Make the bound strict (`>=` rejects), so post-compaction history is
guaranteed strictly below the threshold. Updated the boundary test (the
sum-equals-threshold case is now rejected, not accepted) and added an
"accepts just below the threshold" case; nudged one unrelated config that
incidentally sat at the equality boundary.
Codex round 1 CBR-001: a head-anchored compaction checkpoint was
mis-classified by the log-position step-alignment scan, so a second
auto-compaction over a checkpoint-headed surface silently failed.
Root cause: `isStepAlignedStart/End` scanned the LOG by seq, but a
`replace` op lands a checkpoint at a high log seq whose SURFACE position
is the head — its log neighbours (the open step's assistant/message) are
not its surface neighbours, so the forward scan wrongly reported mid-step.
Fix, per the agreed direction:
- Replace the two log-position predicates with one surface-anchored
helper `isToolPairingBalanced(nodes, events, beforeSeq)` in
`dsh-session` (renamed step-boundary.ts → tool-pairing.ts). A cut is
balanced when no unanswered tool-call precedes it on the surface; a
region is collapsible iff both edges are balanced cuts. The open-tail
and free-node cases fall out of the same counter. It also throws on a
corrupt surface (a tool/result with no matching call).
- Move compaction off the in-step seam to a new "pre-step" seam fired
after turn/start and before step/start, so a compaction's log-only
compact/* records and its replacement node land cleanly OUTSIDE any
step (the honest structure crash-safety relies on). Renamed the event
agent/pre-request → agent/pre-step and switched its dispatch from
parallel → serial (listeners mutate the surface as a side effect;
serial isolates them so concurrent appends can't interleave). Extended
the catalog generator to accept @mode serial.
Regression coverage: a real-loop test driving an auto-compaction asserts
the landed checkpoint is a balanced cut on both sides; unit tests pin the
checkpoint case, the mid-step injection case, multi-call steps, and the
corrupt-surface guard. Proven red on the old log-position logic.
Reform the compaction blueprint so a runaway turn survives and the design
stops drifting across review rounds:
- Drop in-flight-turn protection ("layer 2"). Retention is a uniform tail→head
whole-unit walk; the only structural guard is step-alignment. A single turn
that alone exceeds the window now compacts its own early closed steps instead
of being retained verbatim (the failure mode that motivated this).
- Move auto-compaction off the agent/request waterfall onto a new awaited
agent/pre-request loop seam, fired before history derivation. Compaction
mutates the surface; the loop derives once from the result — no double-derive,
and a listener structurally cannot act on not-yet-derived messages.
- Tighten compactIfNeeded to required (session, system, model, signal).
- Enforce a single-pass convergence invariant in resolveConfig: reject configs
where summarizationMaxTokens + retainTokens exceeds the threshold, so a
compaction can never immediately re-trigger.
- Document the crash vs recoverable failure taxonomy; core session repair stays
compaction-agnostic (a log-only orphaned compact/start is inert).
- Wire dsh-compact-basic into examples/coding-agent and add a with-key
compaction e2e (compaction's first real-world exercise + runaway net).
- Rewrite the RFC to encode the blueprint and move it to implemented/.
The runaway-turn snapshot is a named deferred follow-up: dsh-llm-replay cannot
yet serve the interleaved summarization model call.
Collapses the per-round review churn of the prior compact-basic branch into a
single clean baseline on top of compact-interface, so the upcoming retention
refactor lands as fresh, well-scoped commits rather than stacking on a history
of fixes that are being superseded.