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/agents.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Coding agents
|
|
2
|
+
|
|
3
|
+
harnex is built to be driven by coding agents: Claude Code, Pi, or anything
|
|
4
|
+
that can run a shell command or call an MCP tool. A supervising agent hands a
|
|
5
|
+
question or a task to a Pi worker (a GPT model by default), keeps working, and
|
|
6
|
+
gets the worker's reply back as plain text it can act on.
|
|
7
|
+
|
|
8
|
+
There are three ways in, from smoothest to most portable:
|
|
9
|
+
|
|
10
|
+
1. **MCP tools** (`harnex mcp install`): `ask`, `delegate`, `result`,
|
|
11
|
+
`reply`, `land`, `discard` and `lanes` as native tools. See
|
|
12
|
+
[MCP server](mcp.md).
|
|
13
|
+
2. **The `gpt` subagent** for Claude Code: a background subagent that relays
|
|
14
|
+
one brief to a worker and returns the reply verbatim.
|
|
15
|
+
3. **The CLI** from any agent's shell tool: the same verbs as
|
|
16
|
+
[lanes](lanes.md).
|
|
17
|
+
|
|
18
|
+
## Claude Code
|
|
19
|
+
|
|
20
|
+
### MCP tools (main conversation)
|
|
21
|
+
|
|
22
|
+
With the server registered, Claude calls the tools directly. A call that runs
|
|
23
|
+
longer than two minutes moves to the background on its own; Claude keeps
|
|
24
|
+
working and is notified when the result lands, so a `delegate` behaves like a
|
|
25
|
+
background subagent without one:
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
❯ Use the harnex delegate tool to have a GPT worker run the slow benchmark and tell me the score.
|
|
29
|
+
● Calling harnex…
|
|
30
|
+
⎿ pi-slow-bench: working 59s · 3 tool calls · now bash 44s
|
|
31
|
+
● The GPT worker is still running the benchmark. I'll report the score when it finishes.
|
|
32
|
+
● MCP task (harnex/delegate) completed.
|
|
33
|
+
● The score is 302926.
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### The `gpt` subagent
|
|
37
|
+
|
|
38
|
+
`harnex mcp install` also installs a `gpt` agent definition at
|
|
39
|
+
`~/.claude/agents/gpt.md` when none exists. It is a thin Haiku relay with only
|
|
40
|
+
the harnex tools: it never does the task itself and returns the worker's reply
|
|
41
|
+
exactly, footer included. Use it for parallel work (several `Agent` calls in
|
|
42
|
+
one message) and for follow-ups (`SendMessage` to the same agent reaches the
|
|
43
|
+
same worker, with its context).
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
Agent(subagent_type: "gpt", prompt: """
|
|
47
|
+
worktree: word-count
|
|
48
|
+
|
|
49
|
+
Add TextKit.word_count(text) to lib/textkit.rb with a test; run bin/test.
|
|
50
|
+
""")
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Optional header lines, then a blank line, then the brief:
|
|
54
|
+
|
|
55
|
+
| Header | Effect |
|
|
56
|
+
| --- | --- |
|
|
57
|
+
| `worktree: NAME` | Edit in a new worktree on `harnex/NAME`; land it afterwards |
|
|
58
|
+
| `mode: write` | Run commands or edit the current checkout in place |
|
|
59
|
+
| `mode: read` | Read-only, even if the brief mentions commands |
|
|
60
|
+
| `model: provider/model`, `effort: low\|medium\|high` | Pick the worker's model |
|
|
61
|
+
| `fallback: provider/model` | Retry a read-only ask on another model |
|
|
62
|
+
| `cwd: DIR` | Work in another directory |
|
|
63
|
+
|
|
64
|
+
Without a header, questions and reviews go to a read-only worker, and a brief
|
|
65
|
+
that asks to run something or change files goes to a worker that can.
|
|
66
|
+
The definition ships with harnex as [`agents/gpt.md`](../agents/gpt.md).
|
|
67
|
+
|
|
68
|
+
### The CLI from Claude's Bash tool
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
harnex delegate --worktree word-count "Add TextKit.word_count(text) with a test; run bin/test."
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Give the Bash call `timeout: 600000`: commands wait up to 9 minutes, and
|
|
75
|
+
Claude Code's default Bash timeout is two minutes. On exit 124 run
|
|
76
|
+
`harnex result NAME`. In the main conversation, `run_in_background` works too.
|
|
77
|
+
|
|
78
|
+
### Things worth knowing
|
|
79
|
+
|
|
80
|
+
- Claude Code loads agent definitions and MCP servers when a session starts;
|
|
81
|
+
restart after `harnex mcp install`.
|
|
82
|
+
- Background results reach the parent XML-escaped (`&`, `<`). The relay's
|
|
83
|
+
own text is exact.
|
|
84
|
+
- Headless `claude -p` stops background work ten idle minutes after the main
|
|
85
|
+
turn; set `CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=0` for long unattended runs.
|
|
86
|
+
|
|
87
|
+
## Pi
|
|
88
|
+
|
|
89
|
+
`harnex mcp install` adds the server to `~/.pi/agent/mcp.json` with
|
|
90
|
+
`exposure: direct`, so a Pi supervisor sees the tools like built-ins. Without
|
|
91
|
+
MCP, Pi uses the CLI through its `bash` tool; `harnex agents-guide claude` is
|
|
92
|
+
the short guide it needs (it applies to any supervising agent). Workers
|
|
93
|
+
started by harnex never see harnex's own MCP tools, so a worker cannot start
|
|
94
|
+
more workers.
|
|
95
|
+
|
|
96
|
+
## Habits that keep it smooth
|
|
97
|
+
|
|
98
|
+
- **Self-contained briefs.** The worker has not seen your conversation: name
|
|
99
|
+
the files, the expected behavior and the check (`bin/test`).
|
|
100
|
+
- **A worktree for anything that edits.** `--worktree NAME` keeps parallel
|
|
101
|
+
lanes apart and your checkout clean until you land.
|
|
102
|
+
- **Verify, then land.** `done` means the worker's turn ended without error,
|
|
103
|
+
not that the work is right. Read the diff and run the tests yourself.
|
|
104
|
+
- **Land, don't merge by hand.** `land` also removes the worktree and branch.
|
|
105
|
+
- **Check on a worker with `lanes`.** It shows the tool each worker is running
|
|
106
|
+
now.
|
|
107
|
+
|
|
108
|
+
## Measuring it
|
|
109
|
+
|
|
110
|
+
`evals/agents/run` drives real supervisors (Claude Code headless and Pi print
|
|
111
|
+
mode, each with and without MCP) through seven scenarios against a fixture
|
|
112
|
+
repository and scores correctness, tool calls, harnex usage errors, doc
|
|
113
|
+
lookups and leftovers. harnex 0.29 passes all 28 runs with no harnex usage
|
|
114
|
+
errors, no doc lookups and nothing left behind.
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
evals/agents/run --list
|
|
118
|
+
evals/agents/run --supervisor claude,pi S2 S3
|
|
119
|
+
```
|
data/docs/dispatch-telemetry.md
CHANGED
|
@@ -11,6 +11,26 @@ git repo it is `~/.local/state/harnex/dispatch.jsonl`. `harnex history`,
|
|
|
11
11
|
`status --id`, and `wait` consume the same rows. Legacy v1 thin rows and
|
|
12
12
|
pre-v2 envelope-less summaries may coexist and remain readable/skippable.
|
|
13
13
|
|
|
14
|
+
## Portable home paths
|
|
15
|
+
|
|
16
|
+
From 0.14.1, new canonical records and reconciliation repair appends serialize
|
|
17
|
+
string values equal to the current user's home as `~`, and paths beneath it as
|
|
18
|
+
`~/...`. The home-directory match uses a complete path-component boundary;
|
|
19
|
+
sibling directories, other users' tilde forms and paths outside that home are
|
|
20
|
+
not rewritten. A path mentioned later inside a prose string is not scrubbed.
|
|
21
|
+
This is path serialization, not a general identity or secret redactor.
|
|
22
|
+
|
|
23
|
+
The writer serializes a copy. Runtime/session structures, the events stream,
|
|
24
|
+
registry and receipt files retain their normal paths. Existing ledger rows are
|
|
25
|
+
not rewritten automatically. Status API results still expose absolute repository
|
|
26
|
+
roots, and path-consuming retention/report operations resolve portable paths
|
|
27
|
+
against the reader's home.
|
|
28
|
+
|
|
29
|
+
Reconciliation compares absolute paths under the current home and their portable
|
|
30
|
+
forms consistently. Genuinely different paths, identities, values and outcomes
|
|
31
|
+
still conflict. Home-relative paths are interpreted in the current user's home
|
|
32
|
+
context; this does not infer another machine's home directory or migrate archives.
|
|
33
|
+
|
|
14
34
|
## Clean-Tree Checks And The Tracked Stream
|
|
15
35
|
|
|
16
36
|
Some repos deliberately track `.harnex/dispatch.jsonl` as project telemetry.
|
|
@@ -24,7 +44,7 @@ alongside the work it describes.
|
|
|
24
44
|
## CLI flags
|
|
25
45
|
|
|
26
46
|
```text
|
|
27
|
-
harnex run
|
|
47
|
+
harnex run pi --meta '{"model":"openai/gpt-5.1","effort":"high","predicted":{"input_tokens":[200000,800000]}}'
|
|
28
48
|
harnex run pi --context 'Implement the task; Harnex writes the final receipt'
|
|
29
49
|
harnex run pi --artifact-report .harnex/receipts/pi-r-64.json --context 'Optionally write review claims to $HARNEX_ARTIFACT_CLAIMS_PATH'
|
|
30
50
|
harnex run pi --project-id harnex --queue-id queue-005 --entry-id SP-4 --phase implement --intent queue-work --require-attribution
|
|
@@ -76,6 +96,31 @@ harnex orchestration report --dispatch .harnex/dispatch.jsonl --run-id queue-005
|
|
|
76
96
|
|
|
77
97
|
Use `harnex history --json | jq .` for pipelines over the repo-local log.
|
|
78
98
|
|
|
99
|
+
## Optional external archive bridge
|
|
100
|
+
|
|
101
|
+
Repositories may keep the active stream bounded while preserving one logical
|
|
102
|
+
history through two opt-in local executable hooks:
|
|
103
|
+
|
|
104
|
+
- `HARNEX_DISPATCH_ARCHIVER=/absolute/path/to/helper` runs immediately before
|
|
105
|
+
each append as `helper --canonical PATH --incoming-bytes N`. Harnex holds a
|
|
106
|
+
stable sidecar lock under `$HARNEX_STATE_DIR/locks/` before invoking the
|
|
107
|
+
helper and opens the active stream only afterward, so an atomic replacement
|
|
108
|
+
cannot strand a waiting writer on the old inode. A helper failure emits a
|
|
109
|
+
bounded warning and the new row is still appended to the active stream.
|
|
110
|
+
- `HARNEX_DISPATCH_HISTORY_READER=/absolute/path/to/helper` runs as
|
|
111
|
+
`helper --canonical PATH`. It must write the complete archive-plus-active
|
|
112
|
+
JSONL stream to stdout and exit successfully. Harnex captures that stream in
|
|
113
|
+
a private temporary file before exposing any rows; failure or malformed
|
|
114
|
+
output is reported as unavailable history rather than an empty result.
|
|
115
|
+
|
|
116
|
+
Both values must name an absolute, executable, non-symlink local file. Harnex
|
|
117
|
+
passes no telemetry bytes or credentials in arguments, does no network work of
|
|
118
|
+
its own, suppresses helper output on the write path, and retains its ordinary
|
|
119
|
+
active-file behavior when the hooks are unset. The repository-specific helper
|
|
120
|
+
owns encryption, storage credentials, archive verification, and retrieval.
|
|
121
|
+
`harnex history`, canonical assertion/reconciliation, latest-row lookup, and
|
|
122
|
+
attempt-chain accounting consume the complete reader when configured.
|
|
123
|
+
|
|
79
124
|
## Canonical assertion and reconciliation
|
|
80
125
|
|
|
81
126
|
`harnex telemetry assert-canonical` is the read-only drift gate for the
|
|
@@ -130,15 +175,19 @@ Harnex-owned `meta` fields are always populated when derivable: `id`,
|
|
|
130
175
|
These top-level `--meta` keys pass through into `meta` when provided:
|
|
131
176
|
`orchestrator`, `orchestrator_session`, `chain_id`, `parent_dispatch_id`,
|
|
132
177
|
`parent_attempt_id`, `attempt_kind`, `tier`, `phase`, `issue`, `plan`, and
|
|
133
|
-
`task_brief`.
|
|
178
|
+
`task_brief`. `harnex run --context-file` fills `context_file` (absolute
|
|
179
|
+
path), `context_sha256` (SHA-256 of the text actually sent) and, on
|
|
180
|
+
`started.meta` only, `context_section`; it also defaults `task_brief` to the
|
|
181
|
+
file path. Queue-specific keys such as
|
|
134
182
|
`project_id`, `queue_id`, `entry_id`, `entry_title`, and `intent` are used for
|
|
135
183
|
the top-level `queue` block but are not duplicated into legacy `meta`. Unknown
|
|
136
184
|
top-level keys are kept on `started.meta` but are not copied into the v2 end
|
|
137
185
|
row.
|
|
138
186
|
|
|
139
|
-
`predicted` is copied
|
|
140
|
-
otherwise it is `{}`.
|
|
141
|
-
|
|
187
|
+
`predicted` is copied from `--meta.predicted` when it is a JSON object;
|
|
188
|
+
otherwise it is `{}`. The canonical record applies the portable-path serialization
|
|
189
|
+
above; runtime values are unchanged. Harnex does no profile lookup or
|
|
190
|
+
recommendation-table resolution.
|
|
142
191
|
|
|
143
192
|
## Queue, agent, and reliability blocks
|
|
144
193
|
|
|
@@ -168,17 +217,20 @@ routing details; legacy `meta.agent*` and `actual.model` stay for compatibility:
|
|
|
168
217
|
```json
|
|
169
218
|
{
|
|
170
219
|
"agent": {
|
|
171
|
-
"cli": "
|
|
220
|
+
"cli": "pi",
|
|
172
221
|
"provider": "openai",
|
|
173
|
-
"model_requested": "gpt-5.
|
|
174
|
-
"model_effective": "gpt-5.
|
|
222
|
+
"model_requested": "openai/gpt-5.1",
|
|
223
|
+
"model_effective": "gpt-5.1",
|
|
175
224
|
"reasoning_effort": "high",
|
|
176
|
-
"service_tier":
|
|
177
|
-
"adapter_transport": "
|
|
225
|
+
"service_tier": null,
|
|
226
|
+
"adapter_transport": "stdio_jsonl_rpc"
|
|
178
227
|
}
|
|
179
228
|
}
|
|
180
229
|
```
|
|
181
230
|
|
|
231
|
+
`service_tier` is copied from `--meta` and is otherwise null; older rows may
|
|
232
|
+
carry an adapter-set value.
|
|
233
|
+
|
|
182
234
|
The top-level `reliability` block is always emitted and should be preferred over
|
|
183
235
|
legacy `actual.disconnections` for reliability analytics:
|
|
184
236
|
|
|
@@ -270,18 +322,12 @@ last valid terminal and peak values intact while setting
|
|
|
270
322
|
`latest_sample_status: "missing"`. Null never means zero.
|
|
271
323
|
|
|
272
324
|
`context.status` is `observed` for Pi's dedicated
|
|
273
|
-
`get_session_stats.contextUsage` signal, `
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
`pi_get_session_stats`
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
active context size, but it excludes local items appended after that response;
|
|
280
|
-
Harnex therefore labels it estimated and derives
|
|
281
|
-
`terminal_percent = last.totalTokens / modelContextWindow * 100`. That is
|
|
282
|
-
full-window pressure, not Codex TUI's baseline-adjusted “context left” display.
|
|
283
|
-
No prompt, transcript, message, tool payload, or compaction summary is copied
|
|
284
|
-
into this block.
|
|
325
|
+
`get_session_stats.contextUsage` signal, `missing` when a supported source
|
|
326
|
+
yielded no valid occupancy, or `unsupported` when the adapter has no
|
|
327
|
+
active-context source (`estimated` appears only in older rows). `source` is
|
|
328
|
+
`pi_get_session_stats` for Pi and null for unsupported adapters. Pi's
|
|
329
|
+
percentage is adapter reported. No prompt, transcript, message, tool payload,
|
|
330
|
+
or compaction summary is copied into this block.
|
|
285
331
|
|
|
286
332
|
`attribution.status` is `complete` when `project_id`, `phase`, `intent`, and a
|
|
287
333
|
work id are present; `partial` when any attribution is known but that contract
|
|
@@ -294,6 +340,45 @@ is normally `accepted` or `rejected`. `source=harnex_observed_state` identifies
|
|
|
294
340
|
new receipts. The block also contains final commit/path/LOC observations; those
|
|
295
341
|
facts prove the delta, not semantic quality or human authorship.
|
|
296
342
|
|
|
343
|
+
`outcome.class` is one value from a closed vocabulary (#57):
|
|
344
|
+
|
|
345
|
+
- **Completion:** `completed_with_proof`, `completed_with_activity`.
|
|
346
|
+
- **Proof defects:** `completed_no_activity`, `report_missing`,
|
|
347
|
+
`report_invalid`, `report_rejected`.
|
|
348
|
+
- **Process:** `task_failed` (the turn failed), `provider_capacity` (the turn
|
|
349
|
+
failed after the adapter's own provider retries ran out, from Pi's
|
|
350
|
+
structured `auto_retry_end success:false`; capacity or rate limits rather
|
|
351
|
+
than the work), `boot_failure` (structured
|
|
352
|
+
transport died before its first turn), `permission_prompt` (ended on a
|
|
353
|
+
trust/confirmation prompt the adapter recognizes), `timeout` (`--max-runtime`
|
|
354
|
+
stopped running work, or exit 124), `stopped_by_caller` (a manual stop
|
|
355
|
+
while work was running), `process_failed` (nonzero exit with no turn
|
|
356
|
+
verdict).
|
|
357
|
+
- **Fallback:** `unknown`.
|
|
358
|
+
|
|
359
|
+
Completion and proof classes are never overridden. Process classes refine an
|
|
360
|
+
`unknown` or `task_failed` terminal, or a `report_rejected` receipt for a
|
|
361
|
+
process that never signalled completion (finalization always writes a receipt,
|
|
362
|
+
so that rejection records the failed process, not a proof verdict). Signals:
|
|
363
|
+
the exit path, stop provenance, and final input state; never transcript text.
|
|
364
|
+
A stop or deadline outranks `task_failed` only when the work was still running
|
|
365
|
+
when it was stopped. A clean exit 0 without completion keeps its accepted
|
|
366
|
+
receipt (`completed_with_proof`), as before. Older rows are not reclassified.
|
|
367
|
+
|
|
368
|
+
`harnex orchestration report --run-id ID` (or `--queue-id ID`) counts
|
|
369
|
+
`workers.outcome_classes` per raw attempt and per retry family. A family joins
|
|
370
|
+
attempts by `parent_attempt_id`, by `parent_dispatch_id` for non-initial
|
|
371
|
+
attempts (retry/fix/fallback/superseding/review), or by a reused dispatch id,
|
|
372
|
+
so correlated retries count as one root cause. Spawner-derived parents of
|
|
373
|
+
initial workers do not join families. Start rows are not counted. An optional
|
|
374
|
+
`--failure-budget N [--budget-unit attempts|families] [--fatal-classes A,B]`
|
|
375
|
+
adds a `failure_budget` verdict and exits 3 once N fatal failures are counted
|
|
376
|
+
(1 is an error, 2 a usage error; a missing `--dispatch` file is an error).
|
|
377
|
+
By default the fatal classes are `completed_no_activity`, `task_failed`,
|
|
378
|
+
`provider_capacity`, `boot_failure`, `permission_prompt`, `timeout`, and `process_failed`; proof
|
|
379
|
+
defects, caller stops and `unknown` are not fatal.
|
|
380
|
+
Harnex supplies the ledger only: the caller decides what to do on a breach.
|
|
381
|
+
|
|
297
382
|
Every end row has one Harnex-session `attempt`. Its random `id` is distinct
|
|
298
383
|
from operator-visible `run_id`; `parent_attempt_id` and `parent_dispatch_id`
|
|
299
384
|
link retries/fixes/reviews/fallbacks while each row keeps separate raw usage.
|
|
@@ -368,7 +453,7 @@ A generated receipt contains:
|
|
|
368
453
|
"version": 1,
|
|
369
454
|
"author": "harnex",
|
|
370
455
|
"generated_at": "2026-08-03T05:00:00Z",
|
|
371
|
-
"id": "
|
|
456
|
+
"id": "pi-r-64",
|
|
372
457
|
"session_id": "8d8f3f07c8fc343d"
|
|
373
458
|
},
|
|
374
459
|
"outcome": {
|
|
@@ -415,23 +500,64 @@ A generated receipt contains:
|
|
|
415
500
|
(`start_sha != end_sha`, commit count/path/LOC evidence) from an observed
|
|
416
501
|
`no_change` result. `start_dirty`, `end_dirty`, and `worktree_changed` expose
|
|
417
502
|
worktree caveats while unchanged pre-session dirt is excluded from the delta.
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
503
|
+
Pi RPC built-in `bash` tool calls contribute bounded command text, integer
|
|
504
|
+
exit code, status, and optional duration. Pi commands require an integer `result.structuredContent.exit_code`
|
|
505
|
+
correlated by `toolCallId`; Pi bash calls without one (abort, timeout,
|
|
506
|
+
extension override, older Pi, still running at teardown) are counted in
|
|
507
|
+
`observed.commands_unobserved` and mark `command_observation` as `partial`
|
|
508
|
+
when some exits were observed, or `unsupported` when none were. PTY transports
|
|
509
|
+
report `command_observation: "unsupported"` rather than guessing from prose or
|
|
510
|
+
generic tool events. If the 256-KiB receipt cap requires
|
|
422
511
|
trimming, `commands_truncated` / `changed_paths_truncated` make that explicit;
|
|
423
512
|
full aggregate counts remain in the dispatch row. Usage carries the same
|
|
424
513
|
measured/missing/unsupported semantics as the dispatch
|
|
425
514
|
row and is refreshed at teardown when final adapter usage becomes available.
|
|
426
515
|
|
|
427
516
|
`validation.commands` mirrors all bounded observed command exits for legacy
|
|
428
|
-
readers. Its aggregate status is `not_run`, `pass`, or `fail
|
|
517
|
+
readers. Its aggregate status is `not_run`, `pass`, or `fail`, or `unknown`
|
|
518
|
+
when no observed exit failed but `commands_unobserved` is positive; a failed
|
|
429
519
|
exploratory command may precede a successful turn. For harness receipts,
|
|
430
520
|
`validate --final` therefore validates receipt authorship/shape, accepted turn,
|
|
431
521
|
and accepted/no-change outcome instead of letting an intermediate command exit
|
|
432
522
|
rewrite the harness verdict. Queue policy may impose a stricter command gate by
|
|
433
523
|
inspecting `observed.commands`.
|
|
434
524
|
|
|
525
|
+
Final gates (#68) separate acceptance from command history. Declare them with
|
|
526
|
+
repeatable `harnex run --final-gate '<cmd>'` (or meta `"final_gates": [...]`,
|
|
527
|
+
which `--final-gate` appends to). The receipt then carries:
|
|
528
|
+
|
|
529
|
+
```json
|
|
530
|
+
"validation": {
|
|
531
|
+
"status": "pass",
|
|
532
|
+
"commands_had_failures": true,
|
|
533
|
+
"final_gates": {
|
|
534
|
+
"declared": 1, "observed_pass": 1, "observed_fail": 0, "missing": 0,
|
|
535
|
+
"status": "pass",
|
|
536
|
+
"gates": [{ "cmd": "rake test", "exit_code": 0 }]
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
- A gate matches an observed command by exact string. A structured adapter's
|
|
542
|
+
single-shell transport wrapper (`bash -lc '<cmd>'`, `sh -c '<cmd>'`) is
|
|
543
|
+
unwrapped first; the inner script must still equal the gate exactly.
|
|
544
|
+
- Each gate is decided by its **last** observed run, so an intermediate
|
|
545
|
+
failure followed by a green final run passes.
|
|
546
|
+
- A gate that never ran is `fail` when command observation was complete, and
|
|
547
|
+
`unknown` when it was `partial` or `unsupported` (PTY transports), since it
|
|
548
|
+
may be among the unobserved calls.
|
|
549
|
+
- With gates declared, `validation.status` is the gate status. Without them,
|
|
550
|
+
`final_gates.status` is `undeclared` and `validation.status` keeps the
|
|
551
|
+
any-failure semantics above.
|
|
552
|
+
- `commands_had_failures` covers the session's whole command history, not only
|
|
553
|
+
the retained 50-command window.
|
|
554
|
+
- Dispatch rows keep `commands_had_failures` and the final-gate counts and
|
|
555
|
+
status, without the gate command text.
|
|
556
|
+
|
|
557
|
+
This is receipt ergonomics, not proof: `validate --final` and outcome classes
|
|
558
|
+
do not change, and supervisors should still replay declared final commands
|
|
559
|
+
verbatim before accepting work.
|
|
560
|
+
|
|
435
561
|
Reviewers can write one optional input file at
|
|
436
562
|
`HARNEX_ARTIFACT_CLAIMS_PATH`:
|
|
437
563
|
|
|
@@ -467,30 +593,28 @@ not a required worker step for new dispatches.
|
|
|
467
593
|
|
|
468
594
|
## Autonomous completion gate
|
|
469
595
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
delta
|
|
596
|
+
A Pi turn counts as accepted work completion once it settles
|
|
597
|
+
(`agent_settled`) without a final `error`, `aborted`, or `length` stop reason.
|
|
598
|
+
With `--require-artifact-report`, Harnex also requires at least one structured
|
|
599
|
+
command/tool call or a Git delta (PTY adapters can satisfy it only with a Git
|
|
600
|
+
delta). If both are absent, it emits `task_failed` with
|
|
474
601
|
`outcome_class=completed_no_activity`, writes a rejected observed receipt, and
|
|
475
|
-
normalizes auto-stop to non-zero.
|
|
476
|
-
|
|
477
|
-
answer text and claims. Intentional no-op work must still perform observable
|
|
602
|
+
normalizes auto-stop to non-zero. The gate deliberately ignores final answer
|
|
603
|
+
text and claims. Intentional no-op work must still perform observable
|
|
478
604
|
inspection/validation; a model assertion of `no_change` cannot self-approve.
|
|
479
605
|
|
|
480
606
|
PTY transports do not expose equivalent completion-item metadata, so their
|
|
481
|
-
existing prompt-return auto-stop behavior remains.
|
|
607
|
+
existing prompt-return auto-stop behavior remains. PTY transports label
|
|
482
608
|
command observation `unsupported`; Git and terminal state are still recorded
|
|
483
609
|
without guessing.
|
|
484
610
|
|
|
485
611
|
## Actuals
|
|
486
612
|
|
|
487
|
-
At process exit, harnex collects usage through the active adapter.
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
aggregates `tokenUsage.last` plus `modelContextWindow`; neither source is
|
|
493
|
-
substituted with cumulative usage when active occupancy is unavailable.
|
|
613
|
+
At process exit, harnex collects usage through the active adapter. Pi RPC
|
|
614
|
+
sessions read `get_session_stats`, and PTY adapters parse the last 16 KB of
|
|
615
|
+
transcript when they support a parser. Adapters without a parser emit nullable
|
|
616
|
+
usage fields. Separately, Pi aggregates bounded `contextUsage` samples; they are
|
|
617
|
+
never substituted with cumulative usage when active occupancy is unavailable.
|
|
494
618
|
|
|
495
619
|
Git actuals capture the start/end SHA plus committed, staged, unstaged, and
|
|
496
620
|
untracked changes relative to the worktree state observed at session start.
|
|
@@ -498,15 +622,25 @@ Unchanged pre-existing dirt is not credited to the worker, and harness-owned
|
|
|
498
622
|
dispatch/receipt paths are excluded. Git failures leave the corresponding
|
|
499
623
|
consolidated fields `null` and omit `git` events.
|
|
500
624
|
|
|
625
|
+
Git is observed in the repository the agent works in. A lane launched with
|
|
626
|
+
`--cwd <worktree> --root <checkout>`, where the two resolve to different Git
|
|
627
|
+
toplevels, is observed in its `--cwd` worktree: `commit_sha`, `branch`,
|
|
628
|
+
`start_sha`/`end_sha`, `observed.git`, activity and acceptance all describe
|
|
629
|
+
the lane, so supervisor commits on the root never count as the lane's work.
|
|
630
|
+
`--root` still decides where the rows are written. The `git` start event then
|
|
631
|
+
carries `git_root`, and the end row's `root_commit_sha` records the `--root`
|
|
632
|
+
repository's head (it is `null` for every other dispatch).
|
|
633
|
+
|
|
501
634
|
The `actual` block includes model/effort hints from `--meta`, duration, token
|
|
502
635
|
counts, `agent_session_id`, compatibility `cost_usd`, adapter transport, git
|
|
503
636
|
deltas, exit reason, task completion state, signal/exit code, last error,
|
|
504
637
|
operational counters (`stalls`, `force_resumes`, `disconnections`,
|
|
505
|
-
`compactions`, `turn_count`, `tool_calls`, `commands_executed`),
|
|
506
|
-
payloads, output/event volume
|
|
507
|
-
additive attempt counters are
|
|
508
|
-
`
|
|
509
|
-
`
|
|
638
|
+
`compactions`, `turn_count`, `tool_calls`, `commands_executed`), `rate_limits`
|
|
639
|
+
(always null now; only older rows carry payloads), output/event volume
|
|
640
|
+
measurements, and output/events log paths. New additive attempt counters are
|
|
641
|
+
`attempts_total`, `attempts_succeeded`, `attempts_failed`, `retry_count`,
|
|
642
|
+
`throttle_429_count` (no current producer, so 0 in new rows), `disconnect_count`,
|
|
643
|
+
and `fallback_triggered`. Throughput values are populated only for a
|
|
510
644
|
harness-accepted outcome: `throughput_tokens_per_s` and
|
|
511
645
|
`throughput_successes_per_h`; `retry_tax_pct` is `0.0` when no retry occurred
|
|
512
646
|
and `null` until a retry source can measure attributable wasted tokens.
|
|
@@ -538,8 +672,8 @@ jq -s 'map(select(.actual.attempts_total > 0))
|
|
|
538
672
|
- `failure`: wrapped process exited non-zero, the observed-activity gate emitted
|
|
539
673
|
`task_failed`, or Harnex could not write/validate the receipt.
|
|
540
674
|
- `timeout`: wrapped process exited with code `124`.
|
|
541
|
-
- `boot_failure`:
|
|
542
|
-
turn was observed.
|
|
675
|
+
- `boot_failure`: the structured Pi RPC transport exited within the startup
|
|
676
|
+
window (5 s) before a turn was observed.
|
|
543
677
|
- `disconnected`: wrapped process exited `0` but no session summary was parsed.
|
|
544
678
|
|
|
545
679
|
Canonical dispatch-stream writes remain best-effort. Receipt
|
data/docs/events.md
CHANGED
|
@@ -39,7 +39,8 @@ Every row includes this envelope:
|
|
|
39
39
|
|
|
40
40
|
- `schema_version` (Integer): always `1`
|
|
41
41
|
- `seq` (Integer): monotonic per session, starts at `1`
|
|
42
|
-
- `ts` (String): UTC ISO-8601 timestamp
|
|
42
|
+
- `ts` (String): UTC ISO-8601 timestamp with millisecond precision
|
|
43
|
+
(`2026-05-01T11:30:00.123Z`; rows before 0.16.0 have whole seconds)
|
|
43
44
|
- `id` (String): session ID
|
|
44
45
|
- `type` (String): event type
|
|
45
46
|
|
|
@@ -54,6 +55,18 @@ Emitted now (Layer 4):
|
|
|
54
55
|
- `exited`: adds
|
|
55
56
|
- `code` (Integer): synthesized numeric exit code
|
|
56
57
|
- `signal` (Integer, optional): present for signaled exits
|
|
58
|
+
- `task_complete` / `task_failed` (structured adapters): add `turn` (Integer),
|
|
59
|
+
the session's work turn. Turn 1 is the first prompt; a message injected
|
|
60
|
+
after a published result starts the next turn, announced by a `work_turn`
|
|
61
|
+
row (`turn`). `harnex send` reports `after_seq`, the newest `seq` before
|
|
62
|
+
its message, so `harnex wait --until done --after-seq N` waits for the turn
|
|
63
|
+
that message started or joined. They also carry, when known:
|
|
64
|
+
`final_message` (the worker's last reply this turn, at most 4,000
|
|
65
|
+
characters), `final_message_path` (the full text),
|
|
66
|
+
`final_message_truncated`, `follow_ups` (`{sent, pending}`), and the lane's
|
|
67
|
+
`end_sha` / `commits` at the turn's end.
|
|
68
|
+
- `queue_update` (Pi): `follow_up` and `steering`, the sizes of Pi's pending
|
|
69
|
+
queues (counts only, never message text).
|
|
57
70
|
|
|
58
71
|
## 5. Stability promise
|
|
59
72
|
|
|
@@ -119,12 +132,12 @@ New event types:
|
|
|
119
132
|
Example telemetry sequence:
|
|
120
133
|
|
|
121
134
|
```json
|
|
122
|
-
{"schema_version":1,"seq":1,"ts":"2026-05-01T11:30:00Z","id":"
|
|
123
|
-
{"schema_version":1,"seq":2,"ts":"2026-05-01T11:30:00Z","id":"
|
|
124
|
-
{"schema_version":1,"seq":3,"ts":"2026-05-01T11:42:13Z","id":"
|
|
125
|
-
{"schema_version":1,"seq":4,"ts":"2026-05-01T11:42:13Z","id":"
|
|
126
|
-
{"schema_version":1,"seq":5,"ts":"2026-05-01T11:42:13Z","id":"
|
|
127
|
-
{"schema_version":1,"seq":6,"ts":"2026-05-01T11:42:13Z","id":"
|
|
135
|
+
{"schema_version":1,"seq":1,"ts":"2026-05-01T11:30:00Z","id":"pi-i-372","type":"started","pid":12345,"meta":{"issue":"23","plan":"27","predicted":{"input_tokens":[200000,800000]}}}
|
|
136
|
+
{"schema_version":1,"seq":2,"ts":"2026-05-01T11:30:00Z","id":"pi-i-372","type":"git","phase":"start","sha":"a8114695c1f0","branch":"main"}
|
|
137
|
+
{"schema_version":1,"seq":3,"ts":"2026-05-01T11:42:13Z","id":"pi-i-372","type":"usage","input_tokens":104158,"output_tokens":2709,"reasoning_tokens":870,"cached_tokens":250880,"total_tokens":106867,"agent_session_id":"019ddf05-0f03-7d70-904f-23db7f00640f"}
|
|
138
|
+
{"schema_version":1,"seq":4,"ts":"2026-05-01T11:42:13Z","id":"pi-i-372","type":"git","phase":"end","sha":"abc1234567","loc_added":312,"loc_removed":65,"files_changed":7,"commits":1}
|
|
139
|
+
{"schema_version":1,"seq":5,"ts":"2026-05-01T11:42:13Z","id":"pi-i-372","type":"summary","path":"/home/u/proj/.harnex/dispatch.jsonl","exit":"success"}
|
|
140
|
+
{"schema_version":1,"seq":6,"ts":"2026-05-01T11:42:13Z","id":"pi-i-372","type":"exited","code":0,"reason":"success"}
|
|
128
141
|
```
|
|
129
142
|
|
|
130
143
|
Events files are runtime logs, not the durable dispatch summary. Their age and
|