Class: Space::Architect::Harness::ClaudeCodeHarness
- Inherits:
-
Object
- Object
- Space::Architect::Harness::ClaudeCodeHarness
- Defined in:
- lib/space_architect/harness.rb
Constant Summary collapse
- ALLOWED_TOOLS =
"Read,Edit,Write,Grep,Glob,Bash,WebSearch,WebFetch"- DISALLOWED_TOOLS =
[ "Bash(git commit:*)", "Bash(git push:*)", "Bash(git reset:*)", "Bash(git merge:*)", "Bash(git rebase:*)", "Bash(git checkout:*)", "Bash(git branch:*)" ].join(",")
- TIMEOUT_EXIT_CODE =
124- LIVENESS_DELAY_SECONDS =
How long the liveness fiber waits before reading the run log's stream-json init event. Injectable via the run(liveness_delay:) kwarg so tests need not sleep seconds.
5.0
Instance Method Summary collapse
-
#initialize(model:, max_turns:, bin: nil, allowed_tools: ALLOWED_TOOLS, disallowed_tools: DISALLOWED_TOOLS) ⇒ ClaudeCodeHarness
constructor
A new instance of ClaudeCodeHarness.
- #run(prompt_path:, run_log_path:, chdir:, push_url: nil, push_token: nil, push_client: nil, timeout: nil, liveness_delay: LIVENESS_DELAY_SECONDS, err: $stderr) ⇒ Object
- #run_detached(prompt_path:, run_log_path:, chdir:) ⇒ Object
Constructor Details
#initialize(model:, max_turns:, bin: nil, allowed_tools: ALLOWED_TOOLS, disallowed_tools: DISALLOWED_TOOLS) ⇒ ClaudeCodeHarness
Returns a new instance of ClaudeCodeHarness.
48 49 50 51 52 53 54 55 |
# File 'lib/space_architect/harness.rb', line 48 def initialize(model:, max_turns:, bin: nil, allowed_tools: ALLOWED_TOOLS, disallowed_tools: DISALLOWED_TOOLS) @model = model @max_turns = max_turns @bin = bin || ENV.fetch("ARCHITECT_CLAUDE_BIN", "claude") @allowed_tools = allowed_tools @disallowed_tools = disallowed_tools end |
Instance Method Details
#run(prompt_path:, run_log_path:, chdir:, push_url: nil, push_token: nil, push_client: nil, timeout: nil, liveness_delay: LIVENESS_DELAY_SECONDS, err: $stderr) ⇒ Object
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/space_architect/harness.rb', line 63 def run(prompt_path:, run_log_path:, chdir:, push_url: nil, push_token: nil, push_client: nil, timeout: nil, liveness_delay: LIVENESS_DELAY_SECONDS, err: $stderr) 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| r, w = IO.pipe Sync do child = Async::Process::Child.new(*argv, chdir: chdir.to_s, in: prompt_io, out: w, err: log) w.close tasks = start_tee(r, log, push_url: push_url, push_token: push_token, push_client: push_client, err: err) timed_out = false timeout_task = nil # Async::Task#with_timeout cannot do TERM→grace→KILL because # Async::Process::Child#wait_thread's ensure goes straight to KILL. # Instead: a concurrent fiber fires after the deadline and escalates. # transient: true so the reactor doesn't wait for it when main work finishes. 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 # Liveness self-check: after a bounded delay, read the run log's stream-json # init event and print ONE line naming the streamed model + confirming growth. # transient: true so it never keeps the reactor alive; best-effort so it never # raises into the run path. run_detached gets no such fiber. liveness_task = nil if liveness_delay && liveness_delay > 0 liveness_task = Async(transient: true) do sleep liveness_delay emit_liveness(run_log_path, liveness_delay, err) end end status = child.wait timeout_task&.stop liveness_task&.stop tasks.each(&:wait) timed_out ? TIMEOUT_EXIT_CODE : status.exitstatus end end end end |
#run_detached(prompt_path:, run_log_path:, chdir:) ⇒ Object
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/space_architect/harness.rb', line 115 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 |