feat: per-rollout token limits (max_input/output/total_tokens) - #1591
Conversation
…}_tokens) Add framework-enforced token budgets alongside max_turns: max_input_tokens, max_output_tokens, max_total_tokens on EnvConfig. The interception server checks them before each turn via a new RolloutLimits bundle (which also subsumes max_turns), capping the trace's prompt_len / completion_len / total_tokens computed properties. Reaching any limit refuses the turn and records it as the stop condition, and is_truncated now treats the token-limit conditions as truncation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <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 5ae2372. Configure here.
| `completion_len`; framework-enforced between turns.""" | ||
| max_total_tokens: int | None = None | ||
| """Max total (prompt + completion) tokens per rollout (None = no limit). Caps the | ||
| trace's `total_tokens`; framework-enforced between turns.""" |
There was a problem hiding this comment.
Missing docs for token limits
Medium Severity
This PR adds framework max_input_tokens, max_output_tokens, and max_total_tokens on EnvConfig (top-level eval/TOML flags like max_turns), but none of the core docs under docs/ describe them. Users have no reference for configuring or interpreting these limits and truncation stop conditions.
Triggered by project rule: BugBot Instructions
Reviewed by Cursor Bugbot for commit 5ae2372. Configure here.
ApprovabilityVerdict: Needs human review This PR introduces new framework-enforced token limits that change rollout termination behavior - a new feature capability. While the implementation is straightforward and well-structured, new features introducing user-facing behavior changes warrant human review. You can customize Macroscope's approvability policy. Learn more. |
…Intellect-ai#1591) * feat(v1): per-rollout token limits (EnvConfig.max_{input,output,total}_tokens) Add framework-enforced token budgets alongside max_turns: max_input_tokens, max_output_tokens, max_total_tokens on EnvConfig. The interception server checks them before each turn via a new RolloutLimits bundle (which also subsumes max_turns), capping the trace's prompt_len / completion_len / total_tokens computed properties. Reaching any limit refuses the turn and records it as the stop condition, and is_truncated now treats the token-limit conditions as truncation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(v1): drop 'like max_turns' from token-limit field docstrings Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style(v1): trim limit-check comment in interception Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style(v1): ruff format interception Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>


Summary
EnvConfig, alongside the existingmax_turns:max_input_tokens— caps the trace'sprompt_lenmax_output_tokens— caps the trace'scompletion_lenmax_total_tokens— caps the trace'stotal_tokensRolloutLimits(a small frozen bundle ininterception.py) that holds all framework limits — turns + the three token budgets — and exposesreached(trace), returning the name of the first limit reached (orNone). This subsumes the old standalonemax_turnscheck so all framework limits go through one place and thread as a single object (Environment→Rollout→InterceptionServer).limits.reached(trace)before serving each turn (same mechanism as a@stop): the first limit reached refuses the turn, halting any harness, and is recorded as the trace'sstop_condition.Trace.is_truncatednow treats the token-limit stop conditions (max_input_tokens/max_output_tokens/max_total_tokens) as truncation, likemax_turns/harness_timeout.The checks read the trace's existing computed properties (
prompt_len/completion_len/total_tokens), so they apply uniformly to any harness. Token caps are soft by one turn: they're checked between turns, so the turn that crosses a cap still completes.Breaking
InterceptionServer(...)andRollout(...)no longer take amax_turnskeyword — they takelimits: RolloutLimits | Noneinstead.Environment.max_turnsis replaced byEnvironment.limits. These are internal constructors (onlyEnvironment.episode/Rollout.runcall them); the publicEnvConfig.max_turnsconfig field and its CLI flag are unchanged.Verification
ruff check --isolatedclean on all changed files.RolloutLimits.reached:>=boundary semantics, the input/output/total →prompt_len/completion_len/total_tokensmapping, and limit priority (turns before tokens when both are reached). Imports ofenv/rollout/trace/interceptionconfirmed (no circular import from the newenv→interceptionedge).Follow-up (separate PR, prime-rl side): surface these fields on the orchestrator's env config so they can be set from training TOMLs.
Note
Add per-rollout token limits (max_input/output/total_tokens) to rollout execution
RolloutLimitsfrozen dataclass in interception.py that holds optional caps for turns, input tokens, output tokens, and total tokens, with areached()method that checks aTraceagainst all limits.max_input_tokens,max_output_tokens, andmax_total_tokensfields toEnvConfigin env.py, which are aggregated into aRolloutLimitsobject and passed down to eachRollout.InterceptionServernow callslimits.reached()before serving each turn; if any limit is hit, it returns HTTP 400 withrollout stopped: {limit}and sets the trace stop condition to the limit name.Trace.is_truncatedin trace.py now returnsTruefor the three new token-limit stop conditions in addition tomax_turnsandharness_timeout.Macroscope summarized 5ae2372.
Note
Medium Risk
Changes rollout halt behavior and internal Rollout/InterceptionServer APIs; misconfigured limits could truncate eval rollouts unexpectedly, but enforcement mirrors existing max_turns.
Overview
Adds framework-enforced per-rollout token budgets on
EnvConfig(max_input_tokens,max_output_tokens,max_total_tokens) alongside existingmax_turns, exposed as top-level eval/serve CLI flags like other env limits.Introduces
RolloutLimitswithreached(trace)so turn and token caps are checked in one place before each intercepted chat turn (same halt path as@stop).Environmentbuilds limits from config and passes them throughRollouttoInterceptionServerinstead of a standalonemax_turnsargument.Trace.is_truncatednow treats token-limit stop conditions (max_input_tokens,max_output_tokens,max_total_tokens) as truncation, consistent withmax_turnsandharness_timeout. Token caps are evaluated between turns (soft by one turn).Reviewed by Cursor Bugbot for commit 5ae2372. Bugbot is set up for automated code reviews on this repo. Configure here.