Skip to content

Auto-compaction triggers at ~200k tokens instead of model's actual context window (1M) #15871

Closed as not planned

Description

@logos007

Summary

When using context1m: true with Claude Opus 4.6 or Sonnet 4.6, the Memory Flush hook computes its threshold from a hardcoded contextWindow: 200000 in models.generated.js instead of the actual 1M context window. This results in compaction at ~144k tokens (200k − 50k reserve − 6k soft threshold).

Environment

  • OpenClaw 2026.3.2 (9288d93)
  • Model: anthropic/claude-opus-4-6 with context1m: true
  • Compaction mode: safeguard
  • reserveTokensFloor: 50000, softThresholdTokens: 6000

Reproduction

  1. Configure anthropic/claude-opus-4-6 with params.context1m: true
  2. Use the session until context reaches ~144k tokens
  3. Memory Flush triggers compaction

Expected: No compaction below ~944k (1M − 50k reserve − 6k soft)
Actual: Compaction at ~144k (200k − 50k − 6k)

Evidence

6 consecutive compactions, all fromHook: True (Memory Flush):

Timestamp (UTC) tokensBefore
2026-02-28 03:41 157,265
2026-03-01 22:54 271,529
2026-03-03 05:28 264,061
2026-03-03 15:33 249,187
2026-03-03 16:09 163,063
2026-03-03 20:32 150,573

Root Cause

1. models.generated.js hardcodes contextWindow: 200000

File: /app/node_modules/.pnpm/@mariozechner+pi-ai@0.55.3_ws@8.19.0_zod@4.3.6/node_modules/@mariozechner/pi-ai/dist/models.generated.js

Both claude-opus-4-6 and claude-sonnet-4-6 have contextWindow: 200000. This value is cached in MODEL_CACHE at startup via applyDiscoveredContextWindows().

2. resolveMemoryFlushContextWindowTokens ignores context1m

// pi-embedded-DgYXShcG.js, line 47871
function resolveMemoryFlushContextWindowTokens(params) {
    return lookupContextTokens(params.modelId) ?? params.agentCfgContextTokens ?? DEFAULT_CONTEXT_TOKENS;
}

This calls lookupContextTokens() which returns the cached 200k. It does not call resolveContextTokensForModel() (line 22146), which correctly checks context1m:

function resolveContextTokensForModel(params) {
    // ...
    if (resolveConfiguredModelParams(params.cfg, ref.provider, ref.model)?.context1m === true
        && isAnthropic1MModel$1(ref.provider, ref.model))
        return ANTHROPIC_CONTEXT_1M_TOKENS; // 1,048,576
    // ...
}

3. applyDiscoveredContextWindows takes minimum for duplicates

// pi-embedded-DgYXShcG.js, line 22021
if (existing === void 0 || contextWindow < existing) params.cache.set(model.id, contextWindow);

If a model appears with both 200k and 1M entries, 200k wins.

4. lookupContextTokens has a race condition

// pi-embedded-DgYXShcG.js, line 22107
function lookupContextTokens(modelId) {
    ensureContextWindowCacheLoaded();  // async — NOT awaited
    return MODEL_CACHE.get(modelId);   // may return undefined if cache not loaded
}

On startup, the cache may be empty → undefined → falls back to DEFAULT_CONTEXT_TOKENS (200k).

5. DEFAULT_CONTEXT_TOKENS = 2e5

// model-selection-Zb7eBzSY.js, line 30 (6 copies across dist/)
const DEFAULT_CONTEXT_TOKENS = 2e5;

The last-resort fallback is also 200k.

6. Heartbeat model bypasses context-1m header

// pi-embedded-DgYXShcG.js, line 88969
if (extraParams?.context1m === true)
    if (isAnthropic1MModel(modelId)) betas.add(ANTHROPIC_CONTEXT_1M_BETA);

isAnthropic1MModel() matches only claude-opus-4 and claude-sonnet-4 prefixes. If the heartbeat uses a different model (e.g. Haiku), the context-1m header is not sent — but the full session history (built at 1M) is. Anthropic enforces 200k → overflow compaction.

7. Compaction uses triggering model, not session model

// pi-embedded-DgYXShcG.js, line 94460
model: modelId  // uses the model that triggered the overflow

The compaction API call uses the triggering model (e.g. Haiku). Since Haiku is not a 1M model, the compaction request also lacks the context-1m header — so Anthropic enforces 200k on the compaction call itself. Additionally, a weaker model writes the summary instead of the session's primary model.

Suggested Fixes

  1. resolveMemoryFlushContextWindowTokens should use resolveContextTokensForModel — this already correctly handles context1m and returns 1M
  2. Update models.generated.js — claude-opus-4-6 and claude-sonnet-4-6 should have contextWindow: 1048576
  3. applyDiscoveredContextWindows should take maximum, not minimum, for duplicate entries
  4. lookupContextTokens should await ensureContextWindowCacheLoaded() or ensure synchronous population
  5. DEFAULT_CONTEXT_TOKENS should be 1M or derived from the configured model
  6. Heartbeat should use the session's context-1m header if the session was built with a 1M model
  7. Compaction should use the session's primary model, not the triggering model

Workaround

1. Patch models.generated.js

find /path/to/openclaw -name "models.generated.js" \
  -exec sed -i 's/contextWindow: 200000/contextWindow: 1048576/g' {} +

2. Patch DEFAULT_CONTEXT_TOKENS (defense-in-depth for race condition)

find /path/to/openclaw/dist -name "*.js" \
  -exec sed -i 's/const DEFAULT_CONTEXT_TOKENS = 2e5/const DEFAULT_CONTEXT_TOKENS = 1048576/g' {} +

Both patches must be reapplied after every update. Restart OpenClaw after patching.

Files Affected

File Line Issue
models.generated.js various Hardcoded contextWindow: 200000
pi-embedded-DgYXShcG.js 47871 Memory flush ignores context1m
pi-embedded-DgYXShcG.js 22021 Min-value for duplicate entries
pi-embedded-DgYXShcG.js 22107 Race condition (async not awaited)
model-selection-Zb7eBzSY.js 30 DEFAULT_CONTEXT_TOKENS = 2e5
pi-embedded-DgYXShcG.js 88969 Heartbeat bypasses context-1m header
pi-embedded-DgYXShcG.js 94460 Compaction uses triggering model

Activity

  1. added
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on Mar 3, 2026
  2. github-actions commented on Mar 3, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    If your report covers additional distinct bugs not addressed in those issues (Issues 2–6 in your analysis), it may be worth linking to them and focusing this issue on the remaining gaps.

  3. DusKing1 commented on Mar 7, 2026

    @DusKing1

    The premature compaction at ~200k is caused by two things: (1) missing context-1m-2025-08-07 beta header, and (2) default model limits in OpenCode being set to 200k.

    For Anthropic API users, I made a plugin that fixes both. Add to your opencode.json:

    {
      "plugin": ["opencode-anthropic-context-1m"],
      "provider": {
        "anthropic": {
          "models": {
            "claude-opus-4-6":          { "limit": { "context": 1000000, "output": 128000 } },
            "claude-sonnet-4-6":        { "limit": { "context": 1000000, "output": 64000 } },
            "claude-sonnet-4-5":        { "limit": { "context": 1000000, "output": 64000 } },
            "claude-sonnet-4-20250514": { "limit": { "context": 1000000, "output": 64000 } }
          }
        }
      }
    }

    The plugin injects the beta header via chat.headers hook (appends, doesn't overwrite existing SDK flags). The model overrides push compaction threshold from ~168K to ~968K.

    https://github.com/DusKing1/opencode-anthropic-context-1m

  4. jason2077 commented on Mar 15, 2026

    @jason2077

    I can add one more high-confidence data point from a real opencode v1.2.26 session.

    Evidence (DB)

    • Session: ses_3...
    • max_total = 382,889
    • First >=200k: 2026-03-15 13:07:07
    • First >=300k: 2026-03-15 15:40:32
    • Compaction markers in the same session:
      • part.type='compaction' = 0
      • message.mode='compaction' = 0

    Runtime sanity

    • OPENCODE_DISABLE_AUTOCOMPACT was unset
    • OPENCODE_DISABLE_PRUNE was unset
    • Overflow compaction is only disabled when compaction.auto === false (explicit false check)

    Overflow-threshold math used by core

    In session/compaction.ts, overflow uses:

    • usable = input ? input - reserved : context - maxOutputTokens(model)
    • maxOutputTokens = min(model.limit.output, 32000) (from provider/transform.ts)

    Local ~/.cache/opencode/models.json in this environment reports:

    • anthropic/claude-opus-4-6: context=1,000,000, output=128,000
    • anthropic/claude-sonnet-4-6: context=1,000,000, output=64,000

    So derived overflow threshold for both is:

    • usable = 1,000,000 - 32,000 = 968,000

    Observed implication

    Given those values, a session at 382,889 is still below the overflow threshold, which is consistent with observing zero compaction markers.

    Next-session checklist (I plan to run and report back)

    1. Reproduce on a fresh session with the same model and collect the same SQL evidence.
    2. Capture the exact runtime model-limit inputs used by overflow calculation on that path.
    3. Compare behavior in a minimal setup (without extra plugin layers) to isolate core behavior.
  5. imqqmi commented on Mar 15, 2026

    @imqqmi

    Would also be nice if we can just set this manually per model in the opencode.json file, ie set opus 4.6 to 200K instead of 1M because token usage shoots up beyond that.

  6. logos007 commented on Mar 21, 2026

    @logos007
    Author

    The issue is still existing in v2026.3.13.

  7. assigned and unassigned on Apr 26, 2026
  8. removed
    bugSomething isn't working
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on May 3, 2026
  9. rekram1-node commented on May 13, 2026

    @rekram1-node
    Contributor

    this issue seems to cite pi mono not opencode or some other tool

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

Metadata

Metadata

Assignees

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