Skip to content

Take the unknown-capacity compaction assumption from the host - #11

Merged
qduc merged 1 commit into
mainfrom
claude/host-supplied-context-assumption
Nov 11, 2026
Merged

qduc merged 1 commit into
mainfrom
claude/host-supplied-context-assumption

Conversation

@qduc

@qduc qduc commented Nov 11, 2026

Copy link
Copy Markdown
Owner

What leaked

When a model has no catalogue capacity, @qduc/agent-core scheduled automatic compaction at DEFAULT_ASSUMED_CONTEXT_WINDOW × compactThreshold, where DEFAULT_ASSUMED_CONTEXT_WINDOW (128_000) was a constant owned and exported by the core. Which window to assume for an unknown model is the consumer's policy, not the harness's. (#7 raised that constant to a consumer's figure; #10 reverted it. This PR removes the core-side number instead of changing it.)

What the host now supplies

The host already sets a required Model.contextWindow on every model: catalogue capacity when known, otherwise its own fallback. The compaction planner now receives that value as a separate field, assumedContextWindow, distinct from the existing contextWindow field that carries real capacity only.

  • packages/core/src/engine/recovery-compaction.ts: DirectCompactionInput gains required assumedContextWindow: number; the unknown-capacity softTrigger uses it where the constant was. Nothing else reads it. When it would be used (automatic compaction, unknown capacity), a non-positive or non-integer value throws a RangeError, like the existing contextWindow validation, so a bad host value cannot silently compact on every boundary.
  • packages/core/src/engine/turn-model-compaction.ts: the only call site of compactDirectHistory passes assumedContextWindow: model.contextWindow (the resolved model). The real-capacity argument is unchanged (deps.compaction.contextWindow ?? model.catalogContextWindow).
  • packages/core/src/model/index.ts: constant and its export removed; Model.contextWindow documented as a host-supplied window that only schedules maintenance when capacity is unknown.
  • packages/core/README.md (public-interface doc): new section on capacity versus the host's assumption, plus the migration note.

Unchanged: known catalogue capacities, explicit maxRequestInputTokens ceilings, raw compactThresholdTokens, manual compaction (ignores the assumption), and admission (enforceDirectInputLimit still only sees real capacity or the explicit ceiling, so the assumption is never a hard limit).

Breaking change

DEFAULT_ASSUMED_CONTEXT_WINDOW is no longer exported. The package is a prerelease and its one consumer is Term2, so this is a deliberate breaking removal. Consumers that imported it must define their own fallback and set it as Model.contextWindow for uncatalogued models. package.json version is unchanged; packages/wire is untouched.

Verification

Ran on Node 24.22.1 / pnpm 11.7.0 at the head commit: pnpm install --frozen-lockfile, pnpm build, pnpm typecheck, pnpm test (8/8), node scripts/agent-package-smoke.mjs, and pnpm smoke:checkout after committing. All passed.

A temporary probe (not committed) called compactDirectHistory from the built dist of this branch and of origin/main, ratio 0.8, local mode. "Needed" means the planner went past the trigger into an attempt (the probe history has nothing to summarise, so it ends blocked:no_complete_cold_turn); "not needed" is not_needed.

Case This branch origin/main
unknown capacity, host window 500,000, measured 399,999 / 400,000 not needed / needed needed / needed (core 128k governed)
unknown capacity, host window 128,000, measured 102,399 / 102,400 not needed / needed same
unknown, host 128k, compactThresholdTokens 50,000: 49,999 / 50,000 not needed / needed same
unknown, host 128k, maxRequestInputTokens 60,000: 44,999 / 45,000 not needed / needed same
catalogue 200,000 (host window 500k or 128k): 159,999 / 160,000 not needed / needed same

End to end through startTurn, a model with no catalogue capacity and host window 128,000 received a request of roughly a million estimated tokens: the turn completed and the model saw the large request on both builds (admitted, not rejected). Control: with maxRequestInputTokens 200,000 the same turn failed before dispatch on both builds.

Not verified

  • Term2 against this change. It still imports the removed export and needs a follow-up after this is published.
  • The probe exercises the planner and a fake-model turn, not a real provider.
  • Native-compaction mode was not probed separately; the trigger code path is shared.

Generated by Claude Code

The automatic-compaction trigger for a model without catalogue capacity
used a core-owned constant, DEFAULT_ASSUMED_CONTEXT_WINDOW (128,000). That
number is consumer policy. The planner now receives the resolved model's
host-supplied Model.contextWindow as a separate assumedContextWindow field
and uses it only to schedule automatic compaction; it is still never a
capacity or admission limit. Real capacity keeps flowing through
contextWindow as before.

Breaking: DEFAULT_ASSUMED_CONTEXT_WINDOW is no longer exported. Hosts
define their own fallback and supply it as Model.contextWindow.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqbJBBoCXgqyyFZaYn5Co4
@qduc
qduc merged commit f204fa7 into main Nov 11, 2026
1 check passed
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.

2 participants