feat: tool truncation + auto-compaction - #147
Merged
Merged
Conversation
Keep ExecutionPolicy flat: a compaction toggle plus the existing summarize_at_tokens threshold, instead of a nested config object. Setting a threshold still implies compaction. The ACP session-meta policy and the session snapshot flatten with it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The OpenAI SDK cannot construct a bare dict from a response (construct_type needs a parameterized mapping), so threshold discovery raised ValueError past the APIError handler and killed the engine on its first turn against a real server. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The model can ignore tool_choice="none" on the checkpoint turn and reply with a tool call and no text (observed ~6% of compactions on deepseek-v4-flash) - the rebuilt branch then starts with no context. Resample the checkpoint up to three times until it yields a text summary. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The failed checkpoints were not disobedience: the model understood the request and chose to run one more state-gathering tool call before summarizing, which the loop never grants it. Say explicitly that the summary must come from the conversation as it stands. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One checkpoint call per compaction - the hardened prompt is the guard against tool-call replies. Also name estimated_tokens' input for what it counts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts: # src/rlm/config.py # src/rlm/engine.py
Compact when 16k tokens remain below the context window instead of at 90% of it - a fixed reserve keeps constant headroom on any window size (small windows keep at least half). Truncate a tool result over 10KB middle-out before it enters the conversation, with a warning naming the original token count and line count, so one giant output can never leap past the reserve and the model knows what was cut. Matches Codex's output policy; the threshold matches pi's reserve design. Drop the stale README rows for the unimplemented RLM_MAX_OUTPUT knobs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Each marker now names the API whose error wording it matches, and the unattributable generics are gone - "too many tokens" also matches Bedrock throttling, and bare "context length"/"context window" substrings matched more than they targeted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 413 markers could never fire: a 413 arrives as a plain APIStatusError, not BadRequestError. Catch APIStatusError at the compaction sites and gate overflow detection on a deterministic status (400 or 413) so marker-shaped text in a transient failure never triggers a compaction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
samsja
added a commit
that referenced
this pull request
Aug 28, 2026
## Summary Hard-caps any single tool result entering the conversation at **10KB**, middle-out (keep head+tail), with a warning header naming the original size and line count: ``` Warning: truncated output (original token count: 12500) Total output lines: 10001 <first 5KB> [... 40000 bytes truncated ...] <last 5KB> ``` Applies at the engine tool-result boundary — so it covers **all tools** (`bash`, `edit`, and `ipython` cell output) uniformly. ## Scope: context only — tools yes, skills no - The **session log keeps the full output** (audit trail unchanged). - **Skill return values inside the kernel stay uncapped**: `out = await bash(...)` holds the complete string for in-cell filtering; only what the cell *prints* (the tool result) is subject to the cap. Capture-then-filter workflows keep full fidelity — the cap only protects the context window. ## Compatibility The function is byte-compatible with the truncation in [verifiers#2454](PrimeIntellect-ai/verifiers#2454) (bash harness) and the copy bundled inside [#147](#147) (auto-compaction) — extracted standalone here so the cap can ship independently; #147 can relocate/dedupe it when it lands. Motivation from eval traces: unclipped tool results produced single-turn context blowups (observed up to ~690k tokens from one `cat` on a large file), which no compaction threshold can save you from after the fact. Tests: truncation unit test added; 139 passed (6 pre-existing `test_acp.py` env failures, same as clean main); ruff clean.
Final compaction design: proactive at a fixed reserve below a known context window, reactive on attributed 400/413 overflow errors. A rejected checkpoint no longer sheds tool results - it falls back to the last state that passed a threshold check, which by definition holds a full reserve of room; an empty or tool-calling reply is resampled, and after three failed attempts the run ends cleanly as a trainable sample instead of crashing. An overflow with no history beyond the task propagates. Tool truncation grows to 20KB, the checkpoint prompt returns to the Codex wording, and the threshold-learning regexes go away - compaction now requires a known window. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts: # src/rlm/config.py # src/rlm/engine.py # tests/test_acp.py
The raw cast_to parse breaks on one Python version or another: a bare dict cannot be constructed on 3.13, and a parameterized dict trips inspect.isclass on 3.10 (observed as ACP failures in containers whose best Python is 3.10). The SDK's models.list keeps provider extensions in each card's model_extra, on every version. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A reasoning-parsed model (observed: Laguna via the glm45 parser) can put the entire checkpoint reply in reasoning_content, leaving content empty - every attempt then fails and the run ends as compaction-failed despite a perfectly good summary. The checkpoint asked for a summary, so when content is empty accept the reasoning text as the summary. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An unusable checkpoint reply (empty or tool-calling) finishes its request without failing it, so the compaction still held the claim and the resample died on "compaction already has a summary request". Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
vLLM 0.26 names the field "reasoning" and the SDK only keeps it in model_extra, so the attribute lookup never saw it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A checkpoint reply that lives entirely in the reasoning channel is resampled like an empty one - reasoning never enters the summary. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review: last_good started at zero, so a first-turn checkpoint rejection retried over an empty base - a summary of nothing with the task gone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review: an overflow on the post-compaction work call propagated out of the loop and crashed the rollout. The rebuilt conversation is sized to fit by construction, so if it still overflows there are no moves left - convert it to the compaction-failed clean ending. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review: the post-tool snapshot was taken on a chars/4 estimate, which can undercount dense content severalfold - the "good" snapshot could itself be oversized, making the fallback identical to the overflowing request. A state now becomes the fallback only when the provider accepted that exact prompt with real usage below the threshold, which lands the fallback before the tool results, as designed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mikasenghaas
marked this pull request as ready for review
August 29, 2026 00:30
mikasenghaas
requested review from
hallerite and
samsja
and removed request for
samsja
August 29, 2026 00:34
The move into compaction.py had replaced the checkpoint prompt with the Codex template wording. Keep the actionable handoff prompt that main already ships (runnable repro commands, pending edit calls, numbered next steps) and keep the trailing no-tool-call instruction that stops the model from answering the checkpoint with a tool call. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three review findings: - prompt() rollback now restores _last_good with the conversation, so a failed turn can no longer leave a fallback slice past the restored history. - A follow-up prompt() floors _last_good at the turn's opening state, so a checkpoint fallback never drops the newest user instruction. - discover_threshold no longer caches a failed /models listing; a transient failure at startup retries on the next engine instead of disabling compaction for the process lifetime. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An overflow on a conversation that a compaction already reduced to [system, summary] re-raised the provider error, so the run failed instead of ending as a clean compaction_failed like the in-cycle retry path. Track that a compaction happened and convert that overflow to CompactionFailed; the first-turn floor keeps raising. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 7f6a408. Configure here.
The flag marks the live conversation as a compaction floor; a rolled- back turn's compaction is no longer part of that conversation, so a later overflow must propagate again instead of ending the run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
hallerite
added a commit
that referenced
this pull request
Sep 1, 2026
max_tool_output_chars becomes max_tool_output_bytes and reuses the existing head/tail truncation (PR #147's mechanism) as a budget override of the 10KB default, instead of a second chars-based pass — a result could previously be truncated twice with two stacked warning headers. Session logs keep the full output; only the conversation copy is capped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
samsja
previously approved these changes
Sep 1, 2026
mikasenghaas
added a commit
to PrimeIntellect-ai/verifiers
that referenced
this pull request
Sep 1, 2026
## Summary - add the same optional `CompactionConfig` to the RLM harness as the bash harness carries - cross ACP with a flat policy: a `compaction` toggle plus `summarize_at_tokens` - pin merged nano-rlm compaction commit `4ef3438` Builds on merged [#2454](#2454), which adds Bash compaction and the interception `/v1/models` relay. Companion [nano-rlm #147](PrimeIntellect-ai/nano-rlm#147) is merged. ## Breaking - `RLMHarnessConfig.summarize_at_tokens` moves to `RLMHarnessConfig.compaction.summarize_at_tokens` and no longer accepts a `(lo, hi)` range. - Leave `compaction` unset to disable proactive and reactive compaction. ## Verification - `uv run pytest -q tests/v1` — passed; live E2E tests skipped without `PRIME_API_KEY` - `uv run pytest -q tests/v1/test_configs.py` — 12 passed - `uv run ruff check verifiers/v1/harnesses/rlm/harness.py` - `uv run ruff format --check verifiers/v1/harnesses/rlm/harness.py` Terminal-Bench 2 e2e: 8 tasks, local vLLM `poolside/Laguna-XS-2.1` at 32k (glm45 reasoning + glm47 tool parsers), `compaction = {}` so the engine discovers the threshold itself (`32768 − 16384 = 16384`). Trace analysis of the pinned engine: - Threshold discovery and the proactive trigger work through ACP: compaction fired on the 3 episodes whose context crossed ~17k; episodes that stayed below (0.7k-6.7k peaks) never compacted; 20KB tool truncation visible where tool output was large. - The runs surfaced and the pin fixes three integration bugs, each verified against the failing trace: `/models` discovery crashing on Python 3.10 containers (raw `cast_to` parse; now `models.list()`), and two interactions with semantic-edge bookkeeping. A failed checkpoint attempt and a resampled unusable reply each left the compaction's summary-request claim held, which killed the retry with "compaction already has a summary request". - Laguna answers checkpoint prompts entirely in the reasoning channel, so under the summaries-are-content-only rule its compactions exercise the resample-then-end-cleanly path; summary carry-over across branches was demonstrated on content-channel models (Qwen3-0.6B, deepseek-v4-flash). - A final combined verification run on a content-channel model is pending before merge. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Breaking harness configuration and ACP runtime policy shape affect rollout behavior and compaction timing; changes are localized to the RLM harness but alter long-running agent context management. > > **Overview** > **RLM harness compaction** is restructured to match the bash harness: a nested `CompactionConfig` with optional `summarize_at_tokens`, exposed over ACP as `policy.compaction` (on/off) plus the threshold when set. > > **Breaking config change:** `RLMHarnessConfig.summarize_at_tokens` is removed in favor of `compaction`; the `(lo, hi)` per-task random range and `summarize_threshold()` are dropped. **`compaction` unset** means compaction is off; an **empty** `compaction` object enables automatic thresholding (e.g. context window minus 16k when advertised). > > The pinned **nano-rlm** ref updates to **`4ef3438`** for the merged compaction engine. `_runtime_metadata` no longer takes `TaskData` since thresholds are no longer task-index–seeded. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 5f39bb2. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- Macroscope's pull request summary starts here --> <!-- Macroscope will only edit the content between these invisible markers, and the markers themselves will not be visible in the GitHub rendered markdown. --> <!-- If you delete either of the start / end markers from your PR's description, Macroscope will append its summary at the bottom of the description. --> > [!NOTE] > ### Add `CompactionConfig` to `RLMHarness` and remove per-task threshold randomization > - Introduces `CompactionConfig` (derived from `BaseConfig`) with an optional `summarize_at_tokens: PositiveInt` field, replacing the flat `summarize_at_tokens` tuple-range field on `RLMHarnessConfig`. > - Removes `RLMHarness.summarize_threshold`, which previously computed per-task randomized thresholds seeded by task index. `_runtime_metadata` now reads thresholds directly from `self.config.compaction` and emits a boolean `compaction` flag in the session policy. > - Changes the default `version` git ref for `RLMHarnessConfig` to `4ef3438`. > - Behavioral Change: `summarize_at_tokens` no longer accepts `(lo, hi)` tuple ranges; callers must provide a single `PositiveInt` inside `CompactionConfig`. The `data` parameter was removed from `RLMHarness._runtime_metadata`. > > <!-- Macroscope's review summary starts here --> > > <sup><a href="https://app.macroscope.com">Macroscope</a> summarized 5f39bb2.</sup> > <!-- Macroscope's review summary ends here --> > <!-- Macroscope's pull request summary ends here -->
hallerite
added a commit
that referenced
this pull request
Sep 1, 2026
Rebased onto auto-compaction (#147) and consolidated. Three optional ExecutionPolicy caps, configured via the ACP runtime-v1 contract: - max_total_turns: tree-total work-loop call budget (compaction calls excluded), counted live by the supervisor; every engine stops gracefully before its next call once spent (stop_reason=max_total_turns). - max_total_tokens: tree-total NEW-token budget (completion + uncached prompt per call - the cached prefix re-billed each call is not new work); same graceful stop, and the supervisor refuses new sub-agent spawns. - max_tool_output_bytes: byte budget for one tool result entering the conversation, overriding the built-in 20KB default (same middle-out mechanism, one truncation pass). Capped stops report true turn counts and salvage a final answer (this prompt's last assistant text, else the compaction handoff summary, else a bracketed marker). Without a supervisor the engine enforces the budgets with its own counters (a no-broker session is its own whole tree), and a spent cap suppresses proactive compaction (the reactive overflow path stays available). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
hallerite
added a commit
that referenced
this pull request
Sep 1, 2026
Rebased onto auto-compaction (#147) and consolidated. Three optional ExecutionPolicy caps, configured via the ACP runtime-v1 contract: - max_total_turns: tree-total work-loop call budget (compaction calls excluded), counted live by the supervisor; every engine stops gracefully before its next call once spent (stop_reason=max_total_turns). - max_total_tokens: tree-total NEW-token budget (completion + uncached prompt per call - the cached prefix re-billed each call is not new work); same graceful stop, and the supervisor refuses new sub-agent spawns. - max_tool_output_bytes: byte budget for one tool result entering the conversation, overriding the built-in 20KB default (same middle-out mechanism, one truncation pass). Capped stops report true turn counts and salvage a final answer (this prompt's last assistant text, else the compaction handoff summary, else a bracketed marker). Without a supervisor the engine enforces the budgets with its own counters (a no-broker session is its own whole tree), and a spent cap suppresses proactive compaction (the reactive overflow path stays available). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
hallerite
added a commit
that referenced
this pull request
Sep 1, 2026
Rebased onto auto-compaction (#147) and consolidated. Three optional ExecutionPolicy caps, configured via the ACP runtime-v1 contract: - max_total_turns: tree-total work-loop call budget (compaction calls excluded), counted live by the supervisor; every engine stops gracefully before its next call once spent (stop_reason=max_total_turns). - max_total_tokens: tree-total NEW-token budget (completion + uncached prompt per call - the cached prefix re-billed each call is not new work); same graceful stop, and the supervisor refuses new sub-agent spawns. - max_tool_output_bytes: byte budget for one tool result entering the conversation, overriding the built-in 20KB default (same middle-out mechanism, one truncation pass). Capped stops report true turn counts and salvage a final answer (this prompt's last assistant text, else the compaction handoff summary, else a bracketed marker). Without a supervisor the engine enforces the budgets with its own counters (a no-broker session is its own whole tree), and a spent cap suppresses proactive compaction (the reactive overflow path stays available). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mikasenghaas
added a commit
to PrimeIntellect-ai/prime-rl
that referenced
this pull request
Sep 1, 2026
## Summary - advance the `deps/verifiers` submodule from `e2103d6` to `d4a2177` - include merged Bash and RLM context compaction from verifiers #2454 and #2459 - pick up nano-rlm compaction commit `4ef3438` through the default RLM harness pin - refresh `uv.lock` for verifiers' `aiohttp>=3.14.1` requirement - migrate three RLM examples to `compaction.summarize_at_tokens`; use the former range's `98_304` midpoint - include the optional ACP semantic-edge and shared harness utility changes already on verifiers `main` Companions: [verifiers #2454](PrimeIntellect-ai/verifiers#2454), [verifiers #2459](PrimeIntellect-ai/verifiers#2459), and [nano-rlm #147](PrimeIntellect-ai/nano-rlm#147). ## Breaking - RLM harness configs must move `summarize_at_tokens` to `compaction.summarize_at_tokens`. - Leave `compaction` unset to disable proactive and reactive compaction. ## Verification - `git diff --check` - `uv lock` - `uv lock --check` - `uv run pytest -q tests/unit/test_configs.py` — 133 passed - `git submodule status deps/verifiers` — `d4a217794fc0bfd70369a8230e56653c193da8ce` - verified that `d4a2177` descends from the previous `e2103d6` pin <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Submodule bump plus a breaking harness config shape affects all RLM runs still using the old `summarize_at_tokens` field; example migrations are mechanical but custom configs must be updated. > > **Overview** > Advances the **`deps/verifiers`** submodule to pick up merged Bash/RLM **context compaction** (and related harness defaults), with **`uv.lock`** refreshed so **`aiohttp`** meets the new **`>=3.14.1`** floor. > > **Breaking for RLM harness TOML:** flat **`env.agent.harness.summarize_at_tokens`** is replaced by **`env.agent.harness.compaction.summarize_at_tokens`**. The three advanced examples (**`glm-4.5-air/search`**, **`glm-4.5-air/terminal`**, **`nemotron-3-super/swe`**) are updated accordingly—train sources that used a two-threshold list **`[65536, 131072]`** now use a single **`98304`** threshold; eval sources keep **`98304`** but under the nested **`compaction`** key. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 7429aba. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
ExecutionPolicy.compactionoptionCompanion: verifiers #2454.
Breaking
ExecutionPolicy.compactionnow controls proactive and reactive compaction.ExecutionPolicy.summarize_at_tokensnow defaults toNoneinstead of256_000.compaction=trueto enable compaction. Leavesummarize_at_tokensunset to use model-card discovery.Verification
uv run ruff check .uv run ruff format --check .UV_PROJECT_ENVIRONMENT=/tmp/nano-rlm-merge-tests.xApDnC/.venv uv sync --group devUV_PROJECT_ENVIRONMENT=/tmp/nano-rlm-merge-tests.xApDnC/.venv uv run pytest tests/— 157 passedNote
Medium Risk
Changes core LLM turn handling and default context behavior (compaction off unless configured), so long runs and training trajectories may diverge from prior releases; mistakes in overflow detection or checkpoint retry could drop history or end runs early.
Overview
Context compaction is opt-in instead of defaulting to a 256k token threshold. Enable with
RLM_COMPACTION=1or by settingRLM_SUMMARIZE_AT_TOKENS; when compaction is on but no explicit threshold is set, the engine discovers the model window via/modelsand compacts at window minus 16k (small windows keep at least half). Docs dropRLM_MAX_OUTPUT/RLM_MAX_TOOL_OUTPUT_CHARSin favor of a fixed 20KB head/tail truncation on tool results before they hit the chat.Compaction logic moves into
rlm.compaction(overflow heuristics, checkpoint prompts, truncation). The agent loop gains_complete: proactive compaction after large tool turns, reactive compact-and-retry on provider 400/413 context errors, and onfinish_reason=lengthwhen usage crosses the threshold. Checkpoints retry up to three times with_last_goodfallbacks, plain-text-only summaries (no tool calls / no reasoning channel), andCompactionFailedclean stops instead of treating every oversized body as fatal.ExecutionPolicy.compactionand ACP/runtime snapshots expose the flag; semantic edge tracking can release and reclaim summary requests on resample. Tests cover overflow recovery, disabled compaction, discovered thresholds, and sub-agents.Reviewed by Cursor Bugbot for commit f300615. Bugbot is set up for automated code reviews on this repo. Configure here.