@@ -167,21 +167,30 @@ def _assemble_prompt(
167167 return prompt
168168
169169
170- def _execute_agent (
170+ class _AgentResult (NamedTuple ):
171+ """Result of running the agent subprocess."""
172+
173+ returncode : int | None # None means timed out
174+ elapsed : float
175+ log_file : Path | None
176+
177+
178+ def _run_agent_process (
179+ cmd : list [str ],
171180 prompt : str ,
172- config : RunConfig ,
173- state : RunState ,
181+ timeout : float | None ,
174182 log_path_dir : Path | None ,
175- emit : _BoundEmitter ,
176- ) -> int | None :
177- """Run the agent subprocess and emit the result event .
183+ iteration : int ,
184+ ) -> _AgentResult :
185+ """Run the agent subprocess, optionally write logs, and return the result.
178186
179- Updates ``state`` counters (completed / failed / timed_out) and returns
180- the process return code, or ``None`` if the process timed out.
181- """
182- iteration = state .iteration
183- cmd = [config .command ] + config .args
187+ When *log_path_dir* is set, output is captured, written to a log file,
188+ then echoed to stdout/stderr so the user still sees it live. When unset,
189+ output streams directly to the terminal (no capture overhead).
184190
191+ Returns ``returncode=None`` when the process times out.
192+ Raises ``FileNotFoundError`` if the command binary does not exist.
193+ """
185194 start = time .monotonic ()
186195 log_file : Path | None = None
187196 returncode : int | None = None
@@ -191,7 +200,7 @@ def _execute_agent(
191200 cmd ,
192201 input = prompt ,
193202 text = True ,
194- timeout = config . timeout ,
203+ timeout = timeout ,
195204 capture_output = bool (log_path_dir ),
196205 )
197206 if log_path_dir :
@@ -204,39 +213,64 @@ def _execute_agent(
204213 except subprocess .TimeoutExpired as e :
205214 if log_path_dir :
206215 log_file = _write_log (log_path_dir , iteration , e .stdout , e .stderr )
216+
217+ return _AgentResult (
218+ returncode = returncode ,
219+ elapsed = time .monotonic () - start ,
220+ log_file = log_file ,
221+ )
222+
223+
224+ def _execute_agent (
225+ prompt : str ,
226+ config : RunConfig ,
227+ state : RunState ,
228+ log_path_dir : Path | None ,
229+ emit : _BoundEmitter ,
230+ ) -> int | None :
231+ """Run the agent subprocess and emit the result event.
232+
233+ Updates ``state`` counters (completed / failed / timed_out) and returns
234+ the process return code, or ``None`` if the process timed out.
235+ """
236+ cmd = [config .command ] + config .args
237+
238+ try :
239+ agent = _run_agent_process (
240+ cmd , prompt , config .timeout , log_path_dir , state .iteration ,
241+ )
207242 except FileNotFoundError :
208243 raise FileNotFoundError (
209244 f"Agent command not found: { config .command !r} . "
210245 f"Check the [agent] command in ralph.toml."
211246 )
212247
213- elapsed = time .monotonic () - start
214- duration = format_duration (elapsed )
248+ duration = format_duration (agent .elapsed )
215249
216250 # All state counter updates in one place for easy auditing.
217- if returncode is None :
251+ if agent . returncode is None :
218252 state .timed_out += 1
219253 state .failed += 1
220254 event_type = EventType .ITERATION_TIMED_OUT
221255 state_detail = f"timed out after { duration } "
222- elif returncode == 0 :
256+ elif agent . returncode == 0 :
223257 state .completed += 1
224258 event_type = EventType .ITERATION_COMPLETED
225259 state_detail = f"completed ({ duration } )"
226260 else :
227261 state .failed += 1
228262 event_type = EventType .ITERATION_FAILED
229- state_detail = f"failed with exit code { returncode } ({ duration } )"
263+ state_detail = f"failed with exit code { agent . returncode } ({ duration } )"
230264
231265 emit (event_type , {
232- "iteration" : iteration ,
233- "returncode" : returncode ,
234- "duration" : elapsed ,
266+ "iteration" : state . iteration ,
267+ "returncode" : agent . returncode ,
268+ "duration" : agent . elapsed ,
235269 "duration_formatted" : duration ,
236270 "detail" : state_detail ,
237- "log_file" : str (log_file ) if log_file else None ,
271+ "log_file" : str (agent . log_file ) if agent . log_file else None ,
238272 })
239- return returncode
273+ return agent . returncode
240274
241275
242276def _run_checks_phase (
0 commit comments