Module: Silas::SubprocessRunner

Defined in:
lib/silas/subprocess_runner.rb

Overview

The engine-owned analog of StepRunner: it wraps one whole subprocess run in a single anchor Step and persists the durable result. Replay-aware and fail-closed — a resumed run whose subprocess got far enough to register a CLI session but did not finish is FAILED rather than re-spawned, because an engine-owned subprocess can't be replayed exactly-once (design risk #1).

Class Method Summary collapse

Class Method Details

.call(turn) ⇒ Object

Returns :terminal or :failed.



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/silas/subprocess_runner.rb', line 11

def call(turn)
  step = Step.find_or_create_by!(turn: turn, index: 0)
  return :terminal if step.completed?

  if turn.cli_session_id.present?
    # A prior execution spawned a subprocess that never completed the step.
    turn.finish!(:failed, reason: "agent_sdk_interrupted")
    return :failed
  end

  result = Silas.resolved_engine.execute_step(engine_context(turn, step))
  step.update!(
    status: "completed", terminal: true,
    response_blocks: result.blocks, stop_reason: result.stop_reason,
    model: Silas.agent.model,
    input_tokens: result.usage&.dig(:input_tokens),
    output_tokens: result.usage&.dig(:output_tokens)
  )
  :terminal
end

.engine_context(turn, step) ⇒ Object



32
33
34
35
36
37
38
39
# File 'lib/silas/subprocess_runner.rb', line 32

def engine_context(turn, step)
  { turn: turn, step: step, index: 0,
    system: turn.instructions_snapshot,
    messages: MessageBuilder.call(turn, upto_index: nil),
    tools: Silas.tool_definitions,
    model: Silas.agent.model,
    limits: { max_steps: Silas.agent.max_steps } }
end