2424import importlib
2525import importlib .metadata
2626import json
27+ import math
2728import os
2829import signal
2930import sys
3031from pathlib import Path
31- from typing import AsyncIterator
32+ from typing import Any , AsyncIterator
3233
3334FRAMEWORK_VERSION_MISMATCH_EXIT_CODE = 78
3435
@@ -145,6 +146,7 @@ async def run_trial(framework: str, expected_version: str, config_file: Path) ->
145146 publish_ready_task (namespace , target )
146147 else :
147148 trial = await trial_type .create (config )
149+ bind_framework_timeout_budget (framework , trial )
148150 await trial .run ()
149151 finally :
150152 config_file .unlink (missing_ok = True )
@@ -162,6 +164,87 @@ def apply_subject_egress_policy(task: object) -> None:
162164 agent .allowed_hosts = [allowed_host ]
163165
164166
167+ def resolved_framework_timeout_ms (config : object , default_timeout : float | None ) -> int | None :
168+ base_timeout = config .agent .override_timeout_sec or default_timeout
169+ if base_timeout is None :
170+ return None
171+ maximum = config .agent .max_timeout_sec or float ("inf" )
172+ multiplier = (
173+ config .agent_timeout_multiplier
174+ if config .agent_timeout_multiplier is not None
175+ else config .timeout_multiplier
176+ )
177+ timeout_ms = math .floor (min (base_timeout , maximum ) * multiplier * 1000 )
178+ if timeout_ms <= 0 :
179+ raise RuntimeError ("framework agent timeout is shorter than one millisecond" )
180+ return timeout_ms
181+
182+
183+ def bind_framework_timeout_budget (framework : str , trial : object ) -> None :
184+ """Set the RelayAgent cleanup budget from each framework's effective phase timeout."""
185+
186+ if framework == "harbor" :
187+ original = getattr (trial , "_run_agent_phase" , None )
188+ agent = getattr (trial , "agent" , None )
189+ if not callable (original ) or not hasattr (agent , "set_framework_timeout_ms" ):
190+ raise RuntimeError ("Harbor timeout integration is unavailable" )
191+
192+ async def run_agent_phase (* args : Any , ** kwargs : Any ) -> Any :
193+ timeout_sec = kwargs .get ("timeout_sec" )
194+ agent .set_framework_timeout_ms (
195+ None if timeout_sec is None else math .floor (timeout_sec * 1000 )
196+ )
197+ return await original (* args , ** kwargs )
198+
199+ trial ._run_agent_phase = run_agent_phase
200+ return
201+
202+ if framework == "pier" :
203+ agent = getattr (trial , "_agent" , None )
204+ execution = getattr (trial , "_execution" , None )
205+ run_agent = getattr (trial , "_execute_agent" , None )
206+ run_step_agent = getattr (trial , "_execute_step_agent" , None )
207+ resolve_step_timeout = getattr (trial , "_resolve_step_timeout" , None )
208+ if (
209+ not hasattr (agent , "set_framework_timeout_ms" )
210+ or execution is None
211+ or not callable (run_agent )
212+ or not callable (run_step_agent )
213+ or not callable (resolve_step_timeout )
214+ ):
215+ raise RuntimeError ("Pier timeout integration is unavailable" )
216+
217+ async def execute_agent () -> Any :
218+ timeout_sec = execution .agent_timeout_sec
219+ agent .set_framework_timeout_ms (
220+ None if timeout_sec is None else math .floor (timeout_sec * 1000 )
221+ )
222+ return await run_agent ()
223+
224+ async def execute_step_agent (step : object , result : object ) -> Any :
225+ default_timeout = (
226+ step .agent .timeout_sec
227+ if step .agent .timeout_sec is not None
228+ else trial ._task .config .agent .timeout_sec
229+ )
230+ timeout_sec = resolve_step_timeout (
231+ override = trial .config .agent .override_timeout_sec ,
232+ default = default_timeout ,
233+ max_val = trial .config .agent .max_timeout_sec ,
234+ specific_multiplier = trial .config .agent_timeout_multiplier ,
235+ )
236+ agent .set_framework_timeout_ms (
237+ None if timeout_sec is None else math .floor (timeout_sec * 1000 )
238+ )
239+ return await run_step_agent (step , result )
240+
241+ trial ._execute_agent = execute_agent
242+ trial ._execute_step_agent = execute_step_agent
243+ return
244+
245+ raise RuntimeError ("framework must be harbor or pier" )
246+
247+
165248async def create_harbor_trial (trial_type : type , config : object ) -> object :
166249 trial_type ._resolve_agent_skills (config )
167250 task , task_download_result = await trial_type ._load_task (config )
0 commit comments