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/docs/lanes.md ADDED
@@ -0,0 +1,202 @@
1
+ # Lanes
2
+
3
+ A **lane** is one Pi worker doing one job: a question, a task, or a task plus
4
+ its follow-ups. You start one with `harnex ask` or `harnex delegate`, collect
5
+ it with `harnex result`, continue it with `harnex reply`, and finish it with
6
+ `harnex land` or `harnex discard`. Every lane has a name, runs in a visible
7
+ tmux window named after it, and leaves a small record so any later command can
8
+ find it by name from anywhere in the repository.
9
+
10
+ Lanes are the agent-facing layer of harnex. Everything here is also available
11
+ as native tools through [`harnex mcp`](mcp.md), and the low-level `run`, `send`,
12
+ `wait` and `watch` commands remain underneath for custom orchestration.
13
+
14
+ | Need | Command |
15
+ | --- | --- |
16
+ | A second opinion, an explanation, a read-only review | `harnex ask "QUESTION"` |
17
+ | A task that edits code | `harnex delegate --worktree NAME "TASK"`, review, then `harnex land NAME` |
18
+ | A task that only runs or inspects things | `harnex delegate "TASK"` |
19
+ | Several tasks in parallel | `harnex delegate --no-wait --worktree NAME "TASK"` per task, then `harnex result` |
20
+ | More work for the same worker, with its context | `harnex reply NAME "MESSAGE"` |
21
+ | Throw a lane away | `harnex discard NAME` |
22
+ | What is open and what each worker is doing | `harnex lanes` |
23
+
24
+ ## Ask
25
+
26
+ ```bash
27
+ harnex ask "Which module writes dispatch rows, and is the lane case tested?"
28
+ harnex ask --model openai/gpt-5.1 --file /tmp/review.md
29
+ ```
30
+
31
+ The worker reads the repository with read-only tools (`read`, `grep`, `find`,
32
+ `ls`), answers, and stops. It cannot run commands or change anything; for
33
+ that, use `delegate`. stdout is the answer; stderr gets one line naming the
34
+ lane, its time and the model. `--write` lets the worker edit and run commands,
35
+ and `--fallback-model M` asks once more with another model when the first one
36
+ is out of capacity or rate limited.
37
+
38
+ Every ask is a lane (`pi-ask-xxxx`), so `harnex reply pi-ask-xxxx "..."` asks a
39
+ follow-up of the same worker, with everything it read still in context.
40
+
41
+ ## Delegate
42
+
43
+ ```bash
44
+ harnex delegate --worktree word-count \
45
+ "Add TextKit.word_count(text): the number of whitespace-separated words, with a test. Run bin/test."
46
+ ```
47
+
48
+ The worker edits files, runs commands, and replies with a summary. harnex
49
+ prints that reply and then a footer:
50
+
51
+ ```text
52
+ Committed as `a6bf91e` on `harnex/word-count`.
53
+ - Added `TextKit.word_count(text)` in `lib/textkit.rb`.
54
+ - Ran `bin/test`: 3 tests, 6 assertions, all passing.
55
+
56
+ ── word-count: done in 49s · gpt-5.1 · $0.03 · 1 commit, 2 files (+12 -0)
57
+ worktree .harnex/worktrees/word-count (branch harnex/word-count)
58
+ next: review `git diff HEAD...harnex/word-count`, then `harnex land word-count` ...
59
+ ```
60
+
61
+ The footer always says what happened and what to do next: state, time, model,
62
+ cost, commits and the next commands.
63
+
64
+ - **`--worktree NAME`** creates `.harnex/worktrees/NAME` on a new branch
65
+ `harnex/NAME` from `HEAD`. The worker is told to commit there. Parallel lanes
66
+ never collide, and your checkout is untouched until you land. If your checkout
67
+ has uncommitted changes, harnex warns that the worktree starts from `HEAD`.
68
+ - **Without `--worktree`** the worker works in the current directory, which is
69
+ right for tasks that only run or inspect things (tests, a benchmark, a long
70
+ command). `--name NAME` names the lane; otherwise harnex derives a name from
71
+ the task (`pi-slow-bench`).
72
+ - **`--file PATH`** reads a long brief from a file. Briefs must be
73
+ self-contained: the worker has not seen your conversation. Name the files, the
74
+ expected behavior and how to verify it.
75
+ - **`--model provider/model`** and **`--effort low|medium|high`** pick the
76
+ model; the default is Pi's own default model.
77
+
78
+ ### Waiting, and long tasks
79
+
80
+ Blocking commands (`ask`, `delegate`, `reply`, `result`) wait up to 9 minutes
81
+ by default, which fits under the 10-minute tool cap most coding agents have.
82
+ `--timeout DUR` changes it (`540`, `9m`, `1h30m`, or `none`).
83
+
84
+ | Exit code | Meaning |
85
+ | --- | --- |
86
+ | 0 | done |
87
+ | 1 | the worker failed (the footer says why and what to try) |
88
+ | 2 | usage error (nothing was started) |
89
+ | 124 | still working — the worker keeps going; run `harnex result NAME` |
90
+
91
+ ```text
92
+ ── slow-bench: still working (9m02s so far)
93
+ keep waiting: harnex result slow-bench
94
+ ```
95
+
96
+ `harnex result NAME` blocks again (another 9 minutes by default) and prints the
97
+ result in the same format once it lands, even if the worker finished between
98
+ calls.
99
+
100
+ ## Review and land
101
+
102
+ ```bash
103
+ git diff HEAD...harnex/word-count # read the change
104
+ (cd .harnex/worktrees/word-count && bin/test)
105
+ harnex land word-count
106
+ ```
107
+
108
+ `harnex land NAME` merges `harnex/NAME` into the current branch of the main
109
+ checkout, then removes the worktree and the branch. Leftover uncommitted
110
+ changes in the lane are committed first. `harnex land` with no name lands every
111
+ finished worktree lane you started in this repo, oldest first.
112
+
113
+ **Conflicts.** When the merge conflicts, git's merge is left in progress and
114
+ harnex lists the files. Resolve them, `git add` them and `git commit`, then run
115
+ `harnex land NAME` again to clean up (or `git merge --abort` to give up).
116
+ Several lanes that touched the same file land one at a time.
117
+
118
+ **Merging by hand** works too. A lane whose branch is already in `HEAD` (and
119
+ whose worktree is clean) is tidied up by the next `delegate`, `result`, `lanes`
120
+ or bare `land`.
121
+
122
+ `harnex discard NAME` stops the lane if it is still working and deletes its
123
+ worktree and branch without merging.
124
+
125
+ ## Parallel lanes
126
+
127
+ ```bash
128
+ harnex delegate --no-wait --worktree slugify "Fix TextKit.slugify so punctuation is dropped ..."
129
+ harnex delegate --no-wait --worktree truncate "Add TextKit.truncate(text, n) ..."
130
+ harnex delegate --no-wait --worktree titleize "Add TextKit.titleize(text) ..."
131
+ harnex result # waits for all three, printing each result as it lands
132
+ harnex land # lands every finished worktree lane, oldest first
133
+ ```
134
+
135
+ `harnex result` with no names covers every lane you started in this repo whose
136
+ result you have not collected yet. Name lanes to wait for specific ones.
137
+ "You" is the calling agent session (`CLAUDE_CODE_SESSION_ID` or
138
+ `PI_SESSION_ID`); a Claude Code subagent shares its parent's id, so a subagent
139
+ should name the lanes it waits for.
140
+
141
+ ## Follow-ups
142
+
143
+ ```bash
144
+ harnex reply word-count "Also add TextKit.char_count(text), in the same style, with a test."
145
+ ```
146
+
147
+ The worker keeps its context. A worker still on its turn takes the message
148
+ mid-task as a follow-up; a lane that has finished is relaunched on its own Pi
149
+ session (`pi --session`), so it remembers everything from the earlier turns.
150
+ The footer's cost is the lane's total so far.
151
+
152
+ ## When a model fails
153
+
154
+ ```text
155
+ ── word-count: FAILED after 3s (API error (404): model_not_found) · gpt-9 · $0.00 · 0 commits
156
+ next: `harnex reply word-count "Continue the same task from where you stopped" --model openai/gpt-5.1`
157
+ (keeps the task; another model) or `harnex discard word-count`
158
+ ```
159
+
160
+ For provider and model failures (not found, rate limited, out of capacity) the
161
+ footer suggests continuing on Pi's default model. `reply --model` resumes the
162
+ same session, so the task is not lost. For `ask`, `--fallback-model` does this
163
+ automatically.
164
+
165
+ ## Watching a worker
166
+
167
+ ```bash
168
+ harnex lanes
169
+ ```
170
+
171
+ ```text
172
+ NAME STATE AGE MODEL BRANCH NEXT
173
+ slugify working (now bash 12s) 1m40s gpt-5.1 harnex/slugify harnex result slugify
174
+ truncate done 2m03s gpt-5.1 harnex/truncate harnex land truncate
175
+ ```
176
+
177
+ A working lane shows the tool it is running right now (tool names only; harnex
178
+ never records tool arguments). `harnex pane --id NAME --lines 40` captures the
179
+ worker's tmux window, and the window itself is always there to watch.
180
+
181
+ ## Where things live
182
+
183
+ - **Lane records:** `~/.local/state/harnex/lanes/` (one JSON file per lane:
184
+ repo, worktree, branch, model, owner, Pi session, cost, last result). Closed
185
+ lanes are pruned after 30 days; open lanes never are.
186
+ - **Worktrees:** `.harnex/worktrees/NAME` inside your repository, on branch
187
+ `harnex/NAME`.
188
+ - **`.harnex/`** is added to the clone's `.git/info/exclude` the first time
189
+ harnex writes there, so lane worktrees and dispatch rows never show up as
190
+ untracked work. Nothing tracked changes.
191
+
192
+ ## Habits from `harnex run`
193
+
194
+ The lane commands accept the flags people and agents reach for from the
195
+ low-level `run`, instead of refusing them: `--id NAME` and `--tmux NAME` name
196
+ the lane, `--auto-stop` and `--wait-done` are what lanes do anyway,
197
+ `--wait-timeout` means `--timeout`, and `--context`/`--context-file` give the
198
+ task.
199
+
200
+ See also: [MCP server](mcp.md), [Coding agents](agents.md), and the CLI
201
+ reference (`harnex help delegate`, `result`, `reply`, `land`, `discard`,
202
+ `lanes`, `ask`).
data/docs/mcp.md ADDED
@@ -0,0 +1,102 @@
1
+ # MCP server
2
+
3
+ `harnex mcp` serves the [lane](lanes.md) commands as native
4
+ [Model Context Protocol](https://modelcontextprotocol.io) tools over stdio.
5
+ Claude Code, Pi and any other MCP client can then hand work to Pi workers
6
+ without shelling out, quoting a brief, or looping on exit codes.
7
+
8
+ ```bash
9
+ harnex mcp install # register for Claude Code (user scope) and Pi
10
+ harnex mcp status # where it is registered
11
+ harnex mcp uninstall # remove both registrations
12
+ ```
13
+
14
+ Restart running Claude Code and Pi sessions afterwards: clients load MCP
15
+ servers when a session starts. `harnex doctor` reports the registrations under
16
+ `integrations`.
17
+
18
+ ## Tools
19
+
20
+ Each tool runs the matching CLI command and returns its text: the worker's
21
+ reply followed by the `── NAME:` footer.
22
+
23
+ | Tool | Arguments | Does |
24
+ | --- | --- | --- |
25
+ | `ask` | `question`; `model`, `effort`, `fallback_model`, `write`, `cwd`, `wait_minutes` | A read-only answer from a Pi worker |
26
+ | `delegate` | `task`; `worktree`, `name`, `wait`, `model`, `effort`, `cwd`, `wait_minutes` | A worker does the task (in a worktree when `worktree` is set) |
27
+ | `result` | `names`; `cwd`, `wait_minutes` | Wait for lanes and return each result |
28
+ | `reply` | `name`, `message`; `model`, `wait`, `cwd`, `wait_minutes` | Follow-up to the same worker, which keeps its context |
29
+ | `land` | `names` (optional); `cwd` | Merge worktree lanes, remove worktrees and branches |
30
+ | `discard` | `names`; `cwd` | Drop lanes without merging |
31
+ | `lanes` | `all`; `cwd` | Open lanes, what each worker is doing, the next step |
32
+
33
+ Arguments are forgiving: `prompt`, `brief` or `message` work for `task`;
34
+ `prompt` or `query` for `question`; `lane` or `id` for `name`. `cwd` defaults
35
+ to the server's working directory, which MCP clients set to the project.
36
+
37
+ ## Long calls
38
+
39
+ A blocking call (`ask`, `delegate`, `reply`, `result`) waits up to
40
+ `wait_minutes` (default 25) and then returns "still working" with the lane name;
41
+ the worker keeps going and `result` collects it. While it waits, the server
42
+ sends `notifications/progress` every 15 seconds:
43
+
44
+ ```text
45
+ word-count: working 2m10s · 7 tool calls · now bash 12s
46
+ ```
47
+
48
+ Progress resets the clients' idle timers (Claude Code's 30-minute stdio idle
49
+ timeout, Pi's per-request timeout), and Claude Code shows it under the running
50
+ tool call.
51
+
52
+ - **Claude Code, main conversation:** a call still running after two minutes
53
+ moves to a background task on its own; Claude keeps working and is notified
54
+ when the result lands. A plain `delegate` therefore behaves like a background
55
+ subagent.
56
+ - **Claude Code subagents and headless runs** block on the call. Headless
57
+ `claude -p` stops background work ten idle minutes after the main turn ends;
58
+ set `CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=0` for longer unattended runs.
59
+ - **Pi:** `harnex mcp install` registers the server with `exposure: direct`
60
+ (the tools are declared to the model like built-ins) and a two-hour request
61
+ timeout.
62
+ - **Cancelling** a call (for example pressing Esc) stops the wait, not the
63
+ worker; `lanes` and `result` still see it.
64
+
65
+ Calls run concurrently, so a client can start several `delegate` calls with
66
+ `wait: false` and collect them with one `result`.
67
+
68
+ ## Workers never get harnex's tools
69
+
70
+ Pi keeps MCP tools even when a worker is limited with `--tools`, so a worker
71
+ started by harnex would otherwise see `delegate` and could start more workers,
72
+ or escape a read-only ask. harnex prevents both: every Pi worker starts with
73
+ `--exclude-tools mcp__harnex__…`, and a read-only ask worker starts with
74
+ `--no-mcp`.
75
+
76
+ ## Other clients
77
+
78
+ Any client that speaks MCP over stdio can run the server. The command is the
79
+ `harnex` executable with the argument `mcp`. Prefer the absolute path of the
80
+ RubyGems wrapper, `$(ruby -e 'print Gem.bindir')/harnex`, which is what
81
+ `harnex mcp install` registers, so no version-manager shim or working
82
+ directory gets in the way:
83
+
84
+ ```json
85
+ {
86
+ "mcpServers": {
87
+ "harnex": { "command": "/path/to/bin/harnex", "args": ["mcp"] }
88
+ }
89
+ }
90
+ ```
91
+
92
+ The server negotiates MCP protocol revisions 2024-11-05 through 2025-11-25
93
+ and declares only the `tools` capability.
94
+
95
+ ## Troubleshooting
96
+
97
+ - **The tools don't appear:** restart the client session; check
98
+ `harnex mcp status` and, for Pi, `pi mcp list`.
99
+ - **A call ends with "still working":** that is the wait limit, not a failure;
100
+ call `result` with the lane name.
101
+ - **A worker failed on its model:** the footer suggests
102
+ `reply` with another `model`, which keeps the task.
data/docs/pi-rpc.md CHANGED
@@ -7,8 +7,8 @@ scraping the TUI:
7
7
  harnex run pi -> pi --mode rpc -> LF-delimited JSON objects on stdio
8
8
  ```
9
9
 
10
- Pi RPC is JSONL, not JSON-RPC 2.0. Codex uses `codex app-server` JSON-RPC;
11
- Harnex presents the same run/send/wait/events control surface over both.
10
+ Pi RPC is JSONL, not JSON-RPC 2.0. Harnex presents the same
11
+ run/send/wait/events control surface over it as over its PTY adapters.
12
12
 
13
13
  ## Compatibility
14
14
 
@@ -35,11 +35,11 @@ with Pi 0.80.4-0.83.x and avoids printing the final message twice on 0.84+.
35
35
  ## Dispatch
36
36
 
37
37
  ```bash
38
- harnex run pi --id cx-i-42 --tmux cx-i-42 \
38
+ harnex run pi --id pi-i-42 --tmux pi-i-42 \
39
39
  --context "Implement issue 42 and run tests" --auto-stop
40
40
 
41
- harnex wait --id cx-i-42 --until task_complete --timeout 3600
42
- harnex events --id cx-i-42
41
+ harnex wait --id pi-i-42 --until task_complete --timeout 3600
42
+ harnex events --id pi-i-42
43
43
  ```
44
44
 
45
45
  `--context` is sent as an RPC `prompt` command; it is not passed as a Pi
@@ -49,7 +49,7 @@ Harnex model and effort flags are active controls, not telemetry-only labels:
49
49
 
50
50
  ```bash
51
51
  harnex run pi \
52
- --model anthropic/claude-sonnet-4-5 --effort high \
52
+ --model openai/gpt-5.1 --effort high \
53
53
  --context "Review the change" --auto-stop
54
54
  ```
55
55
 
@@ -66,7 +66,7 @@ Pass Pi startup flags after `--`:
66
66
 
67
67
  ```bash
68
68
  harnex run pi --context "Implement X" --auto-stop -- \
69
- --no-session --no-extensions --model anthropic/claude-sonnet-4-5
69
+ --no-session --no-extensions --model openai/gpt-5.1
70
70
  ```
71
71
 
72
72
  When both Harnex `--model` and a Pi child `--model` are supplied, the Harnex
@@ -116,6 +116,55 @@ RPC requests are bounded, stderr is continuously drained into a bounded
116
116
  diagnostic tail, and Harnex consumes Open3's wait thread so the real subprocess
117
117
  exit status is not lost to a competing `waitpid`.
118
118
 
119
+ Persistent workers can be stopped after accepted `agent_settled` completion.
120
+ An explicit idle `harnex stop` preserves the latest accepted/no-change receipt,
121
+ including after a follow-up turn. Expected EOF and TERM teardown are normal
122
+ adapter cleanup, not a lost connection. Harnex reports a logical exit of zero
123
+ for that cleanup; the `process_exited` event retains the raw child exit code
124
+ and signal (TERM yields code 143; Pi may handle it with `process.exit(143)`,
125
+ so signal 15 is only present when the OS reports a signalled exit).
126
+
127
+ A new prompt invalidates the preceding turn's completion before waiting for its
128
+ RPC response. Stop during that new work fails closed, and late lifecycle events
129
+ cannot promote the interrupted turn or replace the idle-stop decision. Unexpected
130
+ EOF (even exit zero), malformed transport, and abnormal cleanup exits remain
131
+ failures. Output is drained before final receipt/dispatch finalization so trailing
132
+ transport errors are not silently lost. Existing `--auto-stop` settlement remains
133
+ supported.
134
+
135
+ ## Runtime limits and activity
136
+
137
+ Harnex 0.14.0 adds opt-in `run --max-runtime DUR`, independent of observer
138
+ `watch --max-wait` / `wait --timeout`. Check `doctor --adapter pi` for
139
+ `capabilities.runtime_budget: 1` before enabling it. The fixed monotonic budget
140
+ is armed before child launch and initial prompting, and never resets on retries,
141
+ follow-up prompts, steering, or UI output. Expiry terminates without waiting for
142
+ an abort RPC or completion/output lock, including during startup. Owned worker
143
+ process groups cover their normal descendants; deliberately detached groups are
144
+ not an OS-level containment guarantee. Active work fails with logical run exit
145
+ 124; cleanup after accepted idle settlement preserves proof. Shutdown/finalization
146
+ has separate I/O overhead. An earlier stop cannot disable the deadline;
147
+ `runtime_budget.enforced` records actual enforcement separately from stop labels.
148
+
149
+ Live JSON status separates `activity` work clocks from `log_idle_s`. Pi assistant
150
+ message boundaries and text/thinking/tool-call deltas update model activity;
151
+ `tool_execution_*` updates tool activity. Thinking content is not stored in the
152
+ activity tracker or newly logged. Extension UI/queue/retry events never fake
153
+ model/tool progress. `model_active` describes an observed stream lifetime, not
154
+ hidden provider state; a quiet active stream is not a completed task. PTY clocks
155
+ are unknown. Dispatch-end rows retain the terminal activity snapshot.
156
+
157
+ The first requested stop records reason/origin/time and work state in a
158
+ `stop_requested` event, JSON status/end metadata, and receipt `observed.stop`.
159
+ The labels are bounded caller declarations, separate from acceptance. Harnex's
160
+ runtime expiry is `runtime_budget`/`runtime`, ordinary stop is `manual`/`cli`,
161
+ and auto-stop is `completion`/`auto_stop`. Expected cancellation EOF is not a
162
+ provider disconnection; unexpected EOF or malformed/unterminated JSONL remains a failure.
163
+ Raw Pi exit evidence is in `process_exited`, before logical normalization.
164
+
165
+ See `harnex agents-guide monitoring` for clock fields, heartbeat streaming,
166
+ observer deadlines, stop labels, and the full runtime-budget contract.
167
+
119
168
  ## Telemetry
120
169
 
121
170
  At settlement and teardown, Harnex reads `get_session_stats` and records:
@@ -127,6 +176,31 @@ At settlement and teardown, Harnex reads `get_session_stats` and records:
127
176
  - terminal and peak active-context usage.
128
177
 
129
178
  Message/tool/retry/compaction events are available through `harnex events`.
179
+
180
+ ### Command proof
181
+
182
+ Built-in `bash` tool calls are command evidence in the observed receipt.
183
+ Harnex correlates `tool_execution_start` and `tool_execution_end` by
184
+ `toolCallId` and records a command only when both structured facts exist:
185
+ non-empty `args.command` text (bounded to 2,000 characters) and an integer
186
+ `result.structuredContent.exit_code`. Status is `completed` or `failed` from
187
+ `isError`; `duration_s` comes from `durationMs`. Command output, including
188
+ `structuredContent.output`, is never persisted.
189
+
190
+ Pi reports aborts, timeouts, and shell exits without a code as plain error
191
+ text with no `structuredContent`; extensions that replace a result can drop it
192
+ too. Harnex never parses that prose. Such calls, malformed exits, and calls
193
+ still in flight at teardown count in `observed.commands_unobserved`, and
194
+ `command_observation` becomes `partial` (some exits observed) or `unsupported`
195
+ (none). A session with no bash calls reports `observed` with no commands.
196
+ Other tools — read, edit, write, MCP, extension tools — are tool calls, not
197
+ commands. `actual.commands_executed` counts bash calls; `actual.tool_calls` is
198
+ Pi's own reported total, which includes them.
199
+
200
+ Live verified on Pi 1.2.0 and 1.3.0. Older Pi releases without bash
201
+ `structuredContent` fail closed to `unsupported`. Harnex 0.15.0+ advertises
202
+ this contract as `harnex doctor` `capabilities.pi_command_proof: 1`; check it
203
+ before treating Pi receipt commands as proof.
130
204
  Blocking extension dialogs (`select`, `confirm`, `input`, `editor`) are
131
205
  currently auto-cancelled so autonomous workers cannot hang. Full extension UI
132
206
  mediation is not implemented.
@@ -0,0 +1,87 @@
1
+ # Upgrading to 0.29
2
+
3
+ harnex 0.29 is the agent release. It adds lanes (`delegate`, `result`,
4
+ `reply`, `land`, `discard`, `lanes`), the `harnex mcp` server and the `gpt`
5
+ subagent for Claude Code. The low-level session commands (`run`, `send`, `wait`,
6
+ `watch`, `stop`, `status`, `pane`, `logs`, `events`) keep working as before.
7
+
8
+ The last RubyGems release before this one was 0.12.0. If you are upgrading
9
+ from it, read the whole page; from 0.21 on, only the 0.22 section applies.
10
+
11
+ ```bash
12
+ gem install harnex # 0.29.0
13
+ harnex --version
14
+ harnex doctor # checks Pi; reports MCP and agent integrations
15
+ harnex mcp install # optional: native tools for Claude Code and Pi
16
+ pi update # harnex needs Pi 0.80.4+; MCP features want 1.0.4+
17
+ ```
18
+
19
+ ## Breaking changes
20
+
21
+ ### Codex support was removed (0.20)
22
+
23
+ harnex now runs Pi workers backed by GPT and other models; Pi's structured RPC
24
+ mode is the worker path that is designed, tested and supported. Removed:
25
+
26
+ - the `codex` and `codex app-server` adapters and the JSON-RPC session
27
+ transport;
28
+ - `harnex run --fast` and `--legacy-pty`, and `harnex preflight --legacy-pty`;
29
+ - the Codex checks in `harnex doctor` (it checks Pi; `--adapter pi|all`).
30
+
31
+ `harnex run codex` is no longer special-cased; it wraps a `codex` binary as a
32
+ generic PTY program. Claude Code, OpenCode and any other terminal CLI still run
33
+ under a PTY as before. Dispatch rows keep their schema; Codex-only fields stay
34
+ `null` in new rows.
35
+
36
+ ### `harnex ask` waits 9 minutes by default (0.22)
37
+
38
+ `ask` used to wait without a limit. It now stops waiting after 9 minutes (the
39
+ worker keeps going) and exits 124, naming `harnex result ID`, which keeps
40
+ waiting and prints the answer. Pass `--timeout none` for the old behavior.
41
+ `ask --resume ID` still works and no longer needs `--repo`.
42
+
43
+ ### Pi starts with `--no-approve` (0.22)
44
+
45
+ Pi's RPC mode cannot show its project-trust prompt, so harnex now starts every
46
+ Pi worker with `--no-approve`: project-local Pi resources (`.pi/` extensions,
47
+ skills, prompts) are ignored unless you pass `-- --approve`. Before, the result
48
+ depended on Pi's default.
49
+
50
+ ### Workers never see harnex's own MCP tools (0.25)
51
+
52
+ When harnex is registered as an MCP server in Pi, every Pi worker harnex
53
+ starts gets `--exclude-tools mcp__harnex__…`, and a read-only ask worker gets
54
+ `--no-mcp`. A worker can no longer start other workers. Your own `--tools`
55
+ and `--exclude-tools` still apply (the exclusions are merged).
56
+
57
+ ## What's new, briefly
58
+
59
+ - **Lanes** — `delegate` hands a task to a worker (optionally in its own git
60
+ worktree) and prints its reply with a footer of state, model, cost, commits
61
+ and next steps. `result` collects parallel lanes, `reply` follows up with the
62
+ worker's context (a finished lane resumes its Pi session), `land` merges and
63
+ cleans up, `discard` drops a lane, `lanes` shows what each worker is doing.
64
+ See [Lanes](lanes.md).
65
+ - **`harnex mcp`** — the same verbs as native MCP tools for Claude Code, Pi and
66
+ any MCP client, with progress notifications for long calls. See
67
+ [MCP server](mcp.md).
68
+ - **The `gpt` subagent** for Claude Code, installed by `harnex mcp install`.
69
+ See [Coding agents](agents.md).
70
+ - **Hygiene** — harnex adds `.harnex/` to the clone's `.git/info/exclude`, so
71
+ lane worktrees and dispatch rows never show up as untracked work.
72
+ - **`harnex doctor`** reports `integrations` (MCP registrations, the agent)
73
+ and new capabilities (`lanes`, `mcp`, `ask_resume`).
74
+ - **`wait --since TIME`** ignores events from an earlier launch of a reused
75
+ session id.
76
+
77
+ ## If something looks different
78
+
79
+ | You see | Why | Do |
80
+ | --- | --- | --- |
81
+ | `ask` exits 124 after 9 minutes | new default wait | `harnex result ID`, or `--timeout none` |
82
+ | A Pi worker ignores `.pi/` resources | `--no-approve` by default | `harnex run pi ... -- --approve` |
83
+ | `harnex run codex` behaves like a plain PTY program | Codex adapters removed | use Pi workers (`harnex delegate`, `harnex run pi`) |
84
+ | A new `.harnex/` line in `.git/info/exclude` | harnex hides its own state | nothing; it is local to the clone |
85
+ | Workers lack `mcp__harnex__*` tools | recursion guard | intended |
86
+
87
+ The full history is in the [changelog](../CHANGELOG.md).