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
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
|
|
13
|
-
harnex run
|
|
14
|
-
harnex run
|
|
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
|
-
|
|
40
|
-
activity or Git delta
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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 (
|
|
85
|
-
| `--no-submit` | Type
|
|
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
|
-
|
|
101
|
-
|
|
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
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|
127
|
-
harnex wait --id
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
207
|
-
harnex run
|
|
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
|
|
210
|
-
harnex send --id
|
|
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
|
|
213
|
-
harnex send --id
|
|
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
|
|
220
|
-
--context "Implement the auth feature. Commit when done."
|
|
221
|
-
|
|
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
|
|
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
|
|
300
|
-
├──
|
|
301
|
-
├──
|
|
302
|
-
├── opencode.rb
|
|
303
|
-
└──
|
|
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
|
|
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
|
|
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.
|
|
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`
|
|
367
|
-
| `validate_runtime!`
|
|
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
|
-
|
|
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
|
-
| `
|
|
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
|
-
###
|
|
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.
|
|
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
|
|
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": "
|
|
527
|
-
"id": "
|
|
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": ["
|
|
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
|
-
-
|
|
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.
|