Class: Space::Architect::Harness::PiHarness

Inherits:
Object
  • Object
show all
Defined in:
lib/space_architect/harness.rb

Overview

pi -p --mode json, session redirected into the lane's build dir via --session-dir (pi otherwise auto-saves under ~/.pi/agent/sessions/, organized by mangled CWD).

Constant Summary collapse

TIMEOUT_EXIT_CODE =
124

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(model:, max_turns:, bin: nil, config_dir:, effort: nil) ⇒ PiHarness

max_turns is accepted for interface parity with the other harnesses but is intentionally absent from argv — pi has no turn cap; the wall-clock timeout (below) is the only bound on a run.



448
449
450
451
452
453
# File 'lib/space_architect/harness.rb', line 448

def initialize(model:, max_turns:, bin: nil, config_dir:, effort: nil)
  @model      = model
  @bin        = bin || ENV.fetch("ARCHITECT_PI_BIN", "pi")
  @config_dir = Pathname.new(config_dir)
  @effort     = effort
end

Class Method Details

.translate_thinking(level, force: false) ⇒ Object

pi's --thinking accepts the full normalized vocabulary unchanged — pi's own per-model thinkingLevelMap clamps further; architect does not clamp for pi.



439
440
441
442
443
# File 'lib/space_architect/harness.rb', line 439

def self.translate_thinking(level, force: false)
  return [nil, nil] if level.nil?
  return [level, "thinking: force --effort=#{level} (unmodified, may be rejected)"] if force
  [level, nil]
end

Instance Method Details

#run(prompt_path:, run_log_path:, chdir:, timeout: nil) ⇒ Object



455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
# File 'lib/space_architect/harness.rb', line 455

def run(prompt_path:, run_log_path:, chdir:, timeout: nil, **)
  prompt_path  = Pathname.new(prompt_path)
  run_log_path = Pathname.new(run_log_path)

  File.open(prompt_path, "r") do |prompt_io|
    File.open(run_log_path, "w") do |log|
      Sync do
        child = Async::Process::Child.new(*argv, chdir: chdir.to_s, in: prompt_io, out: log, err: log)
        timed_out    = false
        timeout_task = nil

        # Same TERM→grace→KILL escalation as ClaudeCodeHarness — with: Async::Process::Child's
        # #wait_thread ensure goes straight to KILL, so a concurrent fiber does the escalation.
        if timeout && timeout > 0
          timeout_task = Async(transient: true) do
            sleep timeout
            timed_out = true
            Process.kill("TERM", -child.pid) rescue nil
            sleep 0.5
            Process.kill("KILL", -child.pid) rescue nil
          end
        end

        status = child.wait
        timeout_task&.stop

        timed_out ? TIMEOUT_EXIT_CODE : status.exitstatus
      end
    end
  end
end

#run_detached(prompt_path:, run_log_path:, chdir:) ⇒ Object



487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
# File 'lib/space_architect/harness.rb', line 487

def run_detached(prompt_path:, run_log_path:, chdir:)
  prompt_path  = Pathname.new(prompt_path)
  run_log_path = Pathname.new(run_log_path)

  prompt_io = File.open(prompt_path, "r")
  log       = File.open(run_log_path, "w")
  begin
    pid = Process.spawn(*argv, chdir: chdir.to_s, pgroup: true,
                        in: prompt_io, out: log, err: log)
    Process.detach(pid)
  ensure
    prompt_io.close
    log.close
  end
  pid
end