Conversation
Terminal-Bench declares an agent budget per task in task.toml ([agent] timeout_sec). The sandboxed Terminus-2 agent ignored it and applied the flat sandbox_timeout to every task, so short tasks ran far past their own budget. prepare.py now carries agent_timeout_sec on each benchmark row, and the agent uses it as the agent-loop deadline, falling back to sandbox_timeout when a dataset supplies no per-task value. A new max_agent_timeout config key caps the resolved budget, mirroring Harbor's agent.max_timeout_sec. The resolved value is recorded as the agent_timeout metric so runs are auditable after the fact. Behaviour is unchanged for datasets that do not emit agent_timeout_sec. Signed-off-by: Wojciech Prazuch <wprazuch@nvidia.com>
yaoyu-33
reviewed
Sep 10, 2026
Comment on lines
+332
to
+337
| def _resolve_agent_timeout(self, task_timeout: float | None) -> float: | ||
| """Per-task budget when the dataset supplies one, else the flat fallback, then capped.""" | ||
| timeout = self.config.sandbox_timeout if task_timeout is None else float(task_timeout) | ||
| if self.config.max_agent_timeout is not None: | ||
| timeout = min(timeout, self.config.max_agent_timeout) | ||
| return timeout |
Contributor
There was a problem hiding this comment.
Could we keep sandbox_timeout as the existing global hard cap? The per-task wiring is necessary, but adding max_agent_timeout is not: with its default of null, a task timeout can now exceed sandbox_timeout, changing the existing config contract from “maximum agent wall clock” to “fallback only.” Taking the minimum preserves backward compatibility while still honoring shorter per-task budgets. If Terminal-Bench 2.1 needs the full 12,000-second long-tail budget, its benchmark config can explicitly raise sandbox_timeout.
Suggested change
| def _resolve_agent_timeout(self, task_timeout: float | None) -> float: | |
| """Per-task budget when the dataset supplies one, else the flat fallback, then capped.""" | |
| timeout = self.config.sandbox_timeout if task_timeout is None else float(task_timeout) | |
| if self.config.max_agent_timeout is not None: | |
| timeout = min(timeout, self.config.max_agent_timeout) | |
| return timeout | |
| def _resolve_agent_timeout(self, task_timeout: float | None) -> float: | |
| """Use the per-task budget without exceeding the configured sandbox timeout.""" | |
| if task_timeout is None: | |
| return self.config.sandbox_timeout | |
| return min(float(task_timeout), self.config.sandbox_timeout) |
With this, max_agent_timeout can also be removed from the config model and YAML.
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.
Problem
Terminal-Bench declares an agent time budget per task, in each task's
task.toml:The sandboxed Terminus-2 agent never reads it.
prepare.pytakes onlytask.name,environment.docker_imageand the task folder, andapp.pywraps the agent loop in a singleflat
sandbox_timeout(10800 s by default) that applies to every task equally.Across the 89 tasks in Terminal-Bench 2.1 the declared budgets range from 600 s to 12000 s, with
a median of 900 s — 48 of the 89 tasks budget exactly 900 s. A flat three-hour wall is therefore
12x the declared budget for the majority of the suite.
On a recent 712-rollout run (89 tasks x 8 repeats) this had a measurable effect on the reported
score, not just on cost:
the task's declared budget had expired. Of those 24, 19 first reported task completion only
after the budget had already passed, so they are not solves under the benchmark's own rules.
public Terminal-Bench 2.1 leaderboard reports a mean trial duration between 548 s and 1043 s.
The reference implementation does honour the per-task value. Harbor's own trial layer resolves
config.agent.override_timeout_sec or self._task.config.agent.timeout_sec, caps it withconfig.agent.max_timeout_sec, and applies the result as the deadline foragent.run(...).Because this agent calls
agent.run(...)directly rather than going throughTrial, that logicis bypassed.
Change
benchmarks/terminal_bench_2_1/prepare.pyemitsagent_timeout_secon every row, read fromtask.toml.terminus_2_sandboxed_agentuses that value as the agent-loop deadline when the dataset rowprovides one, and falls back to
sandbox_timeoutwhen it does not.max_agent_timeoutconfig key caps the resolved budget. This mirrors Harbor'sagent.max_timeout_secand exists so a long-tail task cannot stretch a whole job's wall clock.It defaults to
null(uncapped), so it is opt-in.agent_timeoutmetric, so a completed run can beaudited against the budget it actually ran under.
I followed Harbor's semantics (per-task value, capped) rather than
anyterminal_agent's(per-task value with a
global_agent_timeoutthat replaces it), because a cap is what keeps ajob's wall clock bounded without discarding the per-task budgets. The dataset field is named
agent_timeout_secto match the nameanyterminal_agentalready uses for the sametask.tomlfield.
Compatibility
Behaviour is unchanged for any dataset that does not emit
agent_timeout_sec— that path stillresolves to
sandbox_timeout. Terminal-Bench 2.1 runs will change, which is the intent.Anyone comparing to earlier Terminal-Bench 2.1 numbers should treat this as a new baseline rather
than a regression: the previous numbers measure reward under a flat wall, and these measure reward
under the benchmark's declared budgets. On the run described above the change is worth roughly
-0.034 absolute (0.188 to 0.155), because it withdraws the 24 out-of-spec solves.
Testing
uv run python -m pytest responses_api_agents/terminus_2_sandboxed_agent/tests/test_app.py— 15 passed.binding and not binding.
prepare.pyextraction expression against all 89 realtask.tomlfiles: 89/89yield a non-null budget, distribution
{600: 1, 750: 1, 900: 48, 1200: 5, 1800: 17, 2400: 2, 3600: 13, 7200: 1, 12000: 1}.Terminus2AgentRunRequest(extra="allow") andthat its absence resolves to
None.ruff checkandruff format --checkclean on the touched trees.