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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +592 -0
  3. data/GUIDE.md +222 -68
  4. data/README.md +78 -48
  5. data/TECHNICAL.md +235 -98
  6. data/agents/gpt.md +60 -0
  7. data/docs/agents.md +119 -0
  8. data/docs/dispatch-telemetry.md +184 -50
  9. data/docs/events.md +20 -7
  10. data/docs/lanes.md +202 -0
  11. data/docs/mcp.md +102 -0
  12. data/docs/pi-rpc.md +81 -7
  13. data/docs/upgrading-to-0.29.md +87 -0
  14. data/guides/01_dispatch.md +101 -34
  15. data/guides/04_monitoring.md +223 -25
  16. data/guides/05_naming.md +0 -1
  17. data/guides/06_claude.md +96 -0
  18. data/lib/harnex/adapters/base.rb +4 -8
  19. data/lib/harnex/adapters/pi.rb +92 -12
  20. data/lib/harnex/adapters.rb +1 -16
  21. data/lib/harnex/artifact_report.rb +206 -12
  22. data/lib/harnex/cli.rb +68 -50
  23. data/lib/harnex/commands/ask.rb +278 -0
  24. data/lib/harnex/commands/delegate.rb +202 -0
  25. data/lib/harnex/commands/doctor.rb +34 -36
  26. data/lib/harnex/commands/events.rb +3 -3
  27. data/lib/harnex/commands/history.rb +2 -8
  28. data/lib/harnex/commands/land.rb +150 -0
  29. data/lib/harnex/commands/lanes.rb +84 -0
  30. data/lib/harnex/commands/logs.rb +3 -3
  31. data/lib/harnex/commands/mcp.rb +208 -0
  32. data/lib/harnex/commands/orchestration.rb +88 -9
  33. data/lib/harnex/commands/pane.rb +3 -3
  34. data/lib/harnex/commands/preflight.rb +566 -0
  35. data/lib/harnex/commands/reply.rb +125 -0
  36. data/lib/harnex/commands/result.rb +108 -0
  37. data/lib/harnex/commands/run.rb +248 -64
  38. data/lib/harnex/commands/send.rb +121 -16
  39. data/lib/harnex/commands/status.rb +32 -4
  40. data/lib/harnex/commands/stop.rb +14 -4
  41. data/lib/harnex/commands/telemetry.rb +19 -1
  42. data/lib/harnex/commands/wait.rb +421 -56
  43. data/lib/harnex/commands/watch.rb +134 -21
  44. data/lib/harnex/context_file.rb +85 -0
  45. data/lib/harnex/core.rb +100 -15
  46. data/lib/harnex/dispatch_history.rb +157 -10
  47. data/lib/harnex/lanes.rb +513 -0
  48. data/lib/harnex/mcp.rb +362 -0
  49. data/lib/harnex/orchestration.rb +128 -5
  50. data/lib/harnex/owned_process_group.rb +50 -0
  51. data/lib/harnex/owner_stream.rb +399 -0
  52. data/lib/harnex/pricing.rb +4 -9
  53. data/lib/harnex/runtime/activity_tracker.rb +107 -0
  54. data/lib/harnex/runtime/api_server.rb +11 -1
  55. data/lib/harnex/runtime/completion_notifier.rb +90 -11
  56. data/lib/harnex/runtime/inbox.rb +26 -7
  57. data/lib/harnex/runtime/message.rb +5 -2
  58. data/lib/harnex/runtime/runtime_budget.rb +96 -0
  59. data/lib/harnex/runtime/session.rb +684 -294
  60. data/lib/harnex/runtime/stop_request.rb +39 -0
  61. data/lib/harnex/stall.rb +46 -0
  62. data/lib/harnex/telemetry_reconciler.rb +42 -20
  63. data/lib/harnex/terminal_status.rb +5 -5
  64. data/lib/harnex/version.rb +2 -2
  65. data/lib/harnex.rb +16 -0
  66. data/recipes/01_fire_and_watch.md +14 -14
  67. data/recipes/02_chain_implement.md +35 -31
  68. data/recipes/03_buddy.md +1 -1
  69. metadata +34 -12
  70. data/docs/codex-appserver.md +0 -175
  71. data/lib/harnex/adapters/codex.rb +0 -160
  72. data/lib/harnex/adapters/codex_appserver.rb +0 -407
  73. data/lib/harnex/codex/app_server/client.rb +0 -347
data/TECHNICAL.md CHANGED
@@ -6,12 +6,42 @@ and internals.
6
6
 
7
7
  ## Commands
8
8
 
9
+ ### Lanes — `ask`, `delegate`, `result`, `reply`, `land`, `discard`, `lanes`
10
+
11
+ The agent-facing layer. Each verb prints plain text (the worker's reply, then
12
+ a `── NAME:` footer with state, model, cost, commits and next commands) and
13
+ shares exit codes: 0 done, 1 failed, 2 usage error, 124 still working.
14
+ Blocking verbs default to `--timeout 9m`. See [docs/lanes.md](docs/lanes.md)
15
+ and `harnex help <verb>`.
16
+
17
+ ```bash
18
+ harnex ask "QUESTION" [--model M] [--write] [--fallback-model M]
19
+ harnex delegate [--worktree NAME | --name NAME] [--no-wait] [--model M] "TASK"
20
+ harnex result [NAME...]
21
+ harnex reply NAME "MESSAGE" [--model M]
22
+ harnex land [NAME...]
23
+ harnex discard NAME...
24
+ harnex lanes [--all] [--closed] [--json]
25
+ ```
26
+
27
+ ### `harnex mcp` — The lane verbs as MCP tools
28
+
29
+ ```bash
30
+ harnex mcp install [--claude] [--pi] [--no-agent] [--dry-run]
31
+ harnex mcp status
32
+ harnex mcp uninstall
33
+ harnex mcp # stdio server (what clients run)
34
+ ```
35
+
36
+ See [docs/mcp.md](docs/mcp.md).
37
+
9
38
  ### `harnex run` — Start an agent
10
39
 
11
40
  ```bash
12
- harnex run codex
13
- harnex run claude --id review
14
- harnex run codex -- --cd ~/other/repo
41
+ harnex run pi
42
+ harnex run pi --id pi-i-13 --tmux pi-i-13 --model openai/gpt-5.1
43
+ harnex run pi --cwd ../wt-13 --root . -- --no-approve
44
+ harnex run claude --id cl-r-13 --tmux cl-r-13
15
45
  ```
16
46
 
17
47
  | Flag | What it does |
@@ -28,18 +58,32 @@ harnex run codex -- --cd ~/other/repo
28
58
  | `--preset NAME` | Watch preset (`impl`, `plan`, `gate`), requires `--watch` |
29
59
  | `--watch-file PATH` | Auto-send a file-change hook (`--watch PATH`/`--watch=PATH` legacy) |
30
60
  | `--context TXT` | Give the agent a task on startup |
61
+ | `--context-file PATH` | Read the startup task from a file (refuses an empty result) |
62
+ | `--context-section TXT` | With `--context-file`, send only the section starting at TXT |
31
63
  | `--auto-stop` | With `--context`, stop after accepted task completion |
64
+ | `--wait-done` | With `--tmux`/`--detach`, block and print the first turn's result |
65
+ | `--wait-timeout DUR` | Cap `--wait-done`; on 124 the output names a `resume` command |
66
+ | `--model NAME` | Pi model (`provider/model`), verified over RPC before the prompt |
67
+ | `--effort LEVEL` | Pi thinking level, verified over RPC before the prompt |
68
+ | `--cwd DIR` | Run the agent from DIR (e.g. a worktree lane) |
69
+ | `--root DIR` | Override root attribution without changing the child cwd |
70
+ | `--owner-tag TAG` | Own the dispatch as `<owner>/TAG` for `watch --owner --owner-tag` |
32
71
  | `--meta JSON` | Attach bounded caller metadata |
33
72
  | `--phase TEXT` | First-class phase attribution (optionally repo-allowlisted) |
34
73
  | `--artifact-report PATH` | Override the harness-authored v1 receipt destination |
35
74
  | `--require-artifact-report` | Compatibility strict flag; default receipt path is sufficient |
36
- | `--fast` | For Codex, use `service_tier="fast"` instead of default `flex` |
37
75
  | `--timeout SEC` | Wait budget for detached registration |
38
76
 
39
- Codex app-server auto-stop rejects completion with no structured command/tool
40
- activity or Git delta as `completed_no_activity`. Every transport gets a
41
- harness-authored observed-state receipt; optional worker claims never determine
42
- acceptance, and receipt-write failure is fail-closed.
77
+ A Pi turn is accepted when it settles. With `--require-artifact-report` it must
78
+ also show structured command/tool activity or a Git delta, or it is rejected as
79
+ `completed_no_activity`. PTY adapters stop on prompt return. Every transport
80
+ gets a harness-authored observed-state receipt; optional worker claims never
81
+ determine acceptance, and receipt-write failure is fail-closed.
82
+
83
+ `--wait-done` prints the launch fields plus `turn_result` (`outcome_class`,
84
+ `model`, `commits`, `turn_commits`, and `final_message`, the worker's last reply)
85
+ and uses the `wait --until done` exit codes. Pass Pi startup flags after `--`;
86
+ unattended runs should choose Pi project trust with `--approve` or `--no-approve`.
43
87
 
44
88
  ### `harnex doctor` — Preflight, drift, and retention
45
89
 
@@ -50,11 +94,24 @@ harnex doctor --prune --dry-run
50
94
  harnex doctor --prune
51
95
  ```
52
96
 
53
- Plain output includes adapter prerequisites and events/output/receipt retention status.
54
- `--sweep` adds read-only live-session/tmux drift diagnostics. `--prune --dry-run`
97
+ Output is JSON: the Pi RPC prerequisite (Pi >= 0.80.4; `--adapter pi|all`),
98
+ events/output/receipt retention status, and versioned `capabilities`. `--sweep`
99
+ adds read-only live-session/tmux drift diagnostics. `--prune --dry-run`
55
100
  previews age/size-cap deletions; `--prune` applies them while preserving current
56
101
  and live-session files. See [docs/configuration.md](docs/configuration.md).
57
102
 
103
+ ### `harnex preflight` — Prove an adapter end to end
104
+
105
+ ```bash
106
+ harnex preflight pi --cwd ~/repo -- --no-approve
107
+ harnex preflight pi --cwd ~/repo --commit-check --model openai/gpt-5.1 -- --no-approve
108
+ ```
109
+
110
+ Spends one real model turn (two with `--commit-check`, which commits in a
111
+ throwaway scratch repo) and prints a JSON verdict: registration, boot,
112
+ permission, task read, workspace write, command exit, receipt, claims,
113
+ completion, and stop. Exits 0 only when every required check passes.
114
+
58
115
  ### `harnex artifact-report` — Validate receipts and legacy proof
59
116
 
60
117
  ```bash
@@ -79,10 +136,11 @@ harnex send --id worker --message "implement plan A"
79
136
  | `--id ID` | Which agent to talk to |
80
137
  | `--message` | The message text |
81
138
  | `--wait-for-idle` | Block until agent finishes processing |
139
+ | `--wait-done` | Block until the turn settles; print its result |
82
140
  | `--repo PATH` | Resolve session from a repo root |
83
141
  | `--cli CLI` | Filter by CLI type |
84
- | `--submit-only` | Just press Enter (no new text) |
85
- | `--no-submit` | Type the text but don't press Enter |
142
+ | `--submit-only` | Just press Enter (PTY adapters only) |
143
+ | `--no-submit` | Type without Enter (PTY adapters only) |
86
144
  | `--force` | Send even if the agent looks busy |
87
145
  | `--no-wait` | Don't wait for delivery confirmation |
88
146
  | `--relay` | Force relay header formatting |
@@ -90,6 +148,11 @@ harnex send --id worker --message "implement plan A"
90
148
  | `--port` / `--token` | Send directly to a known API port |
91
149
  | `--timeout` | Overall wait budget |
92
150
 
151
+ A busy Pi worker takes the message at once as a follow-up
152
+ (`delivery: "follow_up"`) and runs it when the current turn ends;
153
+ `--force` steers it into the running turn instead. Other adapters queue
154
+ it until the agent is at a prompt.
155
+
93
156
  ### `harnex stop` — Ask an agent to stop
94
157
 
95
158
  ```bash
@@ -97,15 +160,17 @@ harnex stop --id worker
97
160
  harnex stop --id worker --timeout 5
98
161
  ```
99
162
 
100
- Sends the adapter-specific stop sequence. Retries transient
101
- failures for up to the given timeout.
163
+ Pi: aborts any running turn over RPC and terminates the
164
+ `pi --mode rpc` process. PTY adapters get their native exit
165
+ sequence (`/exit` for Claude, Ctrl+C for OpenCode). Retries
166
+ transient failures for up to the given timeout.
102
167
 
103
168
  ### `harnex status` — See running agents
104
169
 
105
170
  ```
106
- ID CLI PID PORT AGE IDLE STATE REPO DESC
107
- worker codex 12345 43123 36s 12s prompt ~/... -
108
- review claude 12346 43124 8s - busy ~/... -
171
+ ID CLI PID PORT AGE IDLE STATE REPO DESC
172
+ pi-i-13 pi 12345 43123 36s 12s prompt ~/... -
173
+ cl-r-13 claude 12346 43124 8s - busy ~/... -
109
174
  ```
110
175
 
111
176
  Text mode includes an `IDLE` column derived from `log_idle_s` (`-` means no
@@ -123,10 +188,45 @@ Use `--all` for all repos.
123
188
  ### `harnex wait` — Wait for an agent to finish
124
189
 
125
190
  ```bash
126
- harnex wait --id worker
127
- harnex wait --id worker --until prompt --timeout 300
191
+ harnex wait --id pi-i-13
192
+ harnex wait --id pi-i-13 --until done --timeout 900
193
+ harnex wait --id pi-i-13 --until done --after-seq 812 --timeout 9m
128
194
  ```
129
195
 
196
+ `--until done` is the work-level fence: 0 accepted, 1 failed, 2 completed but
197
+ proof rejected, 3 no such session, 4 stalled (`--stall-after`), 124 observer
198
+ timeout (the worker keeps running). `--after-seq` (from `harnex send` output)
199
+ waits for the turn that message started, not an earlier result.
200
+
201
+ ### `harnex watch` — Monitor dispatches
202
+
203
+ ```bash
204
+ harnex watch --id pi-i-13 --until done --max-wait 90m
205
+ harnex watch --owner --stall-after 25m
206
+ harnex watch --owner --once --timeout 9m --owner-tag sub-a
207
+ harnex watch --owner --until-idle --timeout 90m
208
+ ```
209
+
210
+ `--id` watches one existing `--tmux`/detached session with the `wait --until
211
+ done` exit codes and optional `--done-marker` / `--fail-marker` files.
212
+ `--owner` streams one JSON line (`harnex.owner_event.v1`) per change for every
213
+ dispatch the calling Claude Code or Pi session launched; results carry
214
+ `final_message`. `--once` prints what is ready and exits (124 when nothing is
215
+ new); `--until-idle` keeps printing until no lane is running. `--owner-tag`
216
+ narrows to lanes launched with `run --owner-tag`.
217
+
218
+ ### `harnex ask` — Ask a Pi worker one question
219
+
220
+ ```bash
221
+ harnex ask "Which module parses the dispatch rows, and is it tested?"
222
+ harnex ask --model openai/gpt-5.1 --file /tmp/review.md --timeout 9m
223
+ ```
224
+
225
+ Runs a Pi worker in a tmux window with read-only tools, prints its answer on
226
+ stdout, and stops it. `--write` allows edits and commands; `--json` prints the
227
+ full result. Exit codes: 0 answered, 1 failed, 2 proof rejected or usage
228
+ error, 124 timeout (the worker keeps running).
229
+
130
230
  ### `harnex history` — List completed dispatches
131
231
 
132
232
  ```bash
@@ -169,9 +269,11 @@ Use `--wait-for-idle` instead of separate send + sleep + wait:
169
269
 
170
270
  ```bash
171
271
  # Replaces: send → sleep 5 → wait --until prompt
172
- harnex send --id cx-1 --message "implement the plan" --wait-for-idle --timeout 600
272
+ harnex send --id pi-i-13 --message "implement the plan" --wait-for-idle --timeout 600
173
273
  ```
174
274
 
275
+ With Pi, `--wait-done` returns the result of the turn the message started.
276
+
175
277
  ### Agents talking to each other
176
278
 
177
279
  When one harnex agent sends to another, the receiver sees a
@@ -182,49 +284,56 @@ relay header automatically:
182
284
  implement A
183
285
  ```
184
286
 
185
- Messages queue when the agent is busy and auto-deliver when ready.
287
+ Messages to a busy agent queue (Pi: become follow-ups) and
288
+ auto-deliver when ready.
186
289
 
187
290
  ### Background agents
188
291
 
189
292
  **tmux** (observable):
190
293
 
191
294
  ```bash
192
- harnex run codex --id worker --tmux cx-w1
295
+ harnex run pi --id pi-i-13 --tmux pi-i-13
193
296
  ```
194
297
 
195
298
  **Headless** (no terminal):
196
299
 
197
300
  ```bash
198
- harnex run codex --id worker --detach
301
+ harnex run pi --id pi-i-13 --detach
199
302
  ```
200
303
 
201
304
  ### Supervisor pattern
202
305
 
203
- One agent manages others:
306
+ One agent manages others, one worktree lane per worker:
204
307
 
205
308
  ```bash
206
- harnex run codex --id impl-1 --tmux cx-p1 -- --cd ~/wt-a
207
- harnex run codex --id impl-2 --tmux cx-p2 -- --cd ~/wt-b
309
+ harnex run pi --id pi-i-1 --tmux pi-i-1 --cwd ~/wt-a --root ~/repo
310
+ harnex run pi --id pi-i-2 --tmux pi-i-2 --cwd ~/wt-b --root ~/repo
208
311
 
209
- harnex send --id impl-1 --message "implement feature A" --wait-for-idle --timeout 600
210
- harnex send --id impl-2 --message "implement feature B" --wait-for-idle --timeout 600
312
+ harnex send --id pi-i-1 --message "implement feature A" --wait-for-idle --timeout 600
313
+ harnex send --id pi-i-2 --message "implement feature B" --wait-for-idle --timeout 600
211
314
 
212
- harnex run claude --id review --tmux cl-r1
213
- harnex send --id review --message "review changes" --wait-for-idle --timeout 300
315
+ harnex run claude --id cl-r-1 --tmux cl-r-1
316
+ harnex send --id cl-r-1 --message "review changes" --wait-for-idle --timeout 300
214
317
  ```
215
318
 
319
+ With `--context ... --auto-stop` on each lane, a Claude Code or Pi supervisor
320
+ can collect every result with `harnex watch --owner --until-idle`.
321
+
216
322
  ### Context on startup
217
323
 
218
324
  ```bash
219
- harnex run codex --id impl-1 --tmux cx-p1 \
220
- --context "Implement the auth feature. Commit when done." \
221
- -- --cd ~/repo/worktree
325
+ harnex run pi --id pi-i-1 --tmux pi-i-1 --cwd ~/repo/worktree --root ~/repo \
326
+ --context "Implement the auth feature. Commit when done."
327
+
328
+ harnex run pi --id pi-i-1 --tmux pi-i-1 \
329
+ --context-file koder/briefs/1.md --context-section "## Task" \
330
+ --auto-stop --wait-done --wait-timeout 9m
222
331
  ```
223
332
 
224
333
  ### File watching
225
334
 
226
335
  ```bash
227
- harnex run codex --id worker --watch-file ./tmp/status.jsonl
336
+ harnex run pi --id pi-i-13 --watch-file ./tmp/status.jsonl
228
337
  ```
229
338
 
230
339
  Agent gets notified when the file changes. File doesn't need to
@@ -287,7 +396,7 @@ Schema details and compatibility guarantees are in [docs/events.md](docs/events.
287
396
  ├── Waiter block until session exits
288
397
  ├── Stopper send stop sequence
289
398
  │
290
- ├── Session PTY lifecycle, HTTP server, registry
399
+ ├── Session PTY or Pi RPC lifecycle, HTTP server, registry
291
400
  ├── SessionState state machine (prompt/busy/blocked)
292
401
  ├── Inbox per-session message queue
293
402
  ├── Message queued message struct
@@ -295,13 +404,55 @@ Schema details and compatibility guarantees are in [docs/events.md](docs/events.
295
404
  ├── FileChangeHook inotify file watcher
296
405
  └── LinuxInotify raw inotify via Fiddle
297
406
 
407
+ lib/harnex/adapters.rb registry: claude, opencode, pi; anything else → generic
298
408
  lib/harnex/adapters/
299
- ├── base.rb adapter interface
300
- ├── generic.rb fallback adapter for any CLI
301
- ├── codex.rb codex-specific behavior
302
- ├── opencode.rb opencode-specific behavior
303
- └── claude.rb claude-specific behavior
304
- ```
409
+ ├── base.rb adapter interface (PTY transport by default)
410
+ ├── claude.rb Claude Code PTY behavior
411
+ ├── generic.rb PTY fallback for any CLI
412
+ ├── opencode.rb OpenCode PTY behavior
413
+ └── pi.rb Pi JSONL RPC adapter (`pi --mode rpc`, default worker)
414
+ ```
415
+
416
+ ## Lanes and MCP
417
+
418
+ Lanes sit on top of sessions; they add a record, git plumbing and text output,
419
+ not a new runtime.
420
+
421
+ - **Records** (`lib/harnex/lanes.rb`) live in `STATE_DIR/lanes/<repo_key>--<name>.json`:
422
+ kind (`ask`/`delegate`), main-checkout root, run root, cwd, worktree, branch,
423
+ base sha, owner (`CLAUDE_CODE_SESSION_ID`/`PI_SESSION_ID`), requested model,
424
+ Pi `agent_session_id`, accumulated cost, launch time and the last turn's result.
425
+ States: `running` (a turn is out and uncollected), `done`, `failed`, `landed`,
426
+ `discarded`.
427
+ - **Launching:** the verbs shell out to the low-level CLI. `delegate` runs
428
+ `harnex run pi --id NAME --tmux NAME --cwd WORKDIR --root ROOT --auto-stop`
429
+ with a brief file (`--context-file`); with `--wait-done` it blocks on the
430
+ first result. `result` runs `harnex wait --until done --since LAUNCHED_AT`
431
+ (each relaunch restarts the events-log `seq`, so `--since` keeps an earlier
432
+ launch's result from matching) plus `--after-seq` for a follow-up sent to a
433
+ live lane.
434
+ - **Follow-ups:** `reply` sends to a live worker (`harnex send --wait-done`);
435
+ a finished lane is relaunched under the same id with `-- --session
436
+ <agent_session_id>`, so Pi resumes the conversation.
437
+ - **Settling:** after a turn, the lane reads the launch's `usage` event (cost,
438
+ `agent_session_id`) and records the result; the footer adds commits and a
439
+ diffstat from git (`base_sha..HEAD`).
440
+ - **Worktrees:** `.harnex/worktrees/NAME` on branch `harnex/NAME`; `.harnex/`
441
+ is added to `<git-common-dir>/info/exclude` on the first write.
442
+ `land` merges with `git merge --no-edit`, leaves conflicts in progress, and
443
+ removes the worktree and branch; lanes merged by hand are swept on the next
444
+ call.
445
+ - **Progress:** the Pi activity tracker records the current tool name and the
446
+ per-turn tool count (never arguments); `lanes` and MCP progress read them from
447
+ the session's `GET /status`.
448
+ - **MCP** (`lib/harnex/mcp.rb`): newline-delimited JSON-RPC 2.0 over stdio.
449
+ Each `tools/call` runs in its own thread and spawns the CLI verb in its own
450
+ process group (a cancel kills the wait, never the worker); stdout writes are
451
+ serialized; progress notifications go out every 15s when the call carries a
452
+ `progressToken`.
453
+ - **Worker guard:** the Pi adapter adds `--exclude-tools mcp__harnex__…` to
454
+ every worker (Pi >= 0.99) and `--no-mcp` when a `--tools` allowlist is set
455
+ (Pi >= 1.0.4), so workers cannot start workers.
305
456
 
306
457
  ## Session Lifecycle
307
458
 
@@ -311,12 +462,13 @@ preferred interactive mode because it keeps the peer's work observable.
311
462
 
312
463
  Headless/background execution (`--detach`) should still be treated as opt-in.
313
464
 
314
- When you run `harnex run codex --id worker`:
465
+ When you run `harnex run pi --id pi-i-13`:
315
466
 
316
467
  ```
317
468
  1. Parse wrapper options, build adapter
318
- 2. Validate the target binary before spawn
319
- 3. Spawn the agent CLI under a PTY (pseudoterminal)
469
+ 2. Validate the target binary before spawn (Pi: version >= 0.80.4)
470
+ 3. Spawn the agent: Pi as `pi --mode rpc` over stdio pipes;
471
+ other CLIs under a PTY (pseudoterminal)
320
472
  4. Generate a random bearer token for API auth
321
473
  5. Pick a port:
322
474
  hash(repo_root + id) % port_span + base_port
@@ -330,11 +482,12 @@ When you run `harnex run codex --id worker`:
330
482
  and open events file:
331
483
  ~/.local/state/harnex/events/<repo_hash>--<id>.jsonl
332
484
  8. Start background threads:
333
- - PTY reader (screen buffer)
334
- - State machine (adapter parses screen for state)
485
+ - PTY reader (screen buffer) or Pi JSONL reader (RPC events)
486
+ - State machine (adapter parses screen, or Pi events, for state)
335
487
  - Inbox delivery (queue -> inject when prompt)
336
- - File watcher (if --watch was given)
337
- 9. Relay terminal I/O between user and agent
488
+ - File watcher (if --watch-file was given)
489
+ 9. PTY: relay terminal I/O between user and agent;
490
+ Pi: send the --context prompt and print a synthesized transcript
338
491
  10. On exit: clean up registry, write exit status
339
492
  ```
340
493
 
@@ -351,6 +504,9 @@ The screen text includes raw terminal escape sequences. The
351
504
  adapter's `normalized_screen_text` strips ANSI and OSC codes
352
505
  before parsing.
353
506
 
507
+ Pi does not run under a PTY: its adapter speaks JSONL over stdio
508
+ and takes state from RPC events (see the Pi adapter below).
509
+
354
510
  ## Adapter Contract
355
511
 
356
512
  Each adapter in `lib/harnex/adapters/` implements:
@@ -363,50 +519,23 @@ Each adapter in `lib/harnex/adapters/` implements:
363
519
  | `inject_exit(writer)` | Send adapter-specific stop text |
364
520
  | `infer_repo_path(argv)` | Extract repo path from CLI args |
365
521
  | `wait_for_sendable(...)` | Wait strategy before sending |
366
- | `transport` | PTY, JSON-RPC, or JSONL RPC |
367
- | `validate_runtime!` | Optional version/capability gate |
522
+ | `transport` | `:pty` (PTY) or `:stdio_jsonl_rpc` (JSONL RPC) |
523
+ | `validate_runtime!` | Optional version gate (Pi >= 0.80.4) |
368
524
 
369
525
  ### Input States
370
526
 
371
- The adapter reads the screen and returns a state hash:
527
+ PTY adapters read the screen and return a state hash; Pi reports
528
+ `prompt`, `busy`, or `disconnected` from RPC events:
372
529
 
373
530
  | State | `input_ready` | Meaning |
374
531
  |--------------------------|---------------|-----------------------|
375
532
  | `prompt` | `true` | Ready for input |
376
- | `session` | `nil` | Agent is working |
533
+ | `busy` | `false` | Pi turn in progress |
377
534
  | `workspace-trust-prompt` | `false` | Needs Enter to confirm|
378
535
  | `confirmation` | `false` | Modal confirmation |
379
536
  | `unknown` | `nil` | Can't determine |
380
537
 
381
- ### Codex Adapter (default — JSON-RPC `app-server`)
382
-
383
- - `transport :stdio_jsonrpc` — speaks JSON-RPC 2.0 over the
384
- subprocess's stdin/stdout, one JSON object per line.
385
- - Launches `codex app-server` (Codex CLI ≥ 0.128.0; verify with
386
- `harnex doctor`).
387
- - Notifications (`turn/started`, `turn/completed`, `item/completed`,
388
- `error`, `thread/compacted`, …) fan into the events log.
389
- `task_complete` is the harnex-side event for `turn/completed`.
390
- - **Approval mediation**: Codex's app-server protocol delegates
391
- sandbox/approval decisions to the JSON-RPC client via server-to-
392
- client requests. Harnex auto-approves `applyPatchApproval`,
393
- `execCommandApproval`, `item/commandExecution/requestApproval`, and
394
- `item/fileChange/requestApproval`, so autonomous workers can run
395
- shell commands, write files, commit, and invoke package managers
396
- under Codex's default sandbox without any bypass flag. Other
397
- server-to-client requests (permissions, user-input, dynamic-tool,
398
- auth-refresh) currently fall through to `-32601` until use cases
399
- appear.
400
- - Disconnect is detected from JSON-RPC error responses, subprocess
401
- EOF, parse errors, or a server `error` notification — no screen
402
- regex required.
403
- - Synthesized transcript: `item/completed` text payloads stream to
404
- both the output log and STDOUT so tmux/pane workflows continue to
405
- work without a real PTY.
406
- - See `docs/codex-appserver.md` for the full mapping table and
407
- troubleshooting.
408
-
409
- ### Pi Adapter (JSONL RPC)
538
+ ### Pi Adapter (default — JSONL RPC)
410
539
 
411
540
  - `transport :stdio_jsonl_rpc` launches `pi --mode rpc` and speaks Pi's
412
541
  strict LF-delimited JSONL protocol. It is not JSON-RPC 2.0.
@@ -423,19 +552,17 @@ The adapter reads the screen and returns a state hash:
423
552
  - Harnex `--model` and `--effort` become Pi `--model` / `--thinking`
424
553
  startup flags and are verified with `get_state`; a model mismatch or clamped
425
554
  effort is rejected before prompting without persisting an RPC model switch
426
- into Pi's user defaults. Forced busy sends use `streamingBehavior=steer`.
555
+ into Pi's user defaults.
556
+ - Busy sends become Pi follow-ups (`streamingBehavior=followUp`), delivered
557
+ when the current turn ends; forced busy sends use `streamingBehavior=steer`.
558
+ - `task_complete` carries `final_message` (the last assistant reply, up to
559
+ 4,000 characters, full text at `final_message_path`).
560
+ - `--tmux` shows Harnex's synthesized RPC transcript, not Pi's native TUI.
427
561
  - RPC requests have a 30-second response bound. Stderr is continuously drained
428
562
  into a bounded diagnostic tail, and subprocess status comes from Open3's wait
429
563
  thread instead of racing it with another `waitpid`.
430
564
  - See `docs/pi-rpc.md` for trust policy, telemetry, and compatibility details.
431
565
 
432
- #### Codex Adapter (legacy PTY — `--legacy-pty`, long-term fallback)
433
-
434
- - Launches with `--no-alt-screen` for inline screen output
435
- - Detects prompt by looking for `›` prefix in recent lines
436
- - Multi-step submit: types text, then sends Enter after a
437
- 75ms delay (lets the UI process the input)
438
-
439
566
  ### Claude Adapter
440
567
 
441
568
  - Detects workspace trust prompt ("Quick safety check")
@@ -452,7 +579,7 @@ The adapter reads the screen and returns a state hash:
452
579
  - Stop sequence sends a double Ctrl+C to match OpenCode's native
453
580
  terminal shutdown path (interrupt first, force-quit second if
454
581
  needed).
455
- - Multi-step submit mirrors Claude/Codex PTY behavior: text first,
582
+ - Multi-step submit mirrors Claude's PTY behavior: text first,
456
583
  then Enter with a short delay.
457
584
 
458
585
  ## State Machine
@@ -478,7 +605,8 @@ The adapter reads the screen and returns a state hash:
478
605
  - Uses a Mutex + ConditionVariable for thread-safe access
479
606
  - `wait_for_prompt(timeout)` blocks until state == `:prompt`
480
607
  - State is updated by the PTY reader thread calling the
481
- adapter's `input_state` on each screen change
608
+ adapter's `input_state` on each screen change; for Pi, by
609
+ RPC lifecycle events (`agent_start` busy, `agent_settled` prompt)
482
610
 
483
611
  ## Inbox and Message Queue
484
612
 
@@ -509,6 +637,9 @@ Each session has an `Inbox` with a background delivery thread:
509
637
 
510
638
  Poll status with `GET /messages/:id`.
511
639
 
640
+ A send to a busy Pi worker skips the queue: it is handed to Pi at once
641
+ as a follow-up (`delivery: "follow_up"`), so it cannot expire.
642
+
512
643
  ## HTTP API
513
644
 
514
645
  All endpoints are on `127.0.0.1:<port>`. Every request needs
@@ -523,13 +654,13 @@ Returns JSON:
523
654
  "ok": true,
524
655
  "session_id": "abc123",
525
656
  "repo_root": "/path/to/repo",
526
- "cli": "codex",
527
- "id": "worker",
657
+ "cli": "pi",
658
+ "id": "pi-i-13",
528
659
  "description": "implement auth module",
529
660
  "pid": 12345,
530
661
  "host": "127.0.0.1",
531
662
  "port": 43123,
532
- "command": ["codex", "--no-alt-screen"],
663
+ "command": ["pi", "--mode", "rpc"],
533
664
  "started_at": "2026-03-13T20:45:00Z",
534
665
  "last_injected_at": null,
535
666
  "injected_count": 0,
@@ -616,7 +747,8 @@ Every session also writes a repo-keyed transcript:
616
747
  ~/.local/state/harnex/output/<repo_hash>--<normalized_id>.log
617
748
  ```
618
749
 
619
- - PTY output is appended as bytes are read
750
+ - PTY output is appended as bytes are read; Pi sessions append
751
+ the synthesized RPC transcript
620
752
  - The transcript is opened in append mode, so reusing an ID
621
753
  does not wipe prior output
622
754
  - `output_log_path` is exposed via `status --json` and
@@ -668,6 +800,7 @@ Defaults: base `43000`, span `4000` (range 43000–46999).
668
800
  Harnex uses Ruby threads, not processes:
669
801
 
670
802
  - **PTY reader** — reads agent output into screen buffer
803
+ (Pi: **JSONL reader** for RPC events, plus a stderr drain)
671
804
  - **State poller** — feeds screen to adapter for state
672
805
  - **Inbox delivery** — dequeues messages when prompt detected
673
806
  - **HTTP server** — one thread per connection
@@ -686,6 +819,7 @@ guides/02_chain.md
686
819
  guides/03_buddy.md
687
820
  guides/04_monitoring.md
688
821
  guides/05_naming.md
822
+ guides/06_claude.md
689
823
  ```
690
824
 
691
825
  The files are exposed through the CLI:
@@ -694,6 +828,7 @@ The files are exposed through the CLI:
694
828
  harnex agents-guide
695
829
  harnex agents-guide dispatch
696
830
  harnex agents-guide monitoring
831
+ harnex agents-guide claude
697
832
  ```
698
833
 
699
834
  ### What's in the guides
@@ -709,6 +844,7 @@ The agent guides cover:
709
844
  - Buddy monitoring for unattended work
710
845
  - Completion signals, event/log/pane monitoring, and wall-clock caps
711
846
  - Session ID, task file, and done marker naming conventions
847
+ - Claude Code sessions and subagents using Pi workers as tools
712
848
 
713
849
  Older releases shipped `harnex skills install`; that installer was removed in
714
850
  0.6.1. Agents now discover harnex directly with `harnex --help` and
@@ -716,8 +852,9 @@ Older releases shipped `harnex skills install`; that installer was removed in
716
852
 
717
853
  ## Known Limitations
718
854
 
719
- - Adapter prompt detection is heuristic-based. It works well
720
- but can misread unusual screen output.
855
+ - PTY prompt detection is heuristic-based. It works well
856
+ but can misread unusual screen output. Pi state comes from
857
+ RPC events, not the screen.
721
858
  - No read-only HTTP output endpoint for local dashboards (use
722
859
  `harnex pane --follow` or `harnex logs --follow` instead).
723
860
  - File watching: inotify on Linux, stat-polling fallback on
@@ -729,5 +866,5 @@ Older releases shipped `harnex skills install`; that installer was removed in
729
866
 
730
867
  - Ruby 3.x standard library only
731
868
  - No gems required
732
- - Uses: `io/console`, `pty`, `socket`, `json`, `net/http`,
869
+ - Uses: `io/console`, `pty`, `open3`, `socket`, `json`, `net/http`,
733
870
  `digest`, `fileutils`, `shellwords`, `fiddle` (for inotify)
data/agents/gpt.md ADDED
@@ -0,0 +1,60 @@
1
+ ---
2
+ name: gpt
3
+ description: "Hand a task or question to a GPT model (a Pi worker run through harnex) and get its reply back verbatim. Use when the user asks for GPT, for another model's opinion or review, or to delegate work to Pi/GPT workers; launch several at once for parallel GPT work, and SendMessage the same agent for follow-ups (the worker keeps its context). While it runs, the harnex `lanes` tool or `harnex lanes` shows what each worker is doing right now (its current tool). Questions and reviews go to a read-only worker; a brief that asks to run commands, tests or benchmarks, or to change files, goes to a worker that can (in the current checkout). The prompt may open with header lines, then a blank line, then the brief: `worktree: NAME` (edit in a new git worktree on branch harnex/NAME; afterwards land it with the harnex `land` tool or `harnex land NAME`, which merges and removes the worktree and branch; don't git merge lane branches by hand), `mode: write` (run commands or edit the current checkout in place), `mode: read` (read-only even if the brief mentions commands), `model: provider/model`, `effort: low|medium|high`, `fallback: provider/model` (read-only asks), `cwd: <dir>`. Write a self-contained brief: the GPT worker has not seen this conversation."
4
+ tools: mcp__harnex__ask, mcp__harnex__delegate, mcp__harnex__reply, mcp__harnex__result
5
+ mcpServers:
6
+ - harnex
7
+ model: haiku
8
+ color: green
9
+ ---
10
+
11
+ You are a relay to a GPT worker. You never do the task yourself, never answer
12
+ from your own knowledge, and never rewrite, shorten or summarize the brief or
13
+ the reply. Your whole job: pass the brief to one harnex tool, wait for it, and
14
+ return its text.
15
+
16
+ ## 1. Split the prompt
17
+
18
+ The prompt may open with header lines `key: value` using only these keys:
19
+ `worktree`, `mode`, `model`, `effort`, `fallback`, `cwd`. The header ends at
20
+ the first line that is not one of them; drop one blank line after it.
21
+ Everything after the header is the brief, kept exactly as written. No header
22
+ lines means the whole prompt is the brief.
23
+
24
+ ## 2. Call one tool
25
+
26
+ Pick the tool:
27
+
28
+ - **`delegate`:** when the header has `worktree: NAME` or `mode: write`, or
29
+ when the header has no `mode: read` and the brief asks the worker to run
30
+ something (a command, tests, a benchmark, a script) or to change files.
31
+ - **`ask`:** otherwise, for questions, explanations and reviews that need
32
+ only reading. An `ask` worker cannot run commands.
33
+
34
+ Then:
35
+
36
+ - **`delegate`:** call it with
37
+ - `task` = the brief;
38
+ - `worktree` = NAME, if given;
39
+ - and `model`, `effort`, `cwd` if given.
40
+ - **`ask`:** call it with
41
+ - `question` = the brief;
42
+ - and `model`, `effort`, `cwd` if given, plus `fallback_model` from
43
+ `fallback:`.
44
+ - **A later message in this conversation** (a follow-up) goes to `reply`, with
45
+ - `name` = the lane named in the last result's `── NAME:` footer;
46
+ - `message` = the new message, exactly as written.
47
+
48
+ Pass every string exactly as written.
49
+
50
+ ## 3. Still working
51
+
52
+ If the tool's text ends with a footer saying `still working`, call `result`
53
+ with `names` = [that lane name]. Repeat while it says still working, up to 12
54
+ times. Never start a second lane for the same brief.
55
+
56
+ ## 4. Return
57
+
58
+ Your reply is the tool's text exactly as returned, including its `── NAME:`
59
+ footer. Add no preface, summary, commentary or code fence. If the tool
60
+ reports an error, return that text as it is, and do not answer yourself.