Repository navigation
Historian never compresses a single-prompt session: stale-snapshot refresh drops liftLivePromptFloor #795
Description
Short description
In a session with a single user prompt, the historian fires from the execute threshold and then no-ops every time. It never compresses until the 85% force band, because the stale-snapshot refresh drops liftLivePromptFloor.
What happened?
Setup: OpenCode 2.1.4 TUI with a local llama.cpp model (262K context, contextLimit=245760). The historian runs on a separate no-thinking alias, with execute_threshold_percentage at the default 65%. The session is a long autonomous run: one user prompt, then about 130 agent steps (37K → 201K tokens).
Observed: below the threshold, the trigger refuses (no eligible head (live-prompt floor at 1)), which is expected. From 65.6% it alternates on every other step: the floor mark is set, then consumed, the trigger fires, and the historian no-ops:
compartment trigger: not firing at 64.9% — no eligible head (live-prompt floor at 1); offset=1 protectedStart=1 eligibleEnd=1 rawTokens=0 chunkTokens=0
compartment trigger: live-prompt floor mark consumed at 1
compartment trigger: proactive fire at 65.6% (floor=63% projected post-drop=none target=48.8%)
compartment trigger: firing (reason=projected_headroom)
transform: compartmentInProgress flag set, starting agent
historian no-op: stale protected-tail snapshot (raw range fingerprint changed)
compartment trigger: live-prompt floor mark set at 1
compartment trigger: not firing at 65.7% — no eligible head (live-prompt floor at 1); offset=1 protectedStart=1 eligibleEnd=1 rawTokens=0 chunkTokens=0
compartment trigger: live-prompt floor mark consumed at 1
compartment trigger: proactive fire at 65.9% (...)
historian no-op: stale protected-tail snapshot (raw range fingerprint changed)
- No compaction: there were 17 no-ops between 65.6% and 81.8%. No historian request ever reached the model, and no compartment was written.
- No other reclaim either: heuristics logged
transform: skipping heuristics (already ran for turn …)on every execute pass, because the turn never changes. So nothing reclaimed context between 65% and 85%. - Only a manual
/compacthelped. At 81.8% it rode a hard fold and cleared reasoning (201K → 151K), but the compaction summary was an empty<session-history></session-history>.
Root cause (master a5c0299)
- The trigger lifts the floor.
compartment-trigger.tsconsumes the live-prompt floor mark and recomputes the tail withliftLivePromptFloor: true, so the trigger's snapshot does have an eligible head. - The stale-snapshot refresh drops the lift. In
compartment-runner-incremental.ts(~L528-552), when that snapshot fails validation asstale_snapshot, the runner rebuilds it withresolveOpenCodeProtectedTailBoundary({ mode: "incremental-runner", … }), withoutliftLivePromptFloor.- Below the force band,
resolveProtectedTailBoundaryinprotected-tail-boundary.ts(~L730-744) then clampsprotectedTailStartto the last meaningful user ordinal. - In a one-prompt session that ordinal is 1, so
hasRunnableCompartmentWindow(refreshed)is false and the run no-ops.
- Below the force band,
- The snapshot can't carry the lift.
ProtectedTailBoundarySnapshotdoesn't record whether the floor was lifted, so the runner has no way to lift it again.
I didn't pin down why the snapshot is stale on every attempt, possibly the in-memory-primed tail at trigger time against the DB read during validation. The refresh is the path that's meant to recover from that.
packages/pi-plugin/src/pi-historian-runner.ts has the same refresh shape and probably needs the same change.
Fix
--- a/packages/plugin/src/hooks/magic-context/protected-tail-boundary.ts
+++ b/packages/plugin/src/hooks/magic-context/protected-tail-boundary.ts
@@ export interface ProtectedTailBoundarySnapshot {
emergencyTailScale?: 0.5 | 0.25;
+ /** The trigger lifted the live-prompt floor (execute-threshold reclaim); a run-time refresh must lift it too. */
+ liftLivePromptFloor?: boolean;
@@ resolveProtectedTailBoundary: returned snapshot (~L811)
emergencyTailScale: ctx.emergencyTailScale,
+ liftLivePromptFloor: ctx.liftLivePromptFloor === true,
--- a/packages/plugin/src/hooks/magic-context/compartment-runner-incremental.ts
+++ b/packages/plugin/src/hooks/magic-context/compartment-runner-incremental.ts
@@ stale_snapshot refresh (~L541)
usageSource: boundarySnapshot.usageSource,
emergencyTailScale: boundarySnapshot.emergencyTailScale,
+ liftLivePromptFloor: boundarySnapshot.liftLivePromptFloor === true,
});Verified with this patch applied to the 0.49.23 dist, in the same session after a restart at 64.4%:
compartment trigger: proactive fire at 64.4% (floor=63% projected post-drop=none target=48.8%)
historian: refreshed stale protected-tail snapshot at run time (was: raw range fingerprint changed) — eligible head 1-10
historian publish completed: compartments=1 range=1-10
compartment agent: queued 27 drops for messages 1-10
historian: refreshed stale protected-tail snapshot at run time (was: raw range fingerprint changed) — eligible head 11-46
The historian now runs incrementally inside the single prompt instead of no-opping.
A test that would catch this: a one-user-message session above the execute threshold, where the trigger's snapshot is made stale (for example, by appending a message before the runner validates). The runner should refresh to a runnable window and write a compartment, instead of logging historian no-op: stale protected-tail snapshot.
Plugin version
0.49.23 (the bug is also present on master a5c0299)
OpenCode version
2.1.4
Platform
linux x64
Client
OpenCode TUI (CLI)
Activity
magic-alfonso commentedon Oct 26, 2026
Thanks, this is a clear diagnosis and the cause is confirmed. The trigger lifts the live-prompt floor for one evaluation, but when the runner refreshes a stale snapshot at run time, it rebuilds the boundary without the lift (
compartment-runner-incremental.ts, thestale_snapshotbranch).ProtectedTailBoundarySnapshotdoesn't record the lift, so the runner can't carry it over. In a one-prompt session the refreshed head is always empty, and every fire ends as a no-op until the 85% force band. The floor mark came in with 0.49.20, so this is a regression from that release.Your patch is the right shape. The Pi runner's refresh needs the same treatment, along with a test that makes the trigger's snapshot stale in a one-prompt session and expects a compartment. We'll also look at why the snapshot goes stale on every attempt. Until the fix ships, these sessions still compact at the 85% force band.
magic-alfonso commentedon Oct 27, 2026
Fixed in 0.49.24. Thanks @yelrom-liively for the diagnosis and the suggested fix.
When the historian's saved boundary goes stale, the refresh now keeps the one-time permission to summarize past the newest prompt. A session that runs from a single prompt is summarized again once it passes the execute threshold, instead of waiting for the 85% force band. This applies to OpenCode and Pi.
Update and restart your host to pick it up.