harnex 0.12.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +592 -0
- data/GUIDE.md +222 -68
- data/README.md +78 -48
- data/TECHNICAL.md +235 -98
- data/agents/gpt.md +60 -0
- data/docs/agents.md +119 -0
- data/docs/dispatch-telemetry.md +184 -50
- data/docs/events.md +20 -7
- data/docs/lanes.md +202 -0
- data/docs/mcp.md +102 -0
- data/docs/pi-rpc.md +81 -7
- data/docs/upgrading-to-0.29.md +87 -0
- data/guides/01_dispatch.md +101 -34
- data/guides/04_monitoring.md +223 -25
- data/guides/05_naming.md +0 -1
- data/guides/06_claude.md +96 -0
- data/lib/harnex/adapters/base.rb +4 -8
- data/lib/harnex/adapters/pi.rb +92 -12
- data/lib/harnex/adapters.rb +1 -16
- data/lib/harnex/artifact_report.rb +206 -12
- data/lib/harnex/cli.rb +68 -50
- data/lib/harnex/commands/ask.rb +278 -0
- data/lib/harnex/commands/delegate.rb +202 -0
- data/lib/harnex/commands/doctor.rb +34 -36
- data/lib/harnex/commands/events.rb +3 -3
- data/lib/harnex/commands/history.rb +2 -8
- data/lib/harnex/commands/land.rb +150 -0
- data/lib/harnex/commands/lanes.rb +84 -0
- data/lib/harnex/commands/logs.rb +3 -3
- data/lib/harnex/commands/mcp.rb +208 -0
- data/lib/harnex/commands/orchestration.rb +88 -9
- data/lib/harnex/commands/pane.rb +3 -3
- data/lib/harnex/commands/preflight.rb +566 -0
- data/lib/harnex/commands/reply.rb +125 -0
- data/lib/harnex/commands/result.rb +108 -0
- data/lib/harnex/commands/run.rb +248 -64
- data/lib/harnex/commands/send.rb +121 -16
- data/lib/harnex/commands/status.rb +32 -4
- data/lib/harnex/commands/stop.rb +14 -4
- data/lib/harnex/commands/telemetry.rb +19 -1
- data/lib/harnex/commands/wait.rb +421 -56
- data/lib/harnex/commands/watch.rb +134 -21
- data/lib/harnex/context_file.rb +85 -0
- data/lib/harnex/core.rb +100 -15
- data/lib/harnex/dispatch_history.rb +157 -10
- data/lib/harnex/lanes.rb +513 -0
- data/lib/harnex/mcp.rb +362 -0
- data/lib/harnex/orchestration.rb +128 -5
- data/lib/harnex/owned_process_group.rb +50 -0
- data/lib/harnex/owner_stream.rb +399 -0
- data/lib/harnex/pricing.rb +4 -9
- data/lib/harnex/runtime/activity_tracker.rb +107 -0
- data/lib/harnex/runtime/api_server.rb +11 -1
- data/lib/harnex/runtime/completion_notifier.rb +90 -11
- data/lib/harnex/runtime/inbox.rb +26 -7
- data/lib/harnex/runtime/message.rb +5 -2
- data/lib/harnex/runtime/runtime_budget.rb +96 -0
- data/lib/harnex/runtime/session.rb +684 -294
- data/lib/harnex/runtime/stop_request.rb +39 -0
- data/lib/harnex/stall.rb +46 -0
- data/lib/harnex/telemetry_reconciler.rb +42 -20
- data/lib/harnex/terminal_status.rb +5 -5
- data/lib/harnex/version.rb +2 -2
- data/lib/harnex.rb +16 -0
- data/recipes/01_fire_and_watch.md +14 -14
- data/recipes/02_chain_implement.md +35 -31
- data/recipes/03_buddy.md +1 -1
- metadata +34 -12
- data/docs/codex-appserver.md +0 -175
- data/lib/harnex/adapters/codex.rb +0 -160
- data/lib/harnex/adapters/codex_appserver.rb +0 -407
- data/lib/harnex/codex/app_server/client.rb +0 -347
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require "time"
|
|
2
|
+
|
|
3
|
+
module Harnex
|
|
4
|
+
# Caller-declared provenance is deliberately an enum, never a free-text log.
|
|
5
|
+
# Session owns first-request-wins synchronization and work acceptance.
|
|
6
|
+
class StopRequest
|
|
7
|
+
REASONS = %w[manual completion runtime_budget].map(&:freeze).freeze
|
|
8
|
+
ORIGINS = %w[api cli auto_stop watch runtime].map(&:freeze).freeze
|
|
9
|
+
WORK_STATES = %w[running completed failed unknown].freeze
|
|
10
|
+
|
|
11
|
+
def self.validate!(reason:, origin:)
|
|
12
|
+
raise ArgumentError, "stop reason must be one of #{REASONS.join(', ')}" unless REASONS.include?(reason)
|
|
13
|
+
raise ArgumentError, "stop origin must be one of #{ORIGINS.join(', ')}" unless ORIGINS.include?(origin)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(reason:, origin:, work_state:, requested_at: Time.now, runtime_limit_s: nil)
|
|
17
|
+
self.class.validate!(reason: reason, origin: origin)
|
|
18
|
+
raise ArgumentError, "invalid stop work state" unless WORK_STATES.include?(work_state)
|
|
19
|
+
|
|
20
|
+
@payload = {
|
|
21
|
+
reason: reason.dup.freeze,
|
|
22
|
+
origin: origin.dup.freeze,
|
|
23
|
+
requested_at: requested_at.getutc.iso8601(3).freeze,
|
|
24
|
+
work_state: work_state.dup.freeze
|
|
25
|
+
}
|
|
26
|
+
unless runtime_limit_s.nil?
|
|
27
|
+
limit = Float(runtime_limit_s)
|
|
28
|
+
raise ArgumentError, "invalid stop runtime limit" unless limit.finite? && limit.positive?
|
|
29
|
+
|
|
30
|
+
@payload[:runtime_limit_s] = limit
|
|
31
|
+
end
|
|
32
|
+
@payload.freeze
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def to_h
|
|
36
|
+
@payload.dup
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/lib/harnex/stall.rb
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Harnex
|
|
2
|
+
# #76: one "no progress" predicate over a live status payload, shared by
|
|
3
|
+
# `wait/watch --stall-after` and later observers (#77). It only reads clocks;
|
|
4
|
+
# it never nudges, stops, or judges the worker.
|
|
5
|
+
module Stall
|
|
6
|
+
STRUCTURED_CLOCKS = %w[turn_age_s model_idle_s tool_idle_s].freeze
|
|
7
|
+
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
# Seconds since the session last showed progress, with the clock used:
|
|
11
|
+
# activity - the structured Pi adapter: the most
|
|
12
|
+
# recent of turn start, model event, or tool event, and
|
|
13
|
+
# only while a turn is active;
|
|
14
|
+
# output_log - PTY adapters: seconds since the transcript last changed.
|
|
15
|
+
# nil when no clock applies: settled work, no active structured turn, or
|
|
16
|
+
# nothing observable.
|
|
17
|
+
def progress_idle(payload)
|
|
18
|
+
return nil unless payload.is_a?(Hash)
|
|
19
|
+
return nil if payload["done"] == true || payload["task_complete"] == true || payload["task_failed"] == true
|
|
20
|
+
|
|
21
|
+
activity = payload["activity"]
|
|
22
|
+
if activity.is_a?(Hash) && activity["status"] == "observed"
|
|
23
|
+
return nil unless activity["turn_active"] == true
|
|
24
|
+
|
|
25
|
+
idle = STRUCTURED_CLOCKS.filter_map { |key| non_negative(activity[key]) }.min
|
|
26
|
+
return idle && { "clock" => "activity", "idle_s" => idle }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
idle = non_negative(payload["log_idle_s"])
|
|
30
|
+
idle && { "clock" => "output_log", "idle_s" => idle }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def stalled(payload, threshold_s)
|
|
34
|
+
progress = progress_idle(payload)
|
|
35
|
+
return nil unless progress && progress["idle_s"] >= threshold_s
|
|
36
|
+
|
|
37
|
+
progress.merge("threshold_s" => threshold_s.to_f)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def non_negative(value)
|
|
41
|
+
return nil unless value.is_a?(Numeric) && value.finite?
|
|
42
|
+
|
|
43
|
+
[value.to_f, 0.0].max
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -37,9 +37,9 @@ module Harnex
|
|
|
37
37
|
|
|
38
38
|
private
|
|
39
39
|
|
|
40
|
-
def analyze
|
|
40
|
+
def analyze(canonical_locked: false)
|
|
41
41
|
reset_diagnostics
|
|
42
|
-
canonical = load_canonical(@canonical)
|
|
42
|
+
canonical = load_canonical(@canonical, locked: canonical_locked)
|
|
43
43
|
sources = discover_sources(@sources, @canonical)
|
|
44
44
|
source_rows = sources[:rows]
|
|
45
45
|
conflict_rows = sources[:conflicts]
|
|
@@ -115,7 +115,7 @@ module Harnex
|
|
|
115
115
|
|
|
116
116
|
def writable?(state) = state[:fatal].empty? && state[:conflict_rows].empty? && state[:missing_rows].any?
|
|
117
117
|
|
|
118
|
-
def load_canonical(path)
|
|
118
|
+
def load_canonical(path, locked: false)
|
|
119
119
|
rows = []
|
|
120
120
|
fatal = []
|
|
121
121
|
families = FAMILY_KEYS.to_h { |key| [key, 0] }
|
|
@@ -123,7 +123,19 @@ module Harnex
|
|
|
123
123
|
ends = {}
|
|
124
124
|
recoverable = []
|
|
125
125
|
|
|
126
|
-
|
|
126
|
+
begin
|
|
127
|
+
entries = read_jsonl(path, complete: true, locked: locked)
|
|
128
|
+
rescue DispatchHistory::HistoryUnavailable => e
|
|
129
|
+
return {
|
|
130
|
+
rows: 0,
|
|
131
|
+
families: families,
|
|
132
|
+
recoverable: [],
|
|
133
|
+
fatal: [e.message],
|
|
134
|
+
open_starts: 0
|
|
135
|
+
}
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
entries.each do |entry|
|
|
127
139
|
if entry[:error]
|
|
128
140
|
fatal << "malformed canonical JSON at #{path}:#{entry[:line]}"
|
|
129
141
|
next
|
|
@@ -281,18 +293,27 @@ module Harnex
|
|
|
281
293
|
records.each_with_index.map { |record, index| { record: record, line: index + 1, path: path } }
|
|
282
294
|
end
|
|
283
295
|
|
|
284
|
-
def read_jsonl(path)
|
|
285
|
-
return [] unless File.file?(path)
|
|
296
|
+
def read_jsonl(path, complete: false, locked: false)
|
|
297
|
+
return [] unless File.file?(path) || complete
|
|
286
298
|
|
|
287
|
-
|
|
299
|
+
entries = []
|
|
300
|
+
index = 0
|
|
301
|
+
consume = proc do |line|
|
|
302
|
+
index += 1
|
|
288
303
|
next if line.strip.empty?
|
|
289
304
|
|
|
290
305
|
begin
|
|
291
|
-
{ record: JSON.parse(line), line: index
|
|
306
|
+
entries << { record: JSON.parse(line), line: index, path: path }
|
|
292
307
|
rescue JSON::ParserError => error
|
|
293
|
-
{ error: error, line: index
|
|
308
|
+
entries << { error: error, line: index, path: path }
|
|
294
309
|
end
|
|
295
310
|
end
|
|
311
|
+
if complete
|
|
312
|
+
DispatchHistory.each_history_line(path, locked: locked, &consume)
|
|
313
|
+
else
|
|
314
|
+
File.foreach(path, &consume)
|
|
315
|
+
end
|
|
316
|
+
entries
|
|
296
317
|
end
|
|
297
318
|
|
|
298
319
|
def canonical_family(record)
|
|
@@ -410,23 +431,22 @@ module Harnex
|
|
|
410
431
|
|
|
411
432
|
def append_missing_under_lock(rows)
|
|
412
433
|
FileUtils.mkdir_p(File.dirname(@canonical))
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
file.rewind
|
|
416
|
-
existing = file.read
|
|
417
|
-
locked = analyze
|
|
434
|
+
DispatchHistory.with_lock(@canonical) do
|
|
435
|
+
locked = analyze(canonical_locked: true)
|
|
418
436
|
return [0, locked] unless writable?(locked)
|
|
419
437
|
|
|
420
438
|
still_missing = locked[:missing_rows].select { |row| rows.any? { |wanted| wanted.identity == row.identity } }
|
|
421
|
-
payload = still_missing.map { |row| JSON.generate(row.record) }.join("\n")
|
|
439
|
+
payload = still_missing.map { |row| JSON.generate(DispatchHistory.portable_record(row.record)) }.join("\n")
|
|
440
|
+
existing = File.file?(@canonical) ? File.binread(@canonical) : ""
|
|
422
441
|
payload = "\n#{payload}" unless existing.empty? || existing.end_with?("\n")
|
|
423
442
|
payload = "#{payload}\n" unless payload.empty?
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
443
|
+
DispatchHistory.prepare_archive(@canonical, payload.bytesize)
|
|
444
|
+
File.open(@canonical, File::WRONLY | File::APPEND | File::CREAT, 0o644) do |file|
|
|
445
|
+
file.write(payload)
|
|
446
|
+
file.flush
|
|
447
|
+
file.fsync
|
|
448
|
+
end
|
|
427
449
|
[still_missing.length, locked.merge(missing_rows: [])]
|
|
428
|
-
ensure
|
|
429
|
-
file.flock(File::LOCK_UN) unless file.closed?
|
|
430
450
|
end
|
|
431
451
|
end
|
|
432
452
|
|
|
@@ -459,6 +479,8 @@ module Harnex
|
|
|
459
479
|
end
|
|
460
480
|
when Array
|
|
461
481
|
value.map { |item| deep_normalize(item) }
|
|
482
|
+
when String
|
|
483
|
+
DispatchHistory.portable_path(value)
|
|
462
484
|
else
|
|
463
485
|
value
|
|
464
486
|
end
|
|
@@ -64,8 +64,7 @@ module Harnex
|
|
|
64
64
|
summary_record = nil
|
|
65
65
|
history_record = nil
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
record = JSON.parse(line)
|
|
67
|
+
DispatchHistory.each_record(path) do |record|
|
|
69
68
|
next unless record.is_a?(Hash)
|
|
70
69
|
next if DispatchHistory.start_record?(record)
|
|
71
70
|
|
|
@@ -83,8 +82,6 @@ module Harnex
|
|
|
83
82
|
elsif history_record?(record) && record["id"].to_s == id
|
|
84
83
|
history_record = record
|
|
85
84
|
end
|
|
86
|
-
rescue JSON::ParserError
|
|
87
|
-
next
|
|
88
85
|
end
|
|
89
86
|
|
|
90
87
|
[summary_record, history_record]
|
|
@@ -139,7 +136,7 @@ module Harnex
|
|
|
139
136
|
terminal = state != "unknown"
|
|
140
137
|
{
|
|
141
138
|
"id" => meta["id"].to_s,
|
|
142
|
-
"repo_root" => meta["repo"] || fallback_repo_root,
|
|
139
|
+
"repo_root" => DispatchHistory.expand_portable_path(meta["repo"] || fallback_repo_root),
|
|
143
140
|
"state" => state,
|
|
144
141
|
"process_state" => Harnex.process_state_for(state, terminal: terminal),
|
|
145
142
|
"terminal" => terminal,
|
|
@@ -149,6 +146,9 @@ module Harnex
|
|
|
149
146
|
"work_state" => Harnex.work_state_for(state, task_complete: task_complete),
|
|
150
147
|
"outcome_class" => blank_to_nil(outcome["class"]),
|
|
151
148
|
"artifact_report_status" => blank_to_nil(outcome["report_status"]),
|
|
149
|
+
"activity" => record["activity"],
|
|
150
|
+
"stop" => record["stop"],
|
|
151
|
+
"runtime_budget" => record["runtime_budget"],
|
|
152
152
|
"exit" => blank_to_nil(actual["exit"]),
|
|
153
153
|
"exit_code" => actual["exit_code"],
|
|
154
154
|
"started_at" => meta["started_at"],
|
data/lib/harnex/version.rb
CHANGED
data/lib/harnex.rb
CHANGED
|
@@ -9,10 +9,16 @@ require_relative "harnex/config"
|
|
|
9
9
|
require_relative "harnex/retention"
|
|
10
10
|
require_relative "harnex/pricing"
|
|
11
11
|
require_relative "harnex/terminal_status"
|
|
12
|
+
require_relative "harnex/runtime/stop_request"
|
|
12
13
|
require_relative "harnex/artifact_report"
|
|
14
|
+
require_relative "harnex/context_file"
|
|
13
15
|
require_relative "harnex/context_telemetry"
|
|
14
16
|
require_relative "harnex/telemetry_reconciler"
|
|
15
17
|
require_relative "harnex/orchestration"
|
|
18
|
+
require_relative "harnex/stall"
|
|
19
|
+
require_relative "harnex/owner_stream"
|
|
20
|
+
require_relative "harnex/lanes"
|
|
21
|
+
require_relative "harnex/mcp"
|
|
16
22
|
require_relative "harnex/watcher"
|
|
17
23
|
require_relative "harnex/adapters"
|
|
18
24
|
require_relative "harnex/runtime/session_state"
|
|
@@ -20,6 +26,8 @@ require_relative "harnex/runtime/message"
|
|
|
20
26
|
require_relative "harnex/runtime/inbox"
|
|
21
27
|
require_relative "harnex/runtime/file_change_hook"
|
|
22
28
|
require_relative "harnex/runtime/completion_notifier"
|
|
29
|
+
require_relative "harnex/runtime/activity_tracker"
|
|
30
|
+
require_relative "harnex/runtime/runtime_budget"
|
|
23
31
|
require_relative "harnex/runtime/api_server"
|
|
24
32
|
require_relative "harnex/runtime/session"
|
|
25
33
|
require_relative "harnex/commands/watch"
|
|
@@ -37,6 +45,14 @@ require_relative "harnex/commands/recipes"
|
|
|
37
45
|
require_relative "harnex/commands/guide"
|
|
38
46
|
require_relative "harnex/commands/agents_guide"
|
|
39
47
|
require_relative "harnex/commands/doctor"
|
|
48
|
+
require_relative "harnex/commands/preflight"
|
|
49
|
+
require_relative "harnex/commands/ask"
|
|
50
|
+
require_relative "harnex/commands/delegate"
|
|
51
|
+
require_relative "harnex/commands/result"
|
|
52
|
+
require_relative "harnex/commands/reply"
|
|
53
|
+
require_relative "harnex/commands/land"
|
|
54
|
+
require_relative "harnex/commands/lanes"
|
|
55
|
+
require_relative "harnex/commands/mcp"
|
|
40
56
|
require_relative "harnex/commands/orchestration"
|
|
41
57
|
require_relative "harnex/commands/artifact_report"
|
|
42
58
|
require_relative "harnex/commands/telemetry"
|
|
@@ -11,7 +11,7 @@ repeating this pattern with file handoffs between steps.
|
|
|
11
11
|
### 1. Spawn the worker
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
harnex run
|
|
14
|
+
harnex run pi --id pi-i-23 --tmux pi-i-23
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
### 2. Send the task
|
|
@@ -19,22 +19,22 @@ harnex run codex --id cx-23 --tmux
|
|
|
19
19
|
If the plan file is self-contained, reference it directly:
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
harnex send --id
|
|
23
|
-
harnex wait --id
|
|
22
|
+
harnex send --id pi-i-23 --message "Implement koder/plans/plan_23.md. Run tests when done."
|
|
23
|
+
harnex wait --id pi-i-23 --until done --timeout 1200
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
For tasks that need structured output, tell the worker to write a
|
|
27
27
|
file and inspect the screen separately:
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
|
-
cat > /tmp/task-
|
|
30
|
+
cat > /tmp/task-impl-23.md <<'EOF'
|
|
31
31
|
Implement koder/plans/plan_23.md.
|
|
32
32
|
Run the full test suite when done.
|
|
33
33
|
Write a short summary to /tmp/impl-23.md.
|
|
34
34
|
EOF
|
|
35
35
|
|
|
36
|
-
harnex send --id
|
|
37
|
-
harnex wait --id
|
|
36
|
+
harnex send --id pi-i-23 --message "Read and execute /tmp/task-impl-23.md"
|
|
37
|
+
harnex wait --id pi-i-23 --until done --timeout 1200
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
### 3. Watch until done
|
|
@@ -42,25 +42,25 @@ harnex wait --id cx-23 --until done --timeout 1200
|
|
|
42
42
|
Use `harnex wait --until done` as the work-level fence, then read the worker's screen:
|
|
43
43
|
|
|
44
44
|
```bash
|
|
45
|
-
harnex pane --id
|
|
45
|
+
harnex pane --id pi-i-23 --lines 25
|
|
46
46
|
```
|
|
47
47
|
|
|
48
48
|
If you prefer to watch while it runs:
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
|
-
harnex pane --id
|
|
51
|
+
harnex pane --id pi-i-23 --follow
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
When the worker looks idle, capture a larger snapshot:
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
|
-
harnex pane --id
|
|
57
|
+
harnex pane --id pi-i-23 --lines 80
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
### 4. Stop the worker
|
|
61
61
|
|
|
62
62
|
```bash
|
|
63
|
-
harnex stop --id
|
|
63
|
+
harnex stop --id pi-i-23
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
Start a fresh worker for the next step instead of reusing this one.
|
|
@@ -69,10 +69,10 @@ Start a fresh worker for the next step instead of reusing this one.
|
|
|
69
69
|
|
|
70
70
|
Use the same pattern for every role:
|
|
71
71
|
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
- Claude review: "Review current changes against `/tmp/plan-23.md` and write findings to `/tmp/review-23.md`."
|
|
75
|
-
-
|
|
72
|
+
- Pi planning: "Write the plan to `/tmp/plan-23.md`. Do not change code."
|
|
73
|
+
- Pi implementation: "Read `/tmp/plan-23.md`, implement it, run tests, write `/tmp/impl-23.md`."
|
|
74
|
+
- Claude (or fresh Pi) review: "Review current changes against `/tmp/plan-23.md` and write findings to `/tmp/review-23.md`."
|
|
75
|
+
- Pi fix: "Read `/tmp/review-23.md`, fix the issues, run tests, write `/tmp/fix-23.md`."
|
|
76
76
|
|
|
77
77
|
## Rationale
|
|
78
78
|
|
|
@@ -5,16 +5,16 @@ This is repeated fire-and-watch for a batch of jobs.
|
|
|
5
5
|
Process plans in series. For each plan, use fresh instances for
|
|
6
6
|
every step:
|
|
7
7
|
|
|
8
|
-
-
|
|
9
|
-
- Claude reviews
|
|
10
|
-
-
|
|
8
|
+
- Pi plans or implements
|
|
9
|
+
- Claude (or a fresh Pi lane) reviews
|
|
10
|
+
- Pi fixes
|
|
11
11
|
- Repeat review and fix until clean, then move to the next plan
|
|
12
12
|
|
|
13
13
|
## Trigger
|
|
14
14
|
|
|
15
15
|
User says something like:
|
|
16
16
|
- "implement plans 23 to 27, review each, fix issues"
|
|
17
|
-
- "implement plans 23 to 27, use fresh
|
|
17
|
+
- "implement plans 23 to 27, use fresh Pi for implementation
|
|
18
18
|
and fresh Claude for review on every plan"
|
|
19
19
|
|
|
20
20
|
## Procedure
|
|
@@ -24,55 +24,58 @@ For each plan in the batch:
|
|
|
24
24
|
### Step 1: Plan or confirm the plan
|
|
25
25
|
|
|
26
26
|
If the plan file does not already exist or needs refinement, spawn a
|
|
27
|
-
fresh
|
|
27
|
+
fresh Pi planner and tell it to write a plan artifact.
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
|
-
harnex run
|
|
31
|
-
harnex send --id
|
|
30
|
+
harnex run pi --id pi-p-23 --tmux pi-p-23
|
|
31
|
+
harnex send --id pi-p-23 --message "Write a concrete implementation plan for plan 23 to /tmp/plan-23.md. Do not change code." --wait-for-idle --timeout 600
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
Inspect with `harnex pane --id
|
|
34
|
+
Inspect with `harnex pane --id pi-p-23 --lines 60`, then stop it.
|
|
35
35
|
|
|
36
36
|
If the plan is already written and trusted, skip this step and use the
|
|
37
37
|
existing plan file directly.
|
|
38
38
|
|
|
39
39
|
### Step 2: Implement
|
|
40
40
|
|
|
41
|
-
Spawn a fresh
|
|
41
|
+
Spawn a fresh Pi worker, send it the plan, watch it, stop it.
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
|
-
harnex run
|
|
45
|
-
harnex send --id
|
|
44
|
+
harnex run pi --id pi-i-23-r1 --tmux pi-i-23-r1
|
|
45
|
+
harnex send --id pi-i-23-r1 --message "Read /tmp/plan-23.md, implement it, run tests, and write a summary to /tmp/impl-23-r1.md." --wait-for-idle --timeout 1200
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
Inspect with `harnex pane --id
|
|
48
|
+
Inspect with `harnex pane --id pi-i-23-r1 --lines 80`, then stop it.
|
|
49
49
|
|
|
50
50
|
### Step 3: Review
|
|
51
51
|
|
|
52
|
-
Spawn a fresh
|
|
53
|
-
recipe.
|
|
52
|
+
Spawn a fresh reviewer: a Claude session, or a fresh Pi lane that has
|
|
53
|
+
not seen the implementation. Reviewers only review in this recipe.
|
|
54
54
|
|
|
55
55
|
```bash
|
|
56
|
-
harnex run claude --id cl-
|
|
57
|
-
harnex send --id cl-
|
|
56
|
+
harnex run claude --id cl-r-23-r1 --tmux cl-r-23-r1
|
|
57
|
+
harnex send --id cl-r-23-r1 --message "Review the current changes against /tmp/plan-23.md. Write findings to /tmp/review-23-r1.md. If there are no issues, say clean." --wait-for-idle --timeout 900
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
For a Pi reviewer, use `harnex run pi --id pi-r-23-r1 --tmux pi-r-23-r1`
|
|
61
|
+
with the same message.
|
|
62
|
+
|
|
63
|
+
Inspect with `harnex pane --id cl-r-23-r1 --lines 80`, then stop it.
|
|
61
64
|
|
|
62
65
|
### Step 4: Fix and repeat if needed
|
|
63
66
|
|
|
64
67
|
If the review is clean, move to the next plan.
|
|
65
68
|
|
|
66
|
-
If the review finds issues, spawn a fresh
|
|
69
|
+
If the review finds issues, spawn a fresh Pi fixer:
|
|
67
70
|
|
|
68
71
|
```bash
|
|
69
|
-
harnex run
|
|
70
|
-
harnex send --id
|
|
72
|
+
harnex run pi --id pi-f-23-r1 --tmux pi-f-23-r1
|
|
73
|
+
harnex send --id pi-f-23-r1 --message "Read /tmp/review-23-r1.md, fix every issue, run tests, and write a summary to /tmp/fix-23-r1.md." --wait-for-idle --timeout 1200
|
|
71
74
|
```
|
|
72
75
|
|
|
73
|
-
Inspect with `harnex pane --id
|
|
76
|
+
Inspect with `harnex pane --id pi-f-23-r1 --lines 80`, then stop it.
|
|
74
77
|
|
|
75
|
-
Then spawn another fresh
|
|
78
|
+
Then spawn another fresh reviewer and repeat the review and fix
|
|
76
79
|
loop until clean or until you decide the plan needs manual attention.
|
|
77
80
|
|
|
78
81
|
### Step 5: Next plan
|
|
@@ -84,12 +87,12 @@ Move to plan 24. Repeat from step 1.
|
|
|
84
87
|
Use the plan number in every worker ID so you can tell them apart in
|
|
85
88
|
`harnex status`:
|
|
86
89
|
|
|
87
|
-
| Step | ID pattern
|
|
88
|
-
| --- | ---
|
|
89
|
-
| Plan | `
|
|
90
|
-
| Implement | `
|
|
91
|
-
| Review | `cl-
|
|
92
|
-
| Fix | `
|
|
90
|
+
| Step | ID pattern | Example |
|
|
91
|
+
| --- | --- | --- |
|
|
92
|
+
| Plan | `pi-p-NN` | `pi-p-23` |
|
|
93
|
+
| Implement | `pi-i-NN-rM` | `pi-i-23-r1` |
|
|
94
|
+
| Review | `cl-r-NN-rM` / `pi-r-NN-rM` | `cl-r-23-r1` |
|
|
95
|
+
| Fix | `pi-f-NN-rM` | `pi-f-23-r1` |
|
|
93
96
|
|
|
94
97
|
## Notes
|
|
95
98
|
|
|
@@ -98,8 +101,9 @@ Use the plan number in every worker ID so you can tell them apart in
|
|
|
98
101
|
implement, review, and fix.
|
|
99
102
|
- The batch is serial. Finish plan 23 completely before starting
|
|
100
103
|
plan 24. Plans often build on each other.
|
|
101
|
-
-
|
|
102
|
-
this recipe
|
|
104
|
+
- The reviewer only reviews. Do not use Claude as the planner or
|
|
105
|
+
fixer in this recipe, and never let the implementing Pi lane review
|
|
106
|
+
its own work.
|
|
103
107
|
- Pass artifacts between steps as files (`/tmp/plan-23.md`,
|
|
104
108
|
`/tmp/review-23-r1.md`), not as harnex reply messages.
|
|
105
109
|
- If the review finds no issues, skip the fix step and move on.
|
|
@@ -111,5 +115,5 @@ fire-and-watch repeated with stronger discipline:
|
|
|
111
115
|
|
|
112
116
|
- one worker per step
|
|
113
117
|
- one artifact per handoff
|
|
114
|
-
- one reviewer role, always Claude
|
|
118
|
+
- one reviewer role, always a fresh Claude or Pi lane
|
|
115
119
|
- one serial job stream, so later plans see earlier fixes
|
data/recipes/03_buddy.md
CHANGED
|
@@ -22,7 +22,7 @@ session using existing primitives.
|
|
|
22
22
|
### 1. Spawn the worker
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
harnex run
|
|
25
|
+
harnex run pi --id worker-42 --tmux worker-42
|
|
26
26
|
harnex send --id worker-42 --message "Read and execute /tmp/task-42.md"
|
|
27
27
|
```
|
|
28
28
|
|
metadata
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: harnex
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.29.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jikku Jose
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
|
-
description:
|
|
14
|
-
|
|
13
|
+
description: 'Hand questions and tasks to Pi workers in visible tmux windows and get
|
|
14
|
+
the result back in one call: lanes in git worktrees, follow-ups that keep context,
|
|
15
|
+
an MCP server for Claude Code and Pi, and a local harness for any terminal agent.'
|
|
15
16
|
email:
|
|
16
17
|
- jikkujose@gmail.com
|
|
17
18
|
executables:
|
|
@@ -24,40 +25,50 @@ files:
|
|
|
24
25
|
- LICENSE
|
|
25
26
|
- README.md
|
|
26
27
|
- TECHNICAL.md
|
|
28
|
+
- agents/gpt.md
|
|
27
29
|
- bin/gem-push
|
|
28
30
|
- bin/harnex
|
|
29
|
-
- docs/
|
|
31
|
+
- docs/agents.md
|
|
30
32
|
- docs/configuration.md
|
|
31
33
|
- docs/dispatch-telemetry.md
|
|
32
34
|
- docs/events.md
|
|
35
|
+
- docs/lanes.md
|
|
36
|
+
- docs/mcp.md
|
|
33
37
|
- docs/pi-rpc.md
|
|
38
|
+
- docs/upgrading-to-0.29.md
|
|
34
39
|
- guides/01_dispatch.md
|
|
35
40
|
- guides/02_chain.md
|
|
36
41
|
- guides/03_buddy.md
|
|
37
42
|
- guides/04_monitoring.md
|
|
38
43
|
- guides/05_naming.md
|
|
44
|
+
- guides/06_claude.md
|
|
39
45
|
- lib/harnex.rb
|
|
40
46
|
- lib/harnex/adapters.rb
|
|
41
47
|
- lib/harnex/adapters/base.rb
|
|
42
48
|
- lib/harnex/adapters/claude.rb
|
|
43
|
-
- lib/harnex/adapters/codex.rb
|
|
44
|
-
- lib/harnex/adapters/codex_appserver.rb
|
|
45
49
|
- lib/harnex/adapters/generic.rb
|
|
46
50
|
- lib/harnex/adapters/opencode.rb
|
|
47
51
|
- lib/harnex/adapters/pi.rb
|
|
48
52
|
- lib/harnex/artifact_report.rb
|
|
49
53
|
- lib/harnex/cli.rb
|
|
50
|
-
- lib/harnex/codex/app_server/client.rb
|
|
51
54
|
- lib/harnex/commands/agents_guide.rb
|
|
52
55
|
- lib/harnex/commands/artifact_report.rb
|
|
56
|
+
- lib/harnex/commands/ask.rb
|
|
57
|
+
- lib/harnex/commands/delegate.rb
|
|
53
58
|
- lib/harnex/commands/doctor.rb
|
|
54
59
|
- lib/harnex/commands/events.rb
|
|
55
60
|
- lib/harnex/commands/guide.rb
|
|
56
61
|
- lib/harnex/commands/history.rb
|
|
62
|
+
- lib/harnex/commands/land.rb
|
|
63
|
+
- lib/harnex/commands/lanes.rb
|
|
57
64
|
- lib/harnex/commands/logs.rb
|
|
65
|
+
- lib/harnex/commands/mcp.rb
|
|
58
66
|
- lib/harnex/commands/orchestration.rb
|
|
59
67
|
- lib/harnex/commands/pane.rb
|
|
68
|
+
- lib/harnex/commands/preflight.rb
|
|
60
69
|
- lib/harnex/commands/recipes.rb
|
|
70
|
+
- lib/harnex/commands/reply.rb
|
|
71
|
+
- lib/harnex/commands/result.rb
|
|
61
72
|
- lib/harnex/commands/run.rb
|
|
62
73
|
- lib/harnex/commands/send.rb
|
|
63
74
|
- lib/harnex/commands/status.rb
|
|
@@ -67,19 +78,28 @@ files:
|
|
|
67
78
|
- lib/harnex/commands/watch.rb
|
|
68
79
|
- lib/harnex/commands/watch_presets.rb
|
|
69
80
|
- lib/harnex/config.rb
|
|
81
|
+
- lib/harnex/context_file.rb
|
|
70
82
|
- lib/harnex/context_telemetry.rb
|
|
71
83
|
- lib/harnex/core.rb
|
|
72
84
|
- lib/harnex/dispatch_history.rb
|
|
85
|
+
- lib/harnex/lanes.rb
|
|
86
|
+
- lib/harnex/mcp.rb
|
|
73
87
|
- lib/harnex/orchestration.rb
|
|
88
|
+
- lib/harnex/owned_process_group.rb
|
|
89
|
+
- lib/harnex/owner_stream.rb
|
|
74
90
|
- lib/harnex/pricing.rb
|
|
75
91
|
- lib/harnex/retention.rb
|
|
92
|
+
- lib/harnex/runtime/activity_tracker.rb
|
|
76
93
|
- lib/harnex/runtime/api_server.rb
|
|
77
94
|
- lib/harnex/runtime/completion_notifier.rb
|
|
78
95
|
- lib/harnex/runtime/file_change_hook.rb
|
|
79
96
|
- lib/harnex/runtime/inbox.rb
|
|
80
97
|
- lib/harnex/runtime/message.rb
|
|
98
|
+
- lib/harnex/runtime/runtime_budget.rb
|
|
81
99
|
- lib/harnex/runtime/session.rb
|
|
82
100
|
- lib/harnex/runtime/session_state.rb
|
|
101
|
+
- lib/harnex/runtime/stop_request.rb
|
|
102
|
+
- lib/harnex/stall.rb
|
|
83
103
|
- lib/harnex/telemetry_reconciler.rb
|
|
84
104
|
- lib/harnex/terminal_status.rb
|
|
85
105
|
- lib/harnex/version.rb
|
|
@@ -96,7 +116,9 @@ metadata:
|
|
|
96
116
|
homepage_uri: https://github.com/jikkuatwork/harnex
|
|
97
117
|
source_code_uri: https://github.com/jikkuatwork/harnex
|
|
98
118
|
bug_tracker_uri: https://github.com/jikkuatwork/harnex/issues
|
|
99
|
-
|
|
119
|
+
documentation_uri: https://harnex.zyt.app
|
|
120
|
+
changelog_uri: https://github.com/jikkuatwork/harnex/blob/main/CHANGELOG.md
|
|
121
|
+
post_install_message:
|
|
100
122
|
rdoc_options: []
|
|
101
123
|
require_paths:
|
|
102
124
|
- lib
|
|
@@ -112,7 +134,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
112
134
|
version: '0'
|
|
113
135
|
requirements: []
|
|
114
136
|
rubygems_version: 3.5.3
|
|
115
|
-
signing_key:
|
|
137
|
+
signing_key:
|
|
116
138
|
specification_version: 4
|
|
117
|
-
summary:
|
|
139
|
+
summary: Pi workers (GPT models) as tools for you and your coding agents
|
|
118
140
|
test_files: []
|