Skip to content

fix: recover stale context limits after model switches - #341

Open
coleleavitt wants to merge 8 commits into
cortexkit:masterfrom
coleleavitt:fix/context-limit-first-sample
Open

fix: recover stale context limits after model switches#341
coleleavitt wants to merge 8 commits into
cortexkit:masterfrom
coleleavitt:fix/context-limit-first-sample

Conversation

@coleleavitt

@coleleavitt coleleavitt commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Summary

  • treat the first successful usage sample after a model switch as a prompt-only lower bound for ordinary pressure when catalog metadata is stale
  • keep that lower bound in live/persisted usage metadata, not detected_context_limit, so a successful sample cannot masquerade as an exact provider ceiling or force a HARD m[0] fold
  • gate ordinary usage/model transitions on error-free terminal host events while preserving tokenless response timing
  • accept legitimate multi-million-token models up to JavaScript exact-integer bounds
  • reject attached errors, finish:error, nonterminal numeric usage, fractional, negative, overflow, and unsafe-integer samples

Fixes #331

Verification

  • real model-switch path reproduces 258,901 / 128,000 and recovers to a 100% prompt lower-bound pressure sample
  • 10M-token and finish:length regressions pass without changing provider geometry
  • error/nonterminal samples leave ordinary pressure unchanged; tokenless updates still refresh response TTL
  • 58 focused event/hook tests pass
  • B9 m[0]/m[1] SOFT-delta E2E passes after proving the lower bound no longer folds into m[0]
  • lint, typecheck, build, and diff checks pass

Greptile Summary

The PR separates usage-observation age from response timing so model-switch recovery can preserve a prompt-only lower bound without turning it into an exact provider ceiling.

  • Adds durable last_usage_observed_at metadata and migration v80.
  • Restores fresh persisted usage for tokenless updates after process-local cache loss.
  • Restricts ordinary usage transitions to valid, successful terminal host events.
  • Validates safe-integer token samples and supports multi-million-token context limits.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
packages/plugin/src/hooks/magic-context/event-handler.ts Restores fresh persisted usage for tokenless events, validates terminal usage samples, and records lower-bound pressure after model switches.
packages/plugin/src/features/magic-context/storage-meta-persisted.ts Loads persisted usage against its original observation timestamp rather than allowing tokenless response timing to extend usage indefinitely.
packages/plugin/src/features/magic-context/migrations.ts Adds migration v80 to persist and backfill the usage-observation timestamp.
packages/plugin/src/hooks/magic-context/event-payloads.ts Centralizes successful terminal host-event classification for usage transitions.
packages/plugin/src/shared/models-dev-cache.ts Expands accepted context limits to safe JavaScript integers while retaining numeric validation.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  E[Assistant message update] --> V{Error-free event?}
  V -->|No| O[Handle overflow evidence only]
  V -->|Yes| T{Valid terminal usage?}
  T -->|Yes| U[Update live and persisted pressure]
  U --> A[Set usage observation time]
  T -->|No, tokenless| K{Known live or fresh persisted usage?}
  K -->|Yes| R[Refresh response timing]
  K -->|No| S[Leave usage and timing unchanged]
  A --> R
Loading

Reviews (9): Last reviewed commit: "fix(plugin): restore usage before tokenl..." | Re-trigger Greptile

Context used:

Comment thread packages/plugin/src/hooks/magic-context/event-handler.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 7 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/plugin/src/hooks/magic-context/event-payloads.ts">

<violation number="1" location="packages/plugin/src/hooks/magic-context/event-payloads.ts:60">
P2: the finish check blacklists only the exact string "error", so every other non-empty finish (e.g. a user-cancelled/interrupted generation the host surfaces as a non-error finish) counts as a successful terminal host event. This gate drives live-model-switch bookkeeping (hook-handlers.ts:291) and ordinary scheduler pressure (event-handler.ts:519), so a cancelled turn that still carries input tokens would be treated as a valid success. Consider whitelisting the terminal success reasons ("stop","length") instead, or rejecting the known non-success finishes, to match the documented "error-free terminal host events" intent.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/plugin/src/hooks/magic-context/hook-handlers.ts Outdated
Comment thread packages/plugin/src/hooks/magic-context/event-handler.ts Outdated
completedAt?: number;
}

export function isSuccessfulHostEvent(info: MessageUpdatedAssistantInfo): boolean {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: the finish check blacklists only the exact string "error", so every other non-empty finish (e.g. a user-cancelled/interrupted generation the host surfaces as a non-error finish) counts as a successful terminal host event. This gate drives live-model-switch bookkeeping (hook-handlers.ts:291) and ordinary scheduler pressure (event-handler.ts:519), so a cancelled turn that still carries input tokens would be treated as a valid success. Consider whitelisting the terminal success reasons ("stop","length") instead, or rejecting the known non-success finishes, to match the documented "error-free terminal host events" intent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/plugin/src/hooks/magic-context/event-payloads.ts, line 60:

<comment>the finish check blacklists only the exact string "error", so every other non-empty finish (e.g. a user-cancelled/interrupted generation the host surfaces as a non-error finish) counts as a successful terminal host event. This gate drives live-model-switch bookkeeping (hook-handlers.ts:291) and ordinary scheduler pressure (event-handler.ts:519), so a cancelled turn that still carries input tokens would be treated as a valid success. Consider whitelisting the terminal success reasons ("stop","length") instead, or rejecting the known non-success finishes, to match the documented "error-free terminal host events" intent.</comment>

<file context>
@@ -57,6 +57,15 @@ export interface MessageUpdatedInfo {
     completedAt?: number;
 }
 
+export function isSuccessfulHostEvent(info: MessageUpdatedAssistantInfo): boolean {
+    if (info.error !== undefined && info.error !== null) return false;
+    if (info.finish === "error") return false;
</file context>

Comment thread packages/plugin/src/hooks/magic-context/event-handler.ts
@coleleavitt

Copy link
Copy Markdown
Contributor Author

Retriggering the unchanged commit because the sole failure was the unrelated/flaky B9 cache-invariant E2E; plugin, Pi, Docker, and the context-limit tests passed.

@coleleavitt coleleavitt reopened this Aug 19, 2026
@coleleavitt

Copy link
Copy Markdown
Contributor Author

Merge-order note: this PR now uses schema migration v80 for durable context-usage observation timestamps. PR #340 owns migration v79 and must merge first; rebase #341 after #340 so the combined migration sequence is v79 → v80. I am validating that combined head separately before handoff.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 15 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/plugin/src/features/magic-context/migrations.ts">

<violation number="1" location="packages/plugin/src/features/magic-context/migrations.ts:2824">
P1: When this PR is applied before PR #340, v80 becomes the high-water mark and the later v79 migration is skipped permanently. Land v79 first, or change migration selection to support out-of-order pending versions.</violation>
</file>

<file name="packages/plugin/src/features/magic-context/storage-meta-persisted.ts">

<violation number="1" location="packages/plugin/src/features/magic-context/storage-meta-persisted.ts:311">
P2: After 60 minutes of tokenless responses (no successful usage sample), loadPersistedUsage returns null even when the session is live. This drops the persisted lower-bound pressure, the lastUsageContextLimit used by event-resolvers, and the lastObservedModelKey that transform.ts uses to detect a model change and clear stale per-model state. Confirm an hour-long tokenless stretch is an acceptable reason to discard the lower bound the PR is meant to keep in persisted metadata; if so, consider documenting it, or refresh last_usage_observed_at on tokenless responses so only truly stale bounds expire.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

{
// Temporary merge-order reservation: PR #340 owns v79, so this PR must
// remain v80 even while v79 is absent from this worktree.
version: 80,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When this PR is applied before PR #340, v80 becomes the high-water mark and the later v79 migration is skipped permanently. Land v79 first, or change migration selection to support out-of-order pending versions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/plugin/src/features/magic-context/migrations.ts, line 2824:

<comment>When this PR is applied before PR #340, v80 becomes the high-water mark and the later v79 migration is skipped permanently. Land v79 first, or change migration selection to support out-of-order pending versions.</comment>

<file context>
@@ -2818,6 +2818,21 @@ export const MIGRATIONS: Migration[] = [
+    {
+        // Temporary merge-order reservation: PR #340 owns v79, so this PR must
+        // remain v80 even while v79 is absent from this worktree.
+        version: 80,
+        description: "persist the original observation time for tokenless usage TTL",
+        up(db: Database): void {
</file context>

if (
!isPersistedUsageRow(result) ||
result.last_usage_observed_at <= 0 ||
Date.now() - result.last_usage_observed_at > CONTEXT_USAGE_TTL_MS ||

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: After 60 minutes of tokenless responses (no successful usage sample), loadPersistedUsage returns null even when the session is live. This drops the persisted lower-bound pressure, the lastUsageContextLimit used by event-resolvers, and the lastObservedModelKey that transform.ts uses to detect a model change and clear stale per-model state. Confirm an hour-long tokenless stretch is an acceptable reason to discard the lower bound the PR is meant to keep in persisted metadata; if so, consider documenting it, or refresh last_usage_observed_at on tokenless responses so only truly stale bounds expire.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/plugin/src/features/magic-context/storage-meta-persisted.ts, line 311:

<comment>After 60 minutes of tokenless responses (no successful usage sample), loadPersistedUsage returns null even when the session is live. This drops the persisted lower-bound pressure, the lastUsageContextLimit used by event-resolvers, and the lastObservedModelKey that transform.ts uses to detect a model change and clear stale per-model state. Confirm an hour-long tokenless stretch is an acceptable reason to discard the lower bound the PR is meant to keep in persisted metadata; if so, consider documenting it, or refresh last_usage_observed_at on tokenless responses so only truly stale bounds expire.</comment>

<file context>
@@ -297,12 +301,14 @@ function getDefaultHistorianFailureState(): PersistedHistorianFailureState {
     if (
         !isPersistedUsageRow(result) ||
+        result.last_usage_observed_at <= 0 ||
+        Date.now() - result.last_usage_observed_at > CONTEXT_USAGE_TTL_MS ||
         (result.last_context_percentage === 0 && result.last_input_tokens === 0)
     ) {
</file context>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 5 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/plugin/src/features/magic-context/migrations.ts">

<violation number="1" location="packages/plugin/src/features/magic-context/migrations.ts:2836">
P2: When an existing session received a tokenless response after its last usage sample, this migration records that later response time as the usage-observation time. The restored sample can then pass the TTL check and drive pressure decisions beyond its real freshness window; do not mark legacy usage fresh from a response-only timestamp unless the legacy event is known to contain usage, otherwise expire the legacy sample conservatively.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

);
db.exec(`
UPDATE session_meta
SET last_usage_observed_at = last_response_time

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When an existing session received a tokenless response after its last usage sample, this migration records that later response time as the usage-observation time. The restored sample can then pass the TTL check and drive pressure decisions beyond its real freshness window; do not mark legacy usage fresh from a response-only timestamp unless the legacy event is known to contain usage, otherwise expire the legacy sample conservatively.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/plugin/src/features/magic-context/migrations.ts, line 2836:

<comment>When an existing session received a tokenless response after its last usage sample, this migration records that later response time as the usage-observation time. The restored sample can then pass the TTL check and drive pressure decisions beyond its real freshness window; do not mark legacy usage fresh from a response-only timestamp unless the legacy event is known to contain usage, otherwise expire the legacy sample conservatively.</comment>

<file context>
@@ -2831,6 +2831,13 @@ export const MIGRATIONS: Migration[] = [
             );
+            db.exec(`
+                UPDATE session_meta
+                   SET last_usage_observed_at = last_response_time
+                 WHERE last_usage_observed_at = 0
+                   AND last_input_tokens > 0
</file context>

ualtinok added a commit that referenced this pull request Aug 19, 2026
…ion gap, MAX_SANE_LIMIT expansion, v80-without-v79 migration strand)

Co-Authored-By: Alfonso <alfonso@cortexkit.io>

@alfonso-magic-context alfonso-magic-context left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this — the diagnosis is right, and we independently traced #331 to the same mechanism before reading your PR: an accepted 258,901-token prompt against a stale 128k catalog window after a model switch. Your event-ordering repair is solid (terminal-evidence gating, the pressure-model split, and the provenance correction in ce09be2 are all correct calls), and the behavior-based regression test is the right kind.

Three things block merge as-is, all fixable:

  1. The accepted-prompt floor never reaches shared geometry. It lands in the usage snapshot and last_usage_context_limit, but resolveContextLimit, resolveTrustedContextLimit, and resolveContextWindowGeometry still serve the stale 128k — so the scheduler, protected-tail boundaries, emergency ladder, status surfaces, and rust-mode wire keep the wrong denominator after the fix. The floor needs to propagate through the common limit/geometry chokepoints, with provider-overflow proof keeping precedence, and it should be modeled as an explicit lower bound rather than overloading a field whose readers treat it as a ceiling.
  2. The MAX_SANE_LIMIT change (3M → MAX_SAFE_INTEGER) is unrelated to this bug and changes geometry for >3M-window sessions that never hit #331. Please split it into its own PR so it can be evaluated on its own cache-impact merits.
  3. The v80 migration depends on #340's v79 without containing it. Our migration runner selects only versions above the current applied max, so a database that receives #341 first records v80 and can never select a later-arriving v79. Please rebase on #340 (or land them as a stack), and drop the temporary gap allowance in the armed-replay test.

One more ask: a cross-surface regression starting from the exact reported shape (258,901 accepted over stale 128k) asserting the denominator is consistent across scheduler/status/rust paths, plus a floor-then-overflow test proving overflow still wins. Happy to re-review quickly — this is the fix #331 needs.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

magic context at 202% reported after 2-3 hours of agent coding

2 participants