Skip to content

Historian never compresses a single-prompt session: stale-snapshot refresh drops liftLivePromptFloor #795

Closed

Description

@yelrom-liively

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 /compact helped. 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)

  1. The trigger lifts the floor. compartment-trigger.ts consumes the live-prompt floor mark and recomputes the tail with liftLivePromptFloor: true, so the trigger's snapshot does have an eligible head.
  2. The stale-snapshot refresh drops the lift. In compartment-runner-incremental.ts (~L528-552), when that snapshot fails validation as stale_snapshot, the runner rebuilds it with resolveOpenCodeProtectedTailBoundary({ mode: "incremental-runner", … }), without liftLivePromptFloor.
    • Below the force band, resolveProtectedTailBoundary in protected-tail-boundary.ts (~L730-744) then clamps protectedTailStart to the last meaningful user ordinal.
    • In a one-prompt session that ordinal is 1, so hasRunnableCompartmentWindow(refreshed) is false and the run no-ops.
  3. The snapshot can't carry the lift. ProtectedTailBoundarySnapshot doesn'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

  1. magic-alfonso commented on Oct 26, 2026

    @magic-alfonso

    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, the stale_snapshot branch). ProtectedTailBoundarySnapshot doesn'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.

  2. magic-alfonso commented on Oct 27, 2026

    @magic-alfonso

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions