Skip to content

Commit ae02485

Browse files
migheorgheclaude
andauthored
feat(agent): cap the agent's context window with agent.context_window (#219)
* feat(agent): cap the agent's context window with agent.context_window Add a harness-neutral `agent.context_window` field (tokens) so a benchmark operator can run the same tasks at different context caps, per agent config or per experiment variant. - claude-code: sent as CLAUDE_CODE_AUTO_COMPACT_WINDOW in the CLI env, which outranks --autocompact and every settings scope; range 100000-1000000 (the CLI silently drops out-of-range settings values, so the range is enforced at load). Setting claude_settings.autoCompactWindow too is a load error. - codex: sent as model_context_window in the thread config; Codex caps it to the model's catalog maximum and auto-compacts at 90% of it. - every other agent type rejects the field at load instead of running uncapped under a capped label. The resolved value is recorded in agent_config on every task row and in the config lineage. Rationale: .claude/notes/context-window.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RarvxSsr4K2KkBisTbXxQY * feat(agent): cap Pi's context window with agent.context_window Pi 0.87.1 can lower a model's window with providers.<provider>.modelOverrides.<model>.contextWindow in models.json, and then compacts once the context passes the window minus compaction.reserveTokens, the same effect as Codex's model_context_window. Pi reads models.json only from its agent dir, so a capped agent runs from a temp dir that links every entry of the host's agent dir (auth, settings, extensions) and holds the host's models.json plus the override, pointed at by PI_CODING_AGENT_DIR and removed in stop(). Links keep credentials in one place and the capped variant's setup identical to the uncapped one's. Pi silently ignores an override for an unknown model id, so start() runs `pi --list-models <model>` against the mirror and fails unless that exact provider/model row shows the capped window. The config needs model as provider/model and a window of at least 32768 (twice the default reserve). OpenCode, Antigravity and delegate still reject the field; the note records why. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RarvxSsr4K2KkBisTbXxQY * revert(agent): drop Pi's context_window mapping until it has a live test Reverts d077ad8, as the PR review asked: the claude-code and codex mapping has a live run behind it (compaction at about 160k on a 200k cap), the Pi mapping has none, and no task in the repo uses type: pi. Pi rejects context_window at load again, like antigravity and opencode. The Pi design stays in that commit for a later PR with a live test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RarvxSsr4K2KkBisTbXxQY * fix(agent): address the context_window review - The field description no longer lists harnesses and ranges: each config class declares its range, and docs/agents/HARNESS_PARITY.md is the one table of what each harness accepts and does. TASK_DEFINITION_GUIDE and AB_EXPERIMENTS link to it instead of restating it. - Codex's floor is 32768 instead of 1, so a dropped "000" or a YAML `yes` (which loads as 1) fails at load instead of compacting on every turn. - The table says what an unset window means on each harness: claude-code blanks a host CLAUDE_CODE_AUTO_COMPACT_WINDOW, codex leaves a host model_context_window in config.toml in effect. The design note records that decision instead of saying the host value is left alone, and says why Pi, OpenCode, Antigravity and delegate do not opt in yet. - Only an inline claude_settings dict is checked for a second autoCompactWindow; the docstring and the table now say a settings file's value is overridden the same way. - The run and experiment reports show a Context Window row when a run was capped, read from agent_config, since the value never reaches sdk_options. - EXTENDING.md says how a plugin agent opts in. - Tests for the accepted range ends, the Codex floor and the report row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RarvxSsr4K2KkBisTbXxQY --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent c9456e5 commit ae02485

15 files changed

Lines changed: 539 additions & 5 deletions

‎.claude/notes/README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ rule docstrings in `tests/lint/rules/`, then the guides under `docs/`.
1313
## Contents
1414

1515
- [agents.md](agents.md) — agent adapters, the turn lifecycle, token reconciliation, harness parity
16+
- [context-window.md](context-window.md) — the `agent.context_window` cap, per harness
1617
- [contracts.md](contracts.md) — criteria, datasets, aggregation, judging
1718
- [isolation.md](isolation.md) — the docker driver, the sandbox, detached grading
1819
- [lint-rules.md](lint-rules.md) — why each CE lint rule exists

‎.claude/notes/context-window.md‎

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# Capping the agent's context window
2+
3+
Analysis and design for `agent.context_window`: one harness-neutral field that caps the
4+
context window an agent works within, set per agent config or per experiment variant.
5+
6+
## Problem and evidence
7+
8+
A benchmark of Claude Code on Sonnet 5 (Bedrock, 1M-token window) let runs grow to
9+
250k-480k tokens of context. Above about 150k tokens each extended-thinking step got
10+
about 10x slower, so most runs hit the 2-hour task limit. The operator wants to measure
11+
pass rate and cost at several context caps, for example one variant at 200k and one at
12+
400k, on the same tasks.
13+
14+
Before this change no harness-neutral way existed:
15+
16+
- **claude-code**: `claude_settings` (`--settings`) could carry `autoCompactWindow`. That
17+
works for one harness only, and when `claude_settings` is a file path a variant cannot
18+
merge one key into it.
19+
- **codex**: `CodexAgentConfig` has no pass-through. `CodexAgent._build_thread_options`
20+
builds the thread `config` dict itself, so no value reached Codex.
21+
- **antigravity, opencode, pi, delegate**: no knob is wired.
22+
23+
## How each harness controls its window
24+
25+
### claude-code
26+
27+
Verified in the CLI the SDK spawns. `claude-agent-sdk` runs its **bundled** CLI before it
28+
looks for `claude` on `PATH` (`SubprocessCLITransport._find_cli`), so the CLI that runs is
29+
the one the SDK wheel bundles, not the one the agent image installs with npm:
30+
31+
| coder_eval | claude-agent-sdk (uv.lock) | bundled CLI | image npm CLI |
32+
|---|---|---|---|
33+
| 0.12.1 | 0.2.124 | 2.1.216 | 2.1.177 |
34+
| 0.12.12 | 0.2.159 | 2.1.281 | 2.1.281 |
35+
36+
`environment_info.claude_code_cli` records `claude -v` from `PATH`, so a 0.12.1 run
37+
records 2.1.177 while 2.1.216 ran. That is a separate defect and is not fixed here.
38+
39+
Three inputs set the auto-compact window. Both bundled CLIs (2.1.216 and 2.1.281) contain
40+
the same resolution function. The highest precedence comes first:
41+
42+
1. `CLAUDE_CODE_AUTO_COMPACT_WINDOW` environment variable: a plain token count. It
43+
outranks the flag and every settings scope.
44+
2. `--autocompact <auto|tokens>`: added in 2.1.221 (CLI reference). It is absent from
45+
2.1.216 as a parsed option.
46+
3. `autoCompactWindow` settings key. The settings schema is
47+
`int().min(100000).max(1000000).optional().catch(undefined)`, so an out-of-range
48+
value is **silently dropped**.
49+
50+
The effective window is `min(model context window, configured value)`. Auto-compaction
51+
starts when the context approaches that window.
52+
53+
### codex
54+
55+
Verified in the `openai-codex-cli-bin` 0.156.1 binary (the pin) and its source at tag
56+
`rust-v0.156.1`:
57+
58+
- `model_context_window` ("Size of the context window for the model, in tokens"):
59+
`with_config_overrides` sets the model's `context_window` to
60+
`min(value, max_context_window)`. Three things follow from it. The default
61+
auto-compact limit becomes 90% of it. The hard cap, which forces compaction, becomes
62+
`context_window * effective_context_window_percent / 100`. The remaining-window figure
63+
the model is shown also changes.
64+
- `model_auto_compact_token_limit` ("Token usage threshold triggering auto-compaction"):
65+
the trigger only, clamped to 90% of the resolved window. It does not change the window.
66+
- `model_auto_compact_token_limit_scope` (`total` by default, or `body_after_prefix`) and
67+
`model_post_turn_compact_threshold_percent` (turn-end compaction, off by default) tune
68+
when compaction starts. They are not caps.
69+
70+
The thread `config` dict `thread_start` takes is the same override surface that
71+
coder_eval already uses for `enabled_tools` and `model_providers`.
72+
73+
### antigravity, opencode, pi, delegate
74+
75+
None of them has a context-window knob wired in coder_eval. They are out of scope, and
76+
they reject the field.
77+
78+
## Options considered
79+
80+
**Per-harness pass-through** (a Codex `config` pass-through next to `claude_settings`).
81+
It is flexible, but a variant would have to name a different key per harness, and the
82+
recorded value would mean something different on each. It also opens a free-form
83+
pass-through on Codex with no denylist. Rejected.
84+
85+
**A field under `run_limits`.** Run limits are caps that the orchestrator or the agent
86+
enforces on a run (turns, time, tokens, USD), and the parity table lists them. A context
87+
window is not a spend cap. It is a harness setting that changes what the agent does, and
88+
it depends on the agent type, which `run_limits` does not know. The guide also says that
89+
`agent:` holds no run-time caps. Rejected.
90+
91+
**A field on the agent config.** One name, accepted on a type-less config so that
92+
experiment defaults and variants can set it, and validated against the concrete type
93+
when the config resolves. **Chosen.**
94+
95+
Naming: `context_window`, in tokens. It names the quantity the operator caps, matches
96+
Codex's `model_context_window`, and avoids "auto-compact", which describes a mechanism of
97+
one harness. `max_context_tokens` was considered, but it reads as a per-request input
98+
limit, which neither harness enforces.
99+
100+
## Recommended design
101+
102+
`BaseAgentConfig.context_window: int | None` (default `None`, `gt=0`).
103+
104+
- Each config class declares `_context_window_range: ClassVar[tuple[int, int | None] |
105+
None]`. `None` (the base default) means that the harness cannot apply the field.
106+
`ClaudeCodeAgentConfig` declares `(100_000, 1_000_000)` and `CodexAgentConfig` declares
107+
`(32_768, None)`.
108+
- `check_context_window_supported` (model validator) rejects an unsupported type or an
109+
out-of-range value as soon as the type is known. A plugin agent inherits `None`, so it
110+
also rejects the field until it opts in.
111+
- The field uses the default `replace` merge strategy. It is set at any of the five
112+
layers, and with `-D agent.context_window=N`.
113+
114+
Per-harness mapping:
115+
116+
- **claude-code**: `CLAUDE_CODE_AUTO_COMPACT_WINDOW=<N>` in the SDK `env`. The
117+
environment variable was chosen over the settings key and the flag for four reasons.
118+
It has the highest precedence, so host, project and `claude_settings` values cannot
119+
override the cap. It works whether `claude_settings` is a dict or a file path. Both
120+
bundled CLIs read it, which the flag does not. `extra_args` stays framework-owned.
121+
`claude_settings.autoCompactWindow` together with `context_window` is a load error,
122+
because the environment variable would silently override it.
123+
- **codex**: `model_context_window: <N>` in the thread `config`. No trigger key is set,
124+
so Codex compacts at its default 90% of the window.
125+
126+
## Validation
127+
128+
- `gt=0` on every config, so a type-less config rejects nonsense as well.
129+
- claude-code: 100000-1000000, the CLI's documented range. Outside that range the CLI
130+
drops a settings value silently, so coder_eval enforces the range itself.
131+
- codex: 32768 and up. Codex clamps the value to the model's catalog maximum, and the
132+
floor rejects a window too small to work in, such as a YAML `yes` coerced to 1.
133+
- every other type: rejected at load, naming the supported types. A variant that
134+
switches `type` to an unsupported harness fails when the experiment resolves.
135+
136+
## Recording
137+
138+
The resolved agent config is persisted as `agent_config` on every task row (`run.json`
139+
and `task.json`). `agent.context_window` therefore appears per task, and config lineage
140+
records which layer set it (for example, the variant). The experiment report groups by
141+
variant, so a cap per variant groups without new report code. On claude-code the
142+
`sdk_options` dump also shows the environment variable in `env`.
143+
144+
## Open questions
145+
146+
- **A silent clamp to the model's window.** Neither harness reports that the model's
147+
window lowered the cap. A cap above the model's window runs at the model's window.
148+
Recording the effective window would need a per-model catalog in coder_eval.
149+
- **Trigger points differ.** Codex compacts at 90% of the window. Claude Code compacts
150+
when the context approaches the window, minus an internal buffer. Equal caps therefore
151+
do not compact at exactly the same token count. A separate harness-neutral trigger
152+
field is possible, but it is not added until a benchmark needs it.
153+
- **A host `CLAUDE_CODE_AUTO_COMPACT_WINDOW` and uncapped runs.** Decided: the SDK starts
154+
the CLI with `os.environ` underneath `options.env`, so when the field is unset the
155+
claude-code agent blanks a host value. A run recorded as uncapped is never capped by
156+
the host. This changes every claude-code run on a host that sets the variable,
157+
including runs that never set `context_window`. Codex has no equivalent: an unset
158+
field leaves a host `model_context_window` in `$CODEX_HOME/config.toml` in effect.
159+
- **The recorded CLI version is wrong.** See the version table above:
160+
`environment_info.claude_code_cli` should record the bundled CLI version.
161+
- **Other harnesses.** Pi can lower a model's window with
162+
`providers.<provider>.modelOverrides.<model>.contextWindow` in `models.json`. A mapping
163+
was written and then reverted (d1757a60), because no live run backed it and no task
164+
uses `type: pi`; it comes back with a live test. OpenCode (`limit.context` and
165+
`limit.input` in provider config) needs a `limit.output` coder_eval does not know, and
166+
an unpinned CLI. Antigravity's `CompactionConfig(token_threshold=N)` only moves the
167+
trigger. Delegate keeps its history in the backend. Each needs its own verification
168+
before it opts in.
169+
- **How often a run compacted is not recorded.** A cap A/B cannot yet tell "cap never
170+
reached" from "compacted N times" from the task row alone.

‎docs/AB_EXPERIMENTS.md‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ to the orchestrator are involved.
2323
- [What a Variant Can Override](#what-a-variant-can-override)
2424
- [Recipe: A/B a Skill](#recipe-ab-a-skill)
2525
- [Recipe: A/B a Model](#recipe-ab-a-model)
26+
- [Recipe: A/B a Context Window](#recipe-ab-a-context-window)
2627
- [Recipe: A/B a Prompt](#recipe-ab-a-prompt)
2728
- [Recipe: Smoke vs. e2e Flavors (Early Stop)](#recipe-smoke-vs-e2e-flavors-early-stop)
2829
- [Replicates (Statistical Power)](#replicates-statistical-power)
@@ -222,6 +223,28 @@ variants:
222223
agent: { model: claude-opus-5 }
223224
```
224225

226+
## Recipe: A/B a Context Window
227+
228+
`agent.context_window` caps the context the agent works within, in tokens: the
229+
harness compacts the conversation before it outgrows the cap. Use it to measure
230+
pass rate and cost against the cap on a model with a large window. Each task row
231+
records the cap in `agent_config.context_window`.
232+
233+
```yaml
234+
experiment_id: context-window
235+
description: "The same model with a 200k and a 400k context cap"
236+
237+
variants:
238+
- variant_id: cap-200k
239+
agent: { context_window: 200000 }
240+
- variant_id: cap-400k
241+
agent: { context_window: 400000 }
242+
```
243+
244+
An agent type that cannot apply the cap fails the experiment at load.
245+
[Run-Limit Parity](agents/HARNESS_PARITY.md#the-context-window-cap-per-harness) lists which
246+
agent types accept it and what the cap does on each.
247+
225248
## Recipe: A/B a Prompt
226249

227250
Use `prompt_mutations` (transform the task prompt) or `initial_prompt` /

‎docs/EXTENDING.md‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,22 @@ class MyAgentConfig(BaseAgentConfig):
8080
The factory `create_agent(kind, config, …)` raises `TypeError` if the passed config
8181
isn't an instance of the registered `config_class`, so keep them paired.
8282

83+
To accept `agent.context_window`, declare the inclusive range of tokens your harness
84+
can cap its context window to, and apply `self.config.context_window` when the agent
85+
starts. A config class that declares nothing rejects the field at load, which is the
86+
right default: a capped variant must never run uncapped.
87+
88+
```python
89+
from typing import ClassVar
90+
91+
class MyAgentConfig(BaseAgentConfig):
92+
type: Literal["my-agent"] = "my-agent"
93+
_context_window_range: ClassVar[tuple[int, int | None] | None] = (32_768, None)
94+
```
95+
96+
Add your harness to the table in
97+
[Run-Limit Parity](agents/HARNESS_PARITY.md#the-context-window-cap-per-harness).
98+
8399
### The `Agent` ABC — implementation checklist
84100

85101
Implement these three abstract methods:

‎docs/TASK_DEFINITION_GUIDE.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ agent:
170170
- "Write"
171171
- "Bash"
172172
model: "claude-sonnet-5" # Optional: specific model
173+
context_window: 200000 # Optional: cap the context window, in tokens (claude-code, codex)
173174
sdk_options: # Optional: agent SDK pass-through (keys depend on `type`)
174175
effort: high # claude-code: any non-framework-managed ClaudeAgentOptions field
175176
```
@@ -188,6 +189,13 @@ validates its own keys at YAML load:
188189
Deep-merged across the 5-layer config chain. Override via CLI with the
189190
repeatable `-D agent.sdk_options.KEY=VALUE`.
190191

192+
**`context_window`** caps the context window the agent works within, in tokens: the
193+
harness compacts the conversation before it outgrows the cap, and never runs past the
194+
model's own window. Override via CLI with `-D agent.context_window=400000`. Which agent
195+
types accept it, in what range, what each does with it and what an unset value means:
196+
[Run-Limit Parity](agents/HARNESS_PARITY.md#the-context-window-cap-per-harness). An agent
197+
type that cannot apply it rejects it at load.
198+
191199
**Permission Modes:**
192200
- `default` — Default permission handling
193201
- `acceptEdits` — Auto-accept file edits (recommended for evaluations)

‎docs/agents/CLAUDE_CODE.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ agent:
104104
| `system_prompt_file` | `str \| null` | Path (relative to the task YAML) loaded into `system_prompt` at resolution. Works with either `system_prompt_mode`. |
105105
| `setting_sources` | `list["user"\|"project"\|"local"] \| null` | Which host setting sources the SDK reads. Default resolves to `["project"]`. See [Sandbox isolation](#sandbox-isolation). |
106106
| `claude_settings` | `str \| dict \| null` | Passed to the SDK `--settings`. A dict is JSON-serialized; a str is a settings file path. Use `permissions.deny` to block tools/paths. |
107+
| `context_window` | `int \| null` (100000-1000000) | Auto-compact window, in tokens, sent as `CLAUDE_CODE_AUTO_COMPACT_WINDOW`. It outranks `--autocompact` and every settings scope, and is capped to the model's window. Setting `claude_settings.autoCompactWindow` too is a load error. See [Harness parity](HARNESS_PARITY.md#the-context-window-cap-per-harness). |
107108
| `sdk_options` | `dict` (default `{}`) | Pass-through for `ClaudeAgentOptions` fields Coder Eval doesn't own (e.g. `effort`). Validated at load — an unknown or framework-owned key is a hard error. |
108109
| `ignore_patterns` | `list[str] \| null` | Gitignore-style overrides for the workspace copy used by judge sub-agents (supports `!` negation). |
109110

‎docs/agents/CODEX.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,13 @@ The agent maps `permission_mode` to the Codex SDK's `Sandbox`. The approval mode
192192
193193
`allowed_tools` / `disallowed_tools` are normalized (`Bash` → `shell`, `Write`/`Edit` → `apply_patch`, etc.) and passed as `enabled_tools` / `disabled_tools` in the thread `config`. **Note:** the Codex SDK does not currently enforce `disabled_tools`; do not rely on it as a security boundary (the agent logs a warning when it is set).
194194
195+
### Context Window
196+
197+
`agent.context_window` is passed as `model_context_window` in the thread `config`. Codex
198+
uses it as the model's context window, capped to the model's catalog maximum, and
199+
auto-compacts at 90% of it. See
200+
[Harness parity](HARNESS_PARITY.md#the-context-window-cap-per-harness).
201+
195202
### Skills Discovery
196203
197204
The agent sets up SKILL.md files (Agent Skills open standard) in `.agents/skills/` directory:

‎docs/agents/HARNESS_PARITY.md‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,38 @@ A timeout is a *failure* (partial turn captured, error status); the turn cap is
637637
*clean stop*. Conflating them is the mistake this page exists to prevent: a task
638638
whose cap fires should not look like a task whose harness hung.
639639

640+
## The context window cap per harness
641+
642+
`agent.context_window` is not a run limit, but the same promise applies: one value
643+
must mean the same cap on every harness that accepts it. It is the context window, in
644+
tokens, that the harness compacts the conversation within. A harness that cannot apply
645+
it rejects it when the config loads. It is never ignored, because a variant that runs
646+
uncapped under a "capped" label is a wrong measurement.
647+
648+
| | claude-code | codex | antigravity | opencode | pi | delegate |
649+
|---|---|---|---|---|---|---|
650+
| mechanism | `CLAUDE_CODE_AUTO_COMPACT_WINDOW` in the CLI environment | thread config `model_context_window` | rejected | rejected | rejected | rejected |
651+
| accepted range | 100000-1000000 | 32768 and up | | | | |
652+
| when compaction starts | when the context approaches the window | at 90% of the window (Codex's default auto-compact limit) | | | | |
653+
| above the model's own window | capped to the model's window by the CLI | capped to the model's catalog maximum by Codex | | | | |
654+
| unset | the model's full window: a host `CLAUDE_CODE_AUTO_COMPACT_WINDOW` is blanked | the host's Codex config: a `model_context_window` in `$CODEX_HOME/config.toml` still applies | | | | |
655+
656+
- **claude-code**: the environment variable outranks `--autocompact` and every settings
657+
scope, so a host or project `autoCompactWindow` cannot change the cap. Setting
658+
`claude_settings.autoCompactWindow` together with `context_window` fails at load when
659+
`claude_settings` is an inline dict; a settings file's `autoCompactWindow` is not read,
660+
and is overridden the same way. When `context_window` is unset, a host-set
661+
`CLAUDE_CODE_AUTO_COMPACT_WINDOW` is blanked, so a run recorded as uncapped is never
662+
capped by the host. This applies to every claude-code run, capped or not.
663+
- **codex**: the value replaces the model's context window, so the hard-cap
664+
compaction at the usable window (a per-model share of it, 95% for Codex's fallback
665+
model metadata) also moves. The floor of 32768 rejects a window too small to work in,
666+
such as a dropped `000` or a YAML `yes`, which would load as 1. When `context_window`
667+
is unset, coder_eval sets nothing, so a host `model_context_window` in
668+
`$CODEX_HOME/config.toml` still applies.
669+
- Neither harness reports when the model's own window has lowered the cap. A cap
670+
above the model's window runs at the model's window.
671+
640672
## `agent.plugins[].path` accepts different depths per harness
641673

642674
Not a run limit, but the same promise: one task file, three harnesses, same meaning.

‎src/coder_eval/agents/claude_code_agent.py‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,8 @@ def _turn_usage_slice(
253253

254254
_JSON_START_SEARCH_LIMIT = 200
255255

256+
AUTO_COMPACT_WINDOW_ENV = "CLAUDE_CODE_AUTO_COMPACT_WINDOW"
257+
256258

257259
class _ClaudeTurnState:
258260
"""Per-turn mutable scratch state for one ``ClaudeCodeAgent.communicate`` call.
@@ -1268,6 +1270,14 @@ def _build_claude_query(
12681270
cost_log_tags=cost_log_tags,
12691271
)
12701272
effective_model = self._resolve_effective_model(self.config.model, env, route_model)
1273+
if self.config.context_window is not None:
1274+
# The env var outranks --autocompact and every settings scope, and is
1275+
# read by every CLI the pinned SDKs bundle.
1276+
env[AUTO_COMPACT_WINDOW_ENV] = str(self.config.context_window)
1277+
elif os.environ.get(AUTO_COMPACT_WINDOW_ENV):
1278+
# The SDK spawns the CLI with {**os.environ, **options.env}, so a host-set
1279+
# window would cap a variant recorded as uncapped. Empty is falsy there.
1280+
env[AUTO_COMPACT_WINDOW_ENV] = ""
12711281

12721282
disallowed_tools = list(self.config.disallowed_tools or [])
12731283
# Do not allow ToolSearch. This is required to keep Bedrock backend in sync with the other backends.

‎src/coder_eval/agents/codex_agent.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1500,6 +1500,10 @@ def _build_thread_options(self) -> dict[str, Any]:
15001500
+ f"api_version={'set' if api_version else 'unset'})"
15011501
)
15021502

1503+
if self.config.context_window is not None:
1504+
# Codex then auto-compacts at 90% of this window.
1505+
tool_config["model_context_window"] = self.config.context_window
1506+
15031507
if tool_config:
15041508
options["config"] = tool_config
15051509

0 commit comments

Comments
 (0)