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/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.
|
|
11
|
-
|
|
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
|
|
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
|
|
42
|
-
harnex events --id
|
|
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
|
|
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
|
|
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).
|