harnex 0.8.0 → 0.10.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 +188 -0
- data/GUIDE.md +17 -10
- data/README.md +61 -54
- data/TECHNICAL.md +38 -15
- data/docs/codex-appserver.md +175 -0
- data/docs/configuration.md +118 -0
- data/docs/dispatch-telemetry.md +502 -0
- data/docs/events.md +132 -0
- data/guides/01_dispatch.md +31 -28
- data/guides/04_monitoring.md +10 -9
- data/guides/05_naming.md +18 -8
- data/lib/harnex/adapters/base.rb +10 -0
- data/lib/harnex/adapters/codex_appserver.rb +25 -0
- data/lib/harnex/artifact_report.rb +455 -6
- data/lib/harnex/cli.rb +3 -3
- data/lib/harnex/commands/artifact_report.rb +8 -7
- data/lib/harnex/commands/doctor.rb +38 -4
- data/lib/harnex/commands/history.rb +7 -3
- data/lib/harnex/commands/run.rb +55 -43
- data/lib/harnex/commands/status.rb +0 -2
- data/lib/harnex/commands/wait.rb +0 -1
- data/lib/harnex/config.rb +170 -0
- data/lib/harnex/core.rb +200 -21
- data/lib/harnex/dispatch_history.rb +16 -7
- data/lib/harnex/pricing.rb +135 -0
- data/lib/harnex/retention.rb +320 -0
- data/lib/harnex/runtime/session.rb +451 -166
- data/lib/harnex/terminal_status.rb +18 -21
- data/lib/harnex/version.rb +2 -2
- data/lib/harnex.rb +3 -0
- metadata +9 -2
data/guides/01_dispatch.md
CHANGED
|
@@ -22,9 +22,11 @@ Inside a harnex-managed session, these environment variables are available:
|
|
|
22
22
|
| `HARNEX_SESSION_REPO_ROOT` | Repo root for the session |
|
|
23
23
|
| `HARNEX_SESSION_ID` | Internal harnex instance ID |
|
|
24
24
|
| `HARNEX_SPAWNER_PANE` | Tmux pane ID of the invoker |
|
|
25
|
-
| `HARNEX_ARTIFACT_REPORT_PATH` | Absolute
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
25
|
+
| `HARNEX_ARTIFACT_REPORT_PATH` | Absolute harness-owned final receipt path |
|
|
26
|
+
| `HARNEX_ARTIFACT_CLAIMS_PATH` | Optional bounded worker-claims input path |
|
|
27
|
+
| `HARNEX_ARTIFACT_REPORT_SCHEMA` | Receipt schema identifier |
|
|
28
|
+
| `HARNEX_ARTIFACT_REPORT_MODE` | `observed_state` for harness-authored receipts |
|
|
29
|
+
| `HARNEX_ARTIFACT_REPORT_REQUIRED` | `1` when the compatibility strict flag was passed |
|
|
28
30
|
|
|
29
31
|
Use `harnex send`, `harnex status`, `harnex wait`, `harnex pane`, and
|
|
30
32
|
`harnex logs` to coordinate with peers. If you are not inside harnex,
|
|
@@ -69,8 +71,8 @@ harnex run pi --id pi-i-NN --tmux pi-i-NN \
|
|
|
69
71
|
For one-shot context dispatches that should clean themselves up, add
|
|
70
72
|
`--auto-stop`. It requires `--context` and does not keep the session alive for
|
|
71
73
|
later reuse. On Codex app-server, a turn launched from `--context` is accepted
|
|
72
|
-
only after structured command/tool activity
|
|
73
|
-
|
|
74
|
+
only after structured command/tool activity or a Git delta. Optional claims and
|
|
75
|
+
final prose cannot satisfy that observed-activity gate. A prose-only acknowledgment emits
|
|
74
76
|
`completed_no_activity`, is
|
|
75
77
|
visible as `task_failed` before teardown, and exits non-zero. This keeps
|
|
76
78
|
parallel orchestration compact without converting agent turn completion into
|
|
@@ -103,33 +105,29 @@ harnex run codex --cwd /tmp/leximaze_eval_run_001 \
|
|
|
103
105
|
`--root DIR` only overrides harnex's root attribution; it does not change the
|
|
104
106
|
child process cwd. Neither flag is a sandbox.
|
|
105
107
|
|
|
106
|
-
For queue closeout, ask workers to
|
|
107
|
-
|
|
108
|
-
|
|
108
|
+
For queue closeout, do not ask workers to author proof JSON. Harnex writes a
|
|
109
|
+
canonical receipt for every dispatch from the Git delta, structured command
|
|
110
|
+
exits, turn outcome, and usage it observed. The default receipt lives outside
|
|
111
|
+
the checkout under the Harnex state directory; pass `--artifact-report PATH`
|
|
112
|
+
only when a queue needs a fixed destination. Consumers can run:
|
|
109
113
|
|
|
110
114
|
```bash
|
|
111
|
-
harnex artifact-report
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
harnex artifact-report validate /path/from/the-dispatch-row.json --final
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
A worker may add review context by writing only a small block to
|
|
119
|
+
`$HARNEX_ARTIFACT_CLAIMS_PATH` before it completes:
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{"claims":{"summary":"Review complete","verdict":"changes_requested","findings":{"P1":0,"P2":1,"P3":0}}}
|
|
117
123
|
```
|
|
118
124
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
`
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
`validation.final_reported=true`.
|
|
126
|
-
|
|
127
|
-
Without `--require-artifact-report`, missing/malformed reports remain warning
|
|
128
|
-
telemetry. Strict mode fails closed for missing, malformed, unsupported,
|
|
129
|
-
oversized, schema-incomplete, rejected, or unchanged stale reports. JSON in the
|
|
130
|
-
agent's final prose does not count: only the configured sidecar path is read.
|
|
131
|
-
A fresh valid `no_change` report is the explicit proof path for intentional
|
|
132
|
-
no-delta work.
|
|
125
|
+
Claims are bounded and advisory. They never determine receipt validity, and
|
|
126
|
+
malformed/stale claims are ignored. `HARNEX_ARTIFACT_REPORT_PATH` is owned and
|
|
127
|
+
overwritten by Harnex; JSON printed in final prose is not scraped. The legacy
|
|
128
|
+
`artifact-report init` command and `--require-artifact-report` flag remain for
|
|
129
|
+
compatibility, but normal dispatches need neither worker JSON nor an explicit
|
|
130
|
+
receipt path.
|
|
133
131
|
|
|
134
132
|
Queue runners should pass first-class attribution so dispatch rows can be grouped
|
|
135
133
|
without path/id heuristics:
|
|
@@ -145,6 +143,11 @@ harnex run pi --id pi-i-NN --tmux pi-i-NN \
|
|
|
145
143
|
`intent`, and at least one work id (`queue_id`, `entry_id`, `issue`, or `plan`)
|
|
146
144
|
are present.
|
|
147
145
|
|
|
146
|
+
Every dispatch writes one `dispatch_start` and one rich v2 `dispatch_end` row
|
|
147
|
+
to the canonical repo/global dispatch stream, which is the only telemetry
|
|
148
|
+
destination. Repo `.harnex/config.json` can warn on or reject non-canonical
|
|
149
|
+
phase names before spawn.
|
|
150
|
+
|
|
148
151
|
Pi runs use structured RPC (`pi --mode rpc`). Pass Pi child flags after `--`
|
|
149
152
|
(e.g. `harnex run pi --context "..." -- --model anthropic/claude-sonnet-4-5 --thinking high`).
|
|
150
153
|
|
data/guides/04_monitoring.md
CHANGED
|
@@ -62,19 +62,19 @@ a coordination error (wrong id or wrong repo), and only `0` as success.
|
|
|
62
62
|
|
|
63
63
|
## Duplicate-Dispatch Guard
|
|
64
64
|
|
|
65
|
-
`harnex run --attempt-kind retry` requires
|
|
66
|
-
retry/fix/superseding dispatch whose
|
|
67
|
-
same repo is refused. Wait for the parent
|
|
65
|
+
`harnex run --attempt-kind retry|fallback` requires
|
|
66
|
+
`--parent-dispatch-id`, and any retry/fix/fallback/superseding dispatch whose
|
|
67
|
+
named parent is still running in the same repo is refused. Wait for the parent
|
|
68
68
|
(`harnex wait --id <parent> --until done`) or stop it first. Pass
|
|
69
69
|
`--allow-live-parent` only for intentional parallelism (e.g. isolated
|
|
70
70
|
worktrees). `--attempt-kind review` is exempt: a completed parent may still
|
|
71
71
|
sit at a live prompt while its work is reviewed. For structured sessions (Pi RPC and Codex app-server),
|
|
72
72
|
`harnex wait --until task_complete` remains the exact accepted-turn fence.
|
|
73
73
|
Codex acknowledgment-only auto-stop turns are typed
|
|
74
|
-
`completed_no_activity` and fail this fence without transcript parsing.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
verify the expected artifact or tests afterward.
|
|
74
|
+
`completed_no_activity` and fail this fence without transcript parsing. Harnex
|
|
75
|
+
writes the observed-state receipt before publishing `task_complete`; optional
|
|
76
|
+
worker claims never make an inactive turn pass. Harnex still does not judge
|
|
77
|
+
semantic quality, so verify the expected artifact or tests afterward.
|
|
78
78
|
|
|
79
79
|
## Completion Test
|
|
80
80
|
|
|
@@ -91,8 +91,9 @@ harnex watch --id pi-i-NN --until done --max-wait 90m \
|
|
|
91
91
|
|
|
92
92
|
`harnex watch --until done` wraps the `harnex wait --until done` work fence:
|
|
93
93
|
it succeeds from `task_complete` or durable successful terminal telemetry
|
|
94
|
-
(
|
|
95
|
-
|
|
94
|
+
(the v2 `dispatch_end` in `.harnex/dispatch.jsonl`, an explicit mirror when
|
|
95
|
+
configured, or exit status), returns non-zero for `task_failed` / failed
|
|
96
|
+
terminal telemetry, returns `124` for `--max-wait`, and
|
|
96
97
|
only writes done/fail markers as compatibility outputs after harnex has seen a
|
|
97
98
|
terminal work signal.
|
|
98
99
|
|
data/guides/05_naming.md
CHANGED
|
@@ -66,17 +66,27 @@ If `--id` is missing, harnex generates a random session ID. The tmux window may
|
|
|
66
66
|
look right, but `harnex status`, `harnex pane --id`, and logs need the random
|
|
67
67
|
ID.
|
|
68
68
|
|
|
69
|
-
## Retry Suffixes
|
|
69
|
+
## Retry Suffixes And Linkage
|
|
70
70
|
|
|
71
|
-
If a session fails and you dispatch a fresh attempt,
|
|
71
|
+
If a session fails and you dispatch a fresh attempt, use a new ID and link it
|
|
72
|
+
to the completed parent so Harnex can derive chain counts and recovery:
|
|
72
73
|
|
|
73
74
|
```text
|
|
74
|
-
pi-i-42
|
|
75
|
-
pi-i-
|
|
76
|
-
pi-i-
|
|
75
|
+
pi-i-42 initial attempt
|
|
76
|
+
pi-i-42-r1 retry of pi-i-42
|
|
77
|
+
pi-i-42-r2 retry of pi-i-42-r1
|
|
77
78
|
```
|
|
78
79
|
|
|
79
|
-
|
|
80
|
+
```bash
|
|
81
|
+
harnex run pi --id pi-i-42-r1 --tmux pi-i-42-r1 \
|
|
82
|
+
--attempt-kind retry --parent-dispatch-id pi-i-42 \
|
|
83
|
+
--context "Retry the bounded task."
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Retry/fix/fallback/superseding work is refused while its named parent is still
|
|
87
|
+
running unless `--allow-live-parent` explicitly authorizes isolated parallelism.
|
|
88
|
+
Keep old logs for diagnosis; retention removes only expired/over-cap logs that
|
|
89
|
+
do not belong to a current or live session.
|
|
80
90
|
|
|
81
91
|
## Task Files
|
|
82
92
|
|
|
@@ -104,8 +114,8 @@ If a legacy workflow still expects a done marker, derive it from the session ID:
|
|
|
104
114
|
```
|
|
105
115
|
|
|
106
116
|
Treat done markers as compatibility hints only. Canonical completion should come
|
|
107
|
-
from harnex terminal telemetry (`harnex wait` / `harnex status --json` /
|
|
108
|
-
rows in `.harnex/dispatch.jsonl`).
|
|
117
|
+
from harnex terminal telemetry (`harnex wait` / `harnex status --json` / v2
|
|
118
|
+
`dispatch_end` rows in `.harnex/dispatch.jsonl`).
|
|
109
119
|
|
|
110
120
|
When a brief asks for a completion marker, make it one line and include the
|
|
111
121
|
highest-signal result: tests passed, review clean, or the blocking issue.
|
data/lib/harnex/adapters/base.rb
CHANGED
|
@@ -37,6 +37,16 @@ module Harnex
|
|
|
37
37
|
nil
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
+
# Whether the usage this adapter captures reports input_tokens
|
|
41
|
+
# INCLUSIVE of cached_tokens. Depends on the capture path, not just
|
|
42
|
+
# the provider: codex app-server JSON reports cached as a subset of
|
|
43
|
+
# input, while the codex TUI line the PTY adapter scrapes shows
|
|
44
|
+
# non-cached input with cached as a separate additive count. Feeds
|
|
45
|
+
# Pricing.compute (plan 33 Phase 2).
|
|
46
|
+
def usage_input_includes_cached?
|
|
47
|
+
false
|
|
48
|
+
end
|
|
49
|
+
|
|
40
50
|
# Probes `<base_command.first> --version` with a short timeout and
|
|
41
51
|
# memoizes the result for the adapter's lifetime. Returns nil when
|
|
42
52
|
# the binary is missing, exits non-zero, or stalls past the timeout.
|
|
@@ -59,6 +59,7 @@ module Harnex
|
|
|
59
59
|
@initial_prompt = extract_initial_prompt(extra_args)
|
|
60
60
|
@client = nil
|
|
61
61
|
@thread_id = nil
|
|
62
|
+
@current_model = nil
|
|
62
63
|
@current_turn_id = nil
|
|
63
64
|
@state = :disconnected
|
|
64
65
|
@last_completed_at = nil
|
|
@@ -78,6 +79,21 @@ module Harnex
|
|
|
78
79
|
true
|
|
79
80
|
end
|
|
80
81
|
|
|
82
|
+
# thread/tokenUsage/updated totals report cachedInputTokens as a
|
|
83
|
+
# subset of inputTokens (verified against a live captured row:
|
|
84
|
+
# input + output == total exactly, cached < input).
|
|
85
|
+
def usage_input_includes_cached?
|
|
86
|
+
true
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Effective model reported by the app-server's thread/start or
|
|
90
|
+
# thread/resume response (schema-required field). Feeds
|
|
91
|
+
# Session#summary_model so price-table cost can resolve without the
|
|
92
|
+
# caller passing `--meta '{"model": ...}'`.
|
|
93
|
+
def current_model
|
|
94
|
+
@current_model
|
|
95
|
+
end
|
|
96
|
+
|
|
81
97
|
def context_telemetry_supported?
|
|
82
98
|
true
|
|
83
99
|
end
|
|
@@ -202,6 +218,7 @@ module Harnex
|
|
|
202
218
|
ensure_open!
|
|
203
219
|
result = @client.request("thread/resume", { threadId: thread_id })
|
|
204
220
|
@thread_id = thread_id
|
|
221
|
+
@current_model = extract_model(result) || @current_model
|
|
205
222
|
@state = :prompt
|
|
206
223
|
result
|
|
207
224
|
end
|
|
@@ -279,6 +296,7 @@ module Harnex
|
|
|
279
296
|
|
|
280
297
|
result = @client.request("thread/start", {})
|
|
281
298
|
@thread_id = extract_thread_id(result)
|
|
299
|
+
@current_model = extract_model(result) || @current_model
|
|
282
300
|
end
|
|
283
301
|
|
|
284
302
|
def extract_thread_id(payload)
|
|
@@ -287,6 +305,13 @@ module Harnex
|
|
|
287
305
|
payload.dig("thread", "id")
|
|
288
306
|
end
|
|
289
307
|
|
|
308
|
+
def extract_model(payload)
|
|
309
|
+
return nil unless payload.is_a?(Hash)
|
|
310
|
+
|
|
311
|
+
model = payload["model"]
|
|
312
|
+
model.is_a?(String) && !model.empty? ? model : nil
|
|
313
|
+
end
|
|
314
|
+
|
|
290
315
|
def extract_initial_prompt(extra_args)
|
|
291
316
|
return nil unless extra_args.is_a?(Array)
|
|
292
317
|
|