Skip to content

bug(omp): chained continuation refused after a compaction-marker drain — synthetic m[0]/m[1] timestamps are part of the chain prefix (0.49.22) #791

Closed

Description

@Zireael

Short description

On OMP, a chained continuation is refused right after a compaction-marker drain with
chained continuation: prepared history does not extend the previous managed request, although the only difference
between the two prepared histories is the timestamp on the plugin's own injected m[0]/m[1] pair. Those
timestamps are derived from the current head (head.timestamp - 2 / - 1), the drain removes that head, and
piPreparedHistory() includes timestamps in the positional prefix comparison.

Reproduced with the shipped functions and the two real session entries: prefix 0 of 4 as-is, 4 of 4 with the
synthetic timestamps normalized. The turn is lost and resending reproduces it.

What happened

Plugin 0.49.22, OMP 18.12.6, openai-codex-responses / gpt-6.1-sol, Windows x64.

07:32:06.287 Pi compaction-marker drain: persisted compactionId=9a82e9d0 firstKept=c630be31 …
07:32:06.289 Pi compaction-marker clear: reason=applied endMessageId=synth-user-75a6478d ordinal=3135
07:32:06.519 injected m[0]/m[1] into Pi messages (97534 + 2407 bytes, materialized=false)
07:32:36.544 tail hygiene prefix invalidated: part_index=0 message=timestamp:1792913034834:user
             field=key frozen_parts=338 measured_parts=343 action=re-measured generation=8
07:32:36.662 [magic-context][pi] CONTEXT REFUSED "Magic Context could not safely prepare this turn; send your
             message again. stage=provider dispatch elapsed=315ms recovery=no managed result; refused
             (provider request does not match this pass's managed dispatch (chained continuation:
             prepared history does not extend the previous managed request))"

A second occurrence at 2026-10-26 07:14:52.076Z has the same shape (firstKept=f98a2411, part_index=0 message=timestamp:1792912401145:user).

Note the tail-hygiene line: the plugin's own prefix cache is also invalidated by the same move, which is why every
post-drain pass re-measures from scratch.

Root cause

  • prependM0M1Messages() (packages/pi-plugin/src/inject-compartments-pi.ts:2649) timestamps the injected pair from
    the current first visible message:

    const baseTimestamp = timestampHint ?? piMessages[0]?.timestamp ?? Date.now();
    … { role: "user", content: m0Content, timestamp: baseTimestamp - 2 },
      { role: "user", content: [{ type: "text", text: m1 }], timestamp: baseTimestamp - 1 }

    Both call sites derive it from the live head (…:2726 passes messages[0]?.timestamp).

  • piPreparedHistory() (pi-dispatch-chain.ts) snapshots every prepared message with lkgContentFields(), which
    flattens all enumerable fields, timestamp included (packages/plugin/src/hooks/magic-context/lkg-slot.ts:260).

  • The drain removes the head those timestamps were derived from, so the next pass re-injects the pair with new
    timestamps. Content is byte-identical otherwise; only the two synthetic numbers move.
    exactReusablePrefix(prepared.snapshots, anchor.prepared.snapshots) returns 0 and piExpandChainedRequest()
    refuses at pi-dispatch-chain.ts:215.

Evidence

reports/dispatch-failures/m0-timestamp-probe.mjs runs the shipped prependM0M1Messages, piPreparedHistory,
lkgContentFields and exactReusablePrefix against the real entries named by the log
(pre-drain 09e940de, post-drain f98a2411):

Measurement Value
pre-drain head timestamp 1792912401147
post-drain head timestamp 1792912401580
injected m[0] timestamp before → after 1792912401145 → 1792912401578 (+433 ms)
exactReusablePrefix as-is 0 of 4 (refuses)
exactReusablePrefix, synthetic timestamps normalized 4 of 4 (admits)

The probe also asserts that the rest of the two histories is identical and that the injected m[0] text did not
change.

Proposed fix

  1. Give the injected pair a timestamp that is a function of the session, not of the current head: persist the
    base next to the m0/m1 cache row and pass it through the existing timestampHint parameter. This also stops the
    tail-hygiene re-measure that the log shows on every post-drain pass.
  2. Belt and braces: the two injected rows carry no entry id. Exclude their timestamp from chain identity (they are
    plugin-owned text, never provider-visible content), keeping every other field compared.
  3. Regression test: anchor a pass → drain trims the head → next pass on a chain-capable model must be admitted. This
    is a chain-path test, not a structural one: the prepared content never changed, so a refusal is a false positive
    by construction.

Environment

  • @cortexkit/pi-magic-context 0.49.22 (present in master 0.49.23: prependM0M1Messages and
    piPreparedHistory are unchanged)
  • OMP 18.12.6, Bun, Windows x64
  • openai-codex-responses, gpt-6.1-sol, WebSocket chain enabled (previous_response_id frames observed)
  • Session 01a1584d…, 761 messages, compaction markers drained repeatedly
  • Related: Design proposal: tier the dispatch verdict so unmodeled shapes degrade the check, not the turn #761 (verdict tiering — this is one concrete shape with a proven fix inside the current design)

Activity

  1. magic-alfonso commented on Oct 26, 2026

    @magic-alfonso

    Thanks, and for the probe that isolates the timestamp move.

    Confirmed. The injected m[0]/m[1] pair takes its timestamps from the current first message (prependM0M1Messages), the drain removes that message, and the chain prefix comparison includes the timestamps. So two histories with identical content compare as different, and the continuation is refused.

    We're working on a fix that keeps the injected pair's timestamps stable across a drain. I'll post here when it ships.

  2. magic-alfonso commented on Oct 27, 2026

    @magic-alfonso

    Fixed in 0.49.24. Thanks @Zireael for the diagnosis.

    After a summary is folded into the history, the injected history block gets new timestamps. The chained-request check now ignores the timestamp only on that injected pair, matched by the injector's exact shape, so chained continuations are no longer refused there. A changed timestamp on a real user message is still refused, and no bytes sent to the model changed.

    Update the Pi package and restart Oh My Pi 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