Skip to content

Commit bbfa5d2

Browse files
committed
Add support for per-prompt execution timeout (prompt_timeout_seconds)
Supports setting prompt_timeout_seconds across Agent CLI generators (gemini_cli, claude_code, codex_cli, agy_cli) to terminate prompt turns that spin indefinitely. Configurable per scenario JSON, per model YAML, or per run_config YAML. TAG=agy CONV=4229555b-deeb-44bb-a481-c751dab59476
1 parent c49f45f commit bbfa5d2

16 files changed

Lines changed: 176 additions & 22 deletions

AGENTS.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ simulated_user_model_config: datasets/model_configs/gemini_2.5_pro_model.yaml
166166

167167
runners:
168168
agent_runners: 10
169+
prompt_timeout_seconds: 300
169170

170171
scorers:
171172
trajectory_matcher: {}
@@ -183,11 +184,12 @@ reporting:
183184
```
184185
185186
### 2. Model Configuration (e.g., `gemini_cli_model.yaml`)
186-
Specifies tested version, model ID, and environment variables.
187+
Specifies tested version, model ID, environment variables, and prompt timeout.
187188

188189
```yaml
189190
gemini_cli_version: "@google/gemini-cli@0.36.0"
190191
generator: gemini_cli
192+
prompt_timeout_seconds: 180
191193
env:
192194
GOOGLE_CLOUD_PROJECT: "my-evaluation-project"
193195
GOOGLE_CLOUD_LOCATION: "us-central1"
@@ -211,7 +213,8 @@ Contains the test cases.
211213
"conversation_plan": "Ensure the agent accurately calls list_instances. Verify the output is returned correctly.",
212214
"expected_trajectory": ["cloud-sql__list_instances"],
213215
"env": { "GOOGLE_CLOUD_PROJECT": "my-evaluation-project" },
214-
"max_turns": 4
216+
"max_turns": 4,
217+
"prompt_timeout_seconds": 120
215218
}
216219
]
217220
}

docs/agy_cli_agent_testing.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ reporting:
182182
|-----|----------|-------------|
183183
| `generator` | Yes | Must be `agy_cli` |
184184
| `model` | Optional | agy UI model label, e.g. `"Gemini 3.1 Pro (High)"`. Passed via the `--model` flag. Must be a valid label, not an API id. |
185+
| `prompt_timeout_seconds` | Optional | Timeout limit in seconds for each CLI prompt turn (e.g., `180`) |
185186
| `env` | Optional | Environment variables passed to the CLI process |
186187
| `setup` | Optional | Tool setup block containing `mcp_servers`, `skills`, or `fake_mcp_servers` |
187188

docs/claude_code_agent_testing.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ The model config defines the Claude Code CLI version, model, auth, environment,
206206
| `vertex_project_id` | If `use_vertex` | GCP project for Vertex AI |
207207
| `vertex_region` | If `use_vertex` | Vertex region (e.g., `us-east5`) |
208208
| `env` | Optional | Environment variables passed to the CLI process |
209+
| `prompt_timeout_seconds` | Optional | Timeout limit in seconds for each CLI prompt turn (e.g., `180`) |
209210
| `setup.mcp_servers` | Optional | MCP server configurations (see [MCP Servers](#mcp-servers)) |
210211
| `allowed_tools` | Optional | List of tool names to allow (e.g., `["Bash", "mcp__cloud-sql"]`) |
211212

docs/codex_cli_agent_testing.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ The model config defines the Codex CLI version, model, auth, sandbox/approval po
213213
| `profile` | Optional | Codex profile name (forwarded as `--profile <name>`) |
214214
| `json_flag` | Optional | `"--json"` (default, newer Codex versions) or `"--experimental-json"` (older versions). Codex requires NDJSON for the eval pipeline to extract tool calls and tokens. |
215215
| `pricing` | Optional | Per-model rates used to compute `cost_usd` per turn. See [Pricing & Cost Tracking](#pricing--cost-tracking). |
216+
| `prompt_timeout_seconds` | Optional | Timeout limit in seconds for each CLI prompt turn (e.g., `180`) |
216217
| `env` | Optional | Environment variables passed to the CLI process (e.g., `GOOGLE_CLOUD_PROJECT` for Cloud SQL MCP) |
217218
| `setup.mcp_servers` | Optional | MCP server configurations (see [MCP Servers](#mcp-servers)) |
218219
| `setup.config` | Optional | Free-form key/value pairs written to the top of `~/.codex/config.toml`. Merged on top of the default `forced_login_method = "api"`. |

docs/configs/model-config.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ These settings are passed to all generators, regardless of the specific engine u
1515
| `max_tokens` | Optional | N/A | Specifies the maximum number of tokens the model can generate in a single output. |
1616
| `execs_per_minute` | Optional | `60` | Sets the maximum number of executions allowed per minute. If not provided, it defaults to `60`. This helps throttle the rate of query generation. |
1717
| `max_attempts` | Optional | `3` | Specifies the maximum number of attempts for query generation in case of failures. Defaults to `3` if not provided. |
18+
| `prompt_timeout_seconds` | Optional | N/A | Timeout limit in seconds for each CLI prompt execution turn (e.g. `180`). Overrides run config `runners.prompt_timeout_seconds`. |
1819

1920
## GCP Specific Configuration
2021

docs/configs/run-config.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ This section defines the primary resources used during evaluation, including the
1717
| `num_trials` | Optional | Number of trials to run for each prompt. |
1818
| `scenarios` | Optional | A list of specific scenario IDs to run (only applies to scenario-based agentic datasets like `gemini-cli-format` or `cortado-format`). Defaults to empty (runs all scenarios). |
1919
| `scenario_pattern` | Optional | A glob pattern of scenario IDs to run (only applies to scenario-based agentic datasets). Defaults to None (runs all scenarios). |
20+
| `runners` | Optional | Dictionary configuring concurrency and timeouts. <br>• `agent_runners`: Concurrency level for agent scenario runners (default: 10).<br>• `prompt_timeout_seconds`: Global timeout limit in seconds for a single agent prompt CLI execution turn (e.g. `300`). |
2021
---
2122

2223
## 2. Prompt and Generation Modules

docs/gemini_cli_agent_testing.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ The evalset JSON file defines the test scenarios. Each scenario represents an ag
253253
| `env` | Optional | Per-scenario environment variables (merged with model config env) |
254254
| `kind` | Optional | Category label (e.g., `"tools"`) |
255255
| `max_turns` | Yes | Maximum number of conversation turns before the evaluation stops |
256+
| `prompt_timeout_seconds` | Optional | Timeout limit in seconds for each CLI prompt turn in this scenario. Overrides model config and run config timeout settings. |
256257

257258
#### Tool name format
258259

evalbench/evaluator/agentevaluator.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,12 @@ def process_scenario(
141141
"Declared env file not found in session: %s", src_path
142142
)
143143

144+
prompt_timeout = (
145+
scenario.get("prompt_timeout_seconds")
146+
or getattr(self.generator, "prompt_timeout_seconds", None)
147+
or self.config.get("runners", {}).get("prompt_timeout_seconds")
148+
)
149+
144150
session_id = None
145151
for turn in range(max_turns):
146152
logging.info(
@@ -153,6 +159,7 @@ def process_scenario(
153159
resume=(turn > 0),
154160
session_id=session_id,
155161
cwd=resolved_work_dir,
162+
timeout=prompt_timeout,
156163
)
157164
try:
158165
result = self.generator.safe_generate(cli_cmd)

evalbench/generators/models/agent_cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def version(self) -> str:
2323
@abstractmethod
2424
def create_command(
2525
self, cli: str, prompt: str, env: dict = None, resume: bool = False,
26-
session_id: str = None, cwd: str = None,
26+
session_id: str = None, cwd: str = None, timeout: float | int = None,
2727
):
2828
raise NotImplementedError("Subclasses must implement this method")
2929

evalbench/generators/models/agy_cli.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,13 @@
2323

2424

2525
class CLICommand:
26-
def __init__(self, cli, prompt, env=None, resume=False, cwd=None):
26+
def __init__(self, cli, prompt, env=None, resume=False, cwd=None, timeout=None):
2727
self.cli = cli
2828
self.prompt = prompt
2929
self.env = env if env else {}
3030
self.resume = resume
3131
self.cwd = cwd
32+
self.timeout = timeout
3233

3334

3435
class AgyCliGenerator(AgentCliGenerator):
@@ -781,7 +782,7 @@ def generate_internal(self, cli_cmd):
781782
return self._run_agy_cli(cli_cmd)
782783

783784
def _execute_cli_command(
784-
self, command, env=None, cwd=None
785+
self, command, env=None, cwd=None, timeout=None
785786
) -> subprocess.CompletedProcess:
786787
try:
787788
return subprocess.run(
@@ -791,6 +792,11 @@ def _execute_cli_command(
791792
check=False,
792793
env=env,
793794
cwd=cwd if cwd else self.fake_home,
795+
timeout=timeout,
796+
)
797+
except subprocess.TimeoutExpired:
798+
return subprocess.CompletedProcess(
799+
command, 124, "", f"Error: Command timed out after {timeout} seconds."
794800
)
795801
except FileNotFoundError:
796802
return subprocess.CompletedProcess(
@@ -811,7 +817,7 @@ def _run_agy_cli(self, cli_cmd: CLICommand):
811817
self.agy_bin, cli_cmd.prompt, cli_cmd.resume, self.model
812818
)
813819
cwd = cli_cmd.cwd if cli_cmd.cwd else self.fake_home
814-
result = self._execute_cli_command(command, env=env, cwd=cwd)
820+
result = self._execute_cli_command(command, env=env, cwd=cwd, timeout=cli_cmd.timeout)
815821

816822
if result.returncode == 0:
817823
try:
@@ -1219,7 +1225,7 @@ def safe_generate(
12191225

12201226
def create_command(
12211227
self, cli: str, prompt: str, env: dict = None, resume: bool = False,
1222-
session_id: str = None, cwd: str = None,
1228+
session_id: str = None, cwd: str = None, timeout: float | int = None,
12231229
) -> CLICommand:
12241230
# The executable is always this session's sandbox binary
12251231
# (self.agy_bin); the ``cli`` argument -- the agent_version label "agy"
@@ -1229,4 +1235,4 @@ def create_command(
12291235
# environment are layered in once at invocation time by
12301236
# ``_run_agy_cli`` via ``_merged_env``.
12311237
return CLICommand(cli=self.agy_bin, prompt=prompt, env=env or {},
1232-
resume=resume, cwd=cwd)
1238+
resume=resume, cwd=cwd, timeout=timeout)

0 commit comments

Comments
 (0)