Skip to content

Commit 5266696

Browse files
Kasper Jungeclaude
authored andcommitted
refactor: extract _run_agent_process to separate subprocess I/O from state management
Split _execute_agent into two focused functions: - _run_agent_process: pure subprocess execution + log writing, returns _AgentResult - _execute_agent: interprets the result, updates state counters, emits events This gives each function a single responsibility and makes the conditional capture_output / log-then-echo behavior explicitly documented. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f54ac63 commit 5266696

1 file changed

Lines changed: 56 additions & 22 deletions

File tree

src/ralphify/engine.py

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -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

242276
def _run_checks_phase(

0 commit comments

Comments
 (0)