harnex 0.8.0 → 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9142cc69cec0a0cf0dc3961ccc27dc3728baea445c7d83b4c2d69a34b7e6a499
4
- data.tar.gz: b89c616cb2484d48e74ef88985a438f7ebb1c22b1af17dd44cbaab3ee0c020cd
3
+ metadata.gz: 4158bd117bb14da526926a4f80e61c0d4809b5fb401ff718c2b64ddeb0075730
4
+ data.tar.gz: 6c2b75a37fbe9dbc9236b02898e9672c30af2bf72716c1c83152006760ff67de
5
5
  SHA512:
6
- metadata.gz: 220566d1c646ca49b7f41f8ed053b2909dda8468ab3dc877bfcfe5f47b52988492d54b9d2fcc42ff0e42154921fde97af0824688b27df69e94bd9fc49b77c2f8
7
- data.tar.gz: e52eca128c1f078171ab991acd25bafa810e8341a7f6c8f199535cd03b7a881ee03f2c4193afb01ece92cc00d0a8025a6bdb564739aeb13cbe6a02a4f4d87d0a
6
+ metadata.gz: 016be4a78e559a075d8477e99343290098025f0e0cf70538da97b647c281a94c8fbf3f704276dd5ba6d00d8d3f92321c16b4f875e6849e302037fb6bb2199cac
7
+ data.tar.gz: 8cec64fc830b8a37238b5ea1d0068d4422190ffb76dc0f936a10de95b6928512fdf25cf032ec5990e8c3a6e32d2012d4d0fac41f5946f68ad4e74b2e25ebce3a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,93 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.0] - 2026-08-03 | 01:11 AM | IST
4
+
5
+ Minor bump: the durable dispatch row family is now v2, `--summary-out` changes
6
+ from a default destination to an explicit mirror, and retention/phase policy add
7
+ operator-visible behavior.
8
+
9
+ ### Added
10
+
11
+ - **Price-table cost for token-reporting adapters** (#63, plan 33
12
+ Phase 2; subsumes #58's cost gap): new `lib/harnex/pricing.rb` holds a
13
+ static per-1M-token USD rate table keyed by provider + model, each entry
14
+ `as_of`-dated and hand-copied from the provider pricing pages
15
+ (OpenAI gpt-5.x/codex and Anthropic Claude families, rates as of
16
+ 2026-08-02; update procedure documented in the file header).
17
+ `build_summary_usage` applies it only when `cost_usd` is null, usage
18
+ status is `observed`/`zero`, and the effective model matches the table —
19
+ then `cost_source: "price_table"` and a new always-present
20
+ `usage.cost_price_as_of` field record provenance. Unknown models stay
21
+ null; provider-reported cost (Pi) is never overwritten; costs are never
22
+ backfilled.
23
+ - The codex app-server adapter now captures the effective model from the
24
+ schema-required `model` field on `thread/start` / `thread/resume`
25
+ responses, so `agent.model_effective` (and price-table lookup) resolves
26
+ without the caller passing `--meta '{"model": ...}'`.
27
+ - Token-semantics are capture-path-aware: new adapter hook
28
+ `usage_input_includes_cached?` distinguishes codex app-server JSON
29
+ (cached ⊆ input → billable input = input − cached) from the codex PTY
30
+ transcript line (input excludes cached → cached prices additively).
31
+ Verified against the captured schema fixtures and a live app-server row.
32
+ - Price-table lookup now covers Holm's effective `gpt-5.5` model and keys its
33
+ tier-sensitive rates by the recorded service tier (`standard`, `flex`, or
34
+ `fast`; `priority` aliases fast). Because published `gpt-5.5` rates split at
35
+ 272K active context, Harnex also requires an observed peak below that boundary;
36
+ missing/long-context evidence remains unpriced rather than applying the wrong
37
+ short-context rate.
38
+ - Optional repo phase policy in `.harnex/config.json`: an allowlist can `warn`
39
+ or `reject` non-canonical effective `meta.phase` values before spawn. Explicit
40
+ malformed policy files fail closed; no config preserves existing behavior.
41
+ - Bounded events/output retention: 45-day and 1-GiB defaults per directory,
42
+ repo config plus environment overrides, current/live-session protection,
43
+ oldest-first age/size pruning, one-hour automatic throttle, and bounded
44
+ last-prune metadata. `harnex doctor` reports size/limits; `--prune --dry-run`
45
+ previews bounded candidate paths and `--prune` applies the policy.
46
+
47
+ ### Changed
48
+
49
+ - **Single tracked telemetry stream** (#63, plan 33 Phase 1): a dispatch
50
+ now writes exactly two rows to the repo-tracked `.harnex/dispatch.jsonl`
51
+ — the `dispatch_start` row and one unified v2 `dispatch_end` row that
52
+ merges the thin envelope (top-level `schema_version: 2`, `record_type`,
53
+ `id`, `status`, `tier`, timing, `tmux_state`, …) with the rich summary
54
+ sections (`meta`, `predicted`, `actual`, `agent`, `usage`, `context`,
55
+ `attribution`, `outcome`, `attempt`, `reliability`, `queue?`,
56
+ `orchestration?`, artifact-report keys). Start rows stamp
57
+ `schema_version: 2` as well. Readers accept v1 and v2 rows mixed in one
58
+ file; `harnex history` keeps skipping pre-0.7.3 envelope-less rows.
59
+ - `--summary-out` is now an explicit-only mirror: no default path. When
60
+ set, the identical v2 end record is appended there in addition to the
61
+ tracked stream. Consumers that redirected `--summary-out` to keep rich
62
+ rows should drop the flag — the tracked stream now carries everything.
63
+ `Harnex.default_summary_out_path` is removed; every writer and reader
64
+ resolves the stream through `DispatchHistory.path_for` (git-root walk,
65
+ global fallback), so non-git roots stream to the global file instead of
66
+ a repo-local `.harnex/` directory.
67
+ - The `summary` event now points `path` at the tracked stream and carries
68
+ `mirror_path` when a mirror is configured.
69
+ - `TerminalStatus` resolves a v2 end row as both summary and history in
70
+ one shot (branching on `record_type` first), so `wait --until done` and
71
+ `status --id` fall back to the unified stream; legacy duck-types remain
72
+ for pre-v2 files.
73
+ - Cross-dispatch attempt fields are harness-derived from the canonical stream:
74
+ `attempts_total`/succeeded/failed, `fallback_triggered`, and
75
+ `reliability.recovered` follow bounded parent links and degrade safely on
76
+ missing, duplicate, malformed, or cyclic history. `fallback` is now a public,
77
+ live-parent-guarded attempt kind; in-run `retry_count` remains separate.
78
+ - Public telemetry/configuration references under `docs/*.md` are now packaged
79
+ in the gem. README, GUIDE, TECHNICAL, agent guides, and event/telemetry docs
80
+ use the same canonical-stream, mirror, native-watch, pricing, phase-policy,
81
+ and retention terminology.
82
+
83
+ ### Fixed
84
+
85
+ - `harnex history` no longer renders blank rows for pre-0.7.3
86
+ `{meta, predicted, actual}`-schema telemetry rows in
87
+ `.harnex/dispatch.jsonl`: rows recognized as neither start nor end
88
+ records are skipped in both table and `--json` output. The raw file is
89
+ untouched — legacy rows remain available for forensics.
90
+
3
91
  ## [0.8.0] - 2026-08-02 | 08:46 PM | IST
4
92
 
5
93
  Minor bump: two behavior changes below (`wait --until done` exit codes,
data/GUIDE.md CHANGED
@@ -8,11 +8,11 @@ Treat harnex as a local supervisor harness, not as a conversation
8
8
  bus between agents.
9
9
 
10
10
  - Start a fresh worker for each step, usually with `--tmux`
11
- - Send one clear task, often by pointing the worker at a file
12
- - Use `--wait-for-idle` as a fence, then inspect with `harnex pane`
13
- - Ask the worker to write its output to a file when the next step
14
- needs structured input
15
- - Stop the worker when that step is done
11
+ - Give one clear startup task with `--context`; add `--auto-stop` for one-shot work
12
+ - Monitor existing visible work with `harnex watch --until done`
13
+ - Use `--wait-for-idle` only as a follow-up-send fence, then verify the artifact
14
+ - Ask the worker to write its output to a file when the next step needs it
15
+ - Stop completed interactive workers promptly
16
16
 
17
17
  For multi-step flows, chain fresh workers with file handoffs:
18
18
  Codex writes a plan, another Codex implements it, Claude reviews it,
@@ -49,14 +49,18 @@ automatically when the agent is ready. You don't have to wait
49
49
  or retry. Queueing exists, but the default workflow should still be
50
50
  one task per fresh worker.
51
51
 
52
- For unattended dispatch, prefer built-in monitoring over external poll loops:
52
+ For unattended visible dispatch, prefer the native work-level watcher over
53
+ external poll loops:
53
54
 
54
55
  ```bash
55
- harnex run codex --id impl --tmux impl --watch --preset impl
56
+ harnex run codex --id impl --tmux impl \
57
+ --context "Read and execute /tmp/task-impl.md" --auto-stop
58
+ harnex watch --id impl --until done --max-wait 90m
56
59
  ```
57
60
 
58
- This adds a foreground watcher that checks idle activity and performs bounded
59
- force-resume nudges. For full flag behavior and event-stream consumers, see
61
+ `harnex run --watch --preset impl` is a separate foreground launch-and-stall
62
+ babysitter; do not combine it with `--tmux` or `--detach`. For full flag
63
+ behavior and event-stream consumers, see
60
64
  [TECHNICAL.md](TECHNICAL.md) and the built-in monitoring section in
61
65
  [README.md](README.md).
62
66
 
@@ -262,4 +266,7 @@ harnex agents-guide # deeper agent-facing guidance
262
266
  ## What's next
263
267
 
264
268
  For the full command reference, flags, HTTP API, and internals,
265
- see [TECHNICAL.md](TECHNICAL.md).
269
+ see [TECHNICAL.md](TECHNICAL.md). The packaged
270
+ [dispatch telemetry](docs/dispatch-telemetry.md) and
271
+ [configuration](docs/configuration.md) references cover the canonical v2 stream,
272
+ phase allowlists, and log retention.
data/README.md CHANGED
@@ -60,9 +60,10 @@ job, watch it work, stop it when done.
60
60
  ### Run from a temporary/public bundle
61
61
 
62
62
  Use `--cwd DIR` when the worker should see a specific directory rather than
63
- the orchestrator's current repo. Harnex starts the wrapped agent in `DIR`, sets
64
- that directory as the session root, and resolves default telemetry such as
65
- `.harnex/dispatch.jsonl` there:
63
+ the orchestrator's current repo. Harnex starts the wrapped agent in `DIR` and
64
+ uses it for session attribution. Canonical dispatch telemetry is written to the
65
+ enclosing git root's `.harnex/dispatch.jsonl`; a non-git directory uses the
66
+ global `~/.local/state/harnex/dispatch.jsonl` fallback:
66
67
 
67
68
  ```bash
68
69
  harnex run codex --cwd /tmp/leximaze_eval_run_001 \
@@ -252,14 +253,18 @@ Schema details and compatibility policy are documented in
252
253
 
253
254
  ## Dispatch history
254
255
 
255
- Every finished `harnex run` writes dispatch records. By default, the terminal
256
- summary JSONL path is `<session-root>/.harnex/dispatch.jsonl`; `--cwd DIR` makes
257
- `DIR` the session root, including for non-git temporary bundles. The compact
258
- history record is repo-local in a git tree and falls back to
259
- `~/.local/state/harnex/dispatch.jsonl` outside git.
260
- `harnex history` reads the compact records from that location, and
261
- `harnex status --id ID --json` / `harnex wait` can use the same durable
262
- terminal summaries when the live session registry is already gone.
256
+ Every `harnex run` writes exactly one v2 `dispatch_start` row at registration
257
+ and one rich v2 `dispatch_end` row at teardown. Both use the canonical stream:
258
+ `<git-root>/.harnex/dispatch.jsonl` inside a git repo, or
259
+ `~/.local/state/harnex/dispatch.jsonl` otherwise. The end row combines the
260
+ history envelope with usage, context, attribution, outcome, attempt,
261
+ reliability, queue/orchestration, and optional artifact-report sections.
262
+
263
+ `--summary-out PATH` has no default. It is an explicit compatibility mirror
264
+ that copies the identical `dispatch_end` row to another JSONL file; do not pass
265
+ it merely to preserve rich telemetry. `harnex history`,
266
+ `harnex status --id ID --json`, and `harnex wait` all read the canonical stream
267
+ when the live registry is gone. Mixed legacy v1 and v2 rows remain readable.
263
268
 
264
269
  Use `harnex history` to inspect it:
265
270
 
@@ -312,17 +317,17 @@ harnex run pi --project-id harnex --queue-id queue-005 --entry-id SP-4 \
312
317
  --phase implement --intent queue-work --require-attribution ...
313
318
  ```
314
319
 
315
- Soft budget metadata is copied into summary `meta`; queue/agent/reliability
316
- metadata is copied into top-level `queue`, `agent`, and `reliability` summary
317
- blocks. Every terminal row also has `usage` (so null is distinguishable from
318
- explicit zero or an estimate), `attribution`, `outcome`, and a joinable
319
- per-session `attempt` block. Terminal summary `actual` records timing, exit
320
- classification, token usage when the adapter can capture it, adapter-reported
321
- `cost_usd` when reliably available, git deltas, task-completion state,
322
- operational counters (`stalls`, `force_resumes`, `disconnections`,
323
- `tool_calls`, `commands_executed`), output/event log paths, and rough volume
324
- measurements such as `lines_changed`, `output_lines`, `output_bytes`, and
325
- `event_records`.
320
+ Soft budget metadata is copied into end-row `meta`; queue/agent/reliability
321
+ metadata is copied into top-level `queue`, `agent`, and `reliability` blocks.
322
+ Every end row also has `usage` (so null is distinguishable from explicit zero or
323
+ an estimate), `attribution`, `outcome`, and a joinable `attempt` block. When an
324
+ adapter reports tokens but no cost, Harnex may compute provider list-price cost
325
+ only for an exact maintained provider/model/service-tier/context-rate match; such rows use
326
+ `usage.cost_source: "price_table"` and carry `usage.cost_price_as_of`.
327
+ Provider-reported cost remains authoritative, estimates remain labelled, and
328
+ unknown rates stay null. `actual` also records timing, exit classification, git
329
+ deltas, task-completion state, harness-derived cross-dispatch attempt counts,
330
+ operational counters, output/event paths, and bounded volume measurements.
326
331
 
327
332
  Long queue runners can also opt into logical primary-orchestrator rollups:
328
333
 
@@ -423,7 +428,7 @@ See [recipes/03_buddy.md](recipes/03_buddy.md) for the full pattern.
423
428
  | `harnex history` | List completed dispatches from `.harnex/dispatch.jsonl` |
424
429
  | `harnex wait --id <id>` | Block until process exit by default; use `--until done` for unattended work completion or `--until task_complete` for exact structured turn completion |
425
430
  | `harnex artifact-report init\|validate PATH` | Create or validate bounded `harnex.artifact_report.v1` proof; use `validate --final` before strict completion |
426
- | `harnex doctor` | Run adapter dependency preflight checks; add `--sweep` for read-only session drift diagnostics |
431
+ | `harnex doctor` | Run dependency and retention diagnostics; `--sweep` reports session drift, `--prune --dry-run` previews retention, and `--prune` applies it |
427
432
  | `harnex guide` | Getting started walkthrough |
428
433
  | `harnex agents-guide` | Agent-facing dispatch, chain, buddy, monitoring, and naming guides |
429
434
  | `harnex recipes` | List and read tested workflow patterns (`show 01`, `show buddy`) |
@@ -442,6 +447,8 @@ longer used. Remove stale `~/.claude/skills/harnex-*` or
442
447
 
443
448
  - [GUIDE.md](GUIDE.md) — getting started walkthrough with examples
444
449
  - [TECHNICAL.md](TECHNICAL.md) — full command reference, flags, HTTP API, architecture
450
+ - [docs/dispatch-telemetry.md](docs/dispatch-telemetry.md) — v2 dispatch stream and field contract
451
+ - [docs/configuration.md](docs/configuration.md) — phase allowlists and events/output retention
445
452
 
446
453
  ## License
447
454
 
data/TECHNICAL.md CHANGED
@@ -29,6 +29,9 @@ harnex run codex -- --cd ~/other/repo
29
29
  | `--watch-file PATH` | Auto-send a file-change hook (`--watch PATH`/`--watch=PATH` legacy) |
30
30
  | `--context TXT` | Give the agent a task on startup |
31
31
  | `--auto-stop` | With `--context`, stop after accepted task completion |
32
+ | `--meta JSON` | Attach bounded caller metadata |
33
+ | `--summary-out PATH` | Mirror the canonical v2 dispatch end row to another JSONL file |
34
+ | `--phase TEXT` | First-class phase attribution (optionally repo-allowlisted) |
32
35
  | `--artifact-report PATH` | Expose and ingest a bounded v1 proof sidecar |
33
36
  | `--require-artifact-report` | Fail closed unless the sidecar is accepted final proof |
34
37
  | `--fast` | For Codex, use `service_tier="fast"` instead of default `flex` |
@@ -39,6 +42,20 @@ activity, Git delta, or fresh accepted/no-change report as
39
42
  `completed_no_activity`. Strict artifact-report mode works across transports
40
43
  and returns non-zero for missing, invalid, rejected, or stale proof.
41
44
 
45
+ ### `harnex doctor` — Preflight, drift, and retention
46
+
47
+ ```bash
48
+ harnex doctor
49
+ harnex doctor --sweep
50
+ harnex doctor --prune --dry-run
51
+ harnex doctor --prune
52
+ ```
53
+
54
+ Plain output includes adapter prerequisites and events/output retention status.
55
+ `--sweep` adds read-only live-session/tmux drift diagnostics. `--prune --dry-run`
56
+ previews age/size-cap deletions; `--prune` applies them while preserving current
57
+ and live-session files. See [docs/configuration.md](docs/configuration.md).
58
+
42
59
  ### `harnex artifact-report` — Initialize and validate proof
43
60
 
44
61
  ```bash
@@ -115,8 +132,10 @@ harnex history
115
132
  harnex history --json | jq .
116
133
  ```
117
134
 
118
- Reads `<repo>/.harnex/dispatch.jsonl`, where `<repo>` is found by walking up
119
- until `.git/` is present. Use `--global` for the no-repo fallback file.
135
+ Reads the canonical v2 stream at `<git-root>/.harnex/dispatch.jsonl`; use
136
+ `--global` for `~/.local/state/harnex/dispatch.jsonl` outside a repo. Each run
137
+ has one `dispatch_start` plus one rich `dispatch_end`. `--summary-out` is an
138
+ explicit-only end-row mirror, not a second default stream.
120
139
 
121
140
  ### `harnex logs` — Read session transcripts
122
141
 
@@ -246,8 +265,9 @@ Transport file (append-only JSONL):
246
265
  ```
247
266
 
248
267
  Each row uses schema v1 with envelope fields `schema_version`, `seq`, `ts`,
249
- `id`, and `type`. Emitted today: `started`, `send`, `exited`. `send.msg` is a
250
- 200-character preview with `msg_truncated` when shortened.
268
+ `id`, and `type`. Lifecycle, send, usage, git, summary, attempt, completion,
269
+ and adapter-specific events are additive; consumers must ignore unknown event
270
+ types. `send.msg` is a 200-character preview with `msg_truncated` when shortened.
251
271
 
252
272
  Schema details and compatibility guarantees are in [docs/events.md](docs/events.md).
253
273
 
@@ -300,7 +320,8 @@ When you run `harnex run codex --id worker`:
300
320
  hash(repo_root + id) % port_span + base_port
301
321
  walk forward until a free port is found
302
322
  6. Start HTTP server on 127.0.0.1:<port>
303
- 7. Write registry file:
323
+ 7. Opportunistically enforce configured age/size retention for events/output,
324
+ preserving current and live-session files; then write registry file:
304
325
  ~/.local/state/harnex/sessions/<repo_hash>--<id>.json
305
326
  and open transcript file:
306
327
  ~/.local/state/harnex/output/<repo_hash>--<id>.log
@@ -0,0 +1,172 @@
1
+ # Codex `app-server` adapter
2
+
3
+ harnex 0.6.0 talks to Codex over JSON-RPC 2.0 instead of scraping a
4
+ PTY pane. The adapter spawns `codex app-server` as a subprocess,
5
+ exchanges newline-delimited JSON-RPC messages on stdin/stdout, and
6
+ fans server notifications into the harnex events log.
7
+
8
+ ## Transport
9
+
10
+ - Subprocess: `codex app-server` (CLI ≥ 0.128.0 — verify with
11
+ `harnex doctor`).
12
+ - Wire format: one JSON object per line.
13
+ - Encoding: UTF-8.
14
+ - One `Adapter#transport` value: `:stdio_jsonrpc`.
15
+
16
+ ## Handshake
17
+
18
+ Mirrors `codex-plugin-cc/plugins/codex/scripts/lib/app-server.mjs`.
19
+
20
+ ```ruby
21
+ client.request("initialize", {
22
+ clientInfo: { title: "harnex", name: "harnex", version: Harnex::VERSION },
23
+ capabilities: {
24
+ experimentalApi: false,
25
+ optOutNotificationMethods: %w[
26
+ item/agentMessage/delta
27
+ item/reasoning/summaryTextDelta
28
+ item/reasoning/summaryPartAdded
29
+ item/reasoning/textDelta
30
+ ]
31
+ }
32
+ })
33
+ client.notify("initialized", {})
34
+ ```
35
+
36
+ After the handshake the client is ready to issue `thread/start` and
37
+ `turn/start` requests.
38
+
39
+ ## Notification → event mapping
40
+
41
+ | Server notification | harnex event | Notes |
42
+ |-----------------------------|--------------------|-------|
43
+ | `thread/started` | (metadata) | Stashes `threadId` |
44
+ | `turn/started` | `turn_started` | Carries `turnId` |
45
+ | `turn/completed` | `task_complete` or `task_failed` | Failed/interrupted statuses emit `task_failed` with the Codex error. A completed `--context` turn emits `task_complete` only with structured command/tool activity, Git delta, or fresh accepted/no-change report proof; otherwise it emits typed `completed_no_activity`. |
46
+ | `item/started` | (silent) | Streaming deltas opted out |
47
+ | `item/completed` | `item_completed` + synthesized transcript | See "tmux/STDOUT" below |
48
+ | `error` | `error` | Turn-level Codex error notification; preserves nested `error.message` and does not count as a transport disconnect. |
49
+ | `thread/status/changed` | (state only) | Drives state machine |
50
+ | `thread/tokenUsage/updated` | (status field) | Surfaced via `harnex status --json` |
51
+ | `thread/compacted` | `compaction` | Increments `compactions` counter |
52
+ | `account/rateLimits/updated`| (silent, status) | Visible in `status --json` |
53
+
54
+ ### How disconnects are detected
55
+
56
+ Disconnects are detected from subprocess exit / EOF on stdout and parse errors
57
+ when the server emits a malformed line. Request-level JSON-RPC error responses
58
+ reject only the in-flight request; they do not by themselves imply that the
59
+ transport disconnected. Schema-defined `error` notifications are turn-level
60
+ Codex errors and feed `last_error` / `task_failed` rather than the disconnect
61
+ counter.
62
+
63
+ Unexpected transport loss emits `disconnected` and increments
64
+ `auto_disconnects`. Normal auto-stop teardown after `task_complete` /
65
+ `task_failed` is treated as a clean structured close. There is no need for the
66
+ screen-text regex that the legacy adapter relied on.
67
+
68
+ ## tmux / STDOUT — synthesized transcript
69
+
70
+ Without a PTY, `harnex run codex --tmux` and `harnex pane --id …`
71
+ would otherwise see an empty pane. The JSON-RPC path renders a
72
+ synthesized transcript built from `item/completed` notifications:
73
+
74
+ - `agent_message` items render their text payload
75
+ - `tool_call` items render as `tool: <name> <one-line summary>`
76
+
77
+ The synthesized transcript is written to BOTH the output log AND
78
+ STDOUT, so:
79
+
80
+ - `harnex run codex` (foreground) — user sees the transcript live
81
+ - `harnex run codex --tmux` — the tmux window shows the transcript
82
+ - `harnex pane --id <session>` — captures the synthesized text
83
+ - `harnex logs --id <session>` — replays the same transcript
84
+
85
+ For interactive debugging where the original Codex TUI is wanted,
86
+ `codex resume <thread-id>` opens the same thread in a real Codex
87
+ CLI.
88
+
89
+ ## `harnex wait --until done` / `task_complete` / `task_failed`
90
+
91
+ For unattended monitors, block until Codex work completes, fails, or the session exits:
92
+
93
+ ```
94
+ harnex wait --id cx-i-242 --until done --timeout 300
95
+ ```
96
+
97
+ When you need the exact successful structured turn event, wait for `task_complete`:
98
+
99
+ ```
100
+ harnex wait --id cx-i-242 --until task_complete --timeout 300
101
+ ```
102
+
103
+ `--until done` returns non-zero when it sees `task_failed` or failed terminal
104
+ telemetry. This includes acknowledgment-only autonomous `--context` turns
105
+ (`outcome_class=completed_no_activity`) and strict sidecar failures
106
+ (`report_missing`, `report_invalid`, or `report_rejected`). Harnex classifies
107
+ these from app-server item counters, Git state, and the configured report path;
108
+ it does not inspect final-answer prose. The task-complete/task-failed waiters
109
+ tail the events JSONL — not the API socket — so they keep working across
110
+ restarts and are adapter-agnostic.
111
+
112
+ For blind dispatches that require report proof, combine
113
+ `--artifact-report PATH --require-artifact-report`. Only a fresh valid sidecar
114
+ at `PATH` can satisfy the contract; report-shaped JSON printed in an
115
+ `agentMessage` is ordinary transcript text and is ignored. Use
116
+ `harnex artifact-report init PATH` and `harnex artifact-report validate PATH
117
+ --final` to avoid hand-authoring the schema.
118
+
119
+ ## `harnex doctor`
120
+
121
+ Verifies the Codex CLI is installed and at version ≥ 0.128.0. JSON
122
+ output, exit 0 if healthy.
123
+
124
+ ```
125
+ $ harnex doctor
126
+ {"ok":true,"checks":[{"name":"codex","required":">= 0.128.0","ok":true,"found":"0.128.0"}]}
127
+ ```
128
+
129
+ ## Long-term fallback: `--legacy-pty`
130
+
131
+ The pre-0.6.0 PTY adapter remains available as a long-term supported
132
+ fallback:
133
+
134
+ ```
135
+ harnex run codex --legacy-pty
136
+ ```
137
+
138
+ It's the right tool when you want the full Codex TUI live in tmux —
139
+ status bars, tool diffs, ANSI panels — that the headless `app-server`
140
+ backend doesn't render. JSON-RPC remains the default and is recommended
141
+ for autonomous worker dispatches; legacy-pty is for interactive/TUI use.
142
+
143
+ ## Troubleshooting
144
+
145
+ - **`task_complete` never fires.** Check `harnex events --id <session>` first:
146
+ failed Codex turns emit `task_failed` with the provider/model error. If there
147
+ is neither `task_complete` nor `task_failed`, run `harnex doctor`; Codex <
148
+ 0.128.0 is unsupported.
149
+ - **Empty tmux pane.** Codex hasn't emitted any `item/completed`
150
+ yet — the agent is reasoning. The pane fills as soon as the
151
+ first item completes.
152
+ - **`task_failed` immediately after dispatch.** Check
153
+ `harnex events --id <session>`. Provider/model failures retain their Codex
154
+ error message. `completed_no_activity` means the turn ended with no
155
+ command/tool or Git/report proof; `report_missing` / `report_invalid` /
156
+ `report_rejected` identify strict sidecar defects. Common provider failures
157
+ include auth environment variables (for example `OPENAI_API_KEY` /
158
+ `AZURE_OPENAI_API_KEY`) and model unavailability.
159
+
160
+ ## Schema fixtures
161
+
162
+ `test/fixtures/codex_appserver/schema/` holds hand-pruned subsets
163
+ of `ServerNotification` and `ClientRequest` for the methods harnex
164
+ issues / consumes. Regenerate via:
165
+
166
+ ```
167
+ codex app-server generate-json-schema --out /tmp/codex-schema-X
168
+ ```
169
+
170
+ then re-prune. The full bundle is ~3 MB; the pruned subsets are
171
+ < 50 KB and serve as a compact reference for what's actually wired
172
+ through the adapter.
@@ -0,0 +1,111 @@
1
+ # Repository Configuration
2
+
3
+ Harnex has one optional repository configuration file:
4
+
5
+ ```text
6
+ <git-root>/.harnex/config.json
7
+ ```
8
+
9
+ It is resolved with the same git-root rule as the canonical dispatch stream.
10
+ Non-git launches use the global dispatch stream and do not invent a global
11
+ configuration file. An absent file is valid. An explicitly present malformed
12
+ file fails before agent spawn so policy is never silently ignored.
13
+
14
+ ## Phase allowlist
15
+
16
+ Repositories can normalize queue/work phase names:
17
+
18
+ ```json
19
+ {
20
+ "phase": {
21
+ "allowlist": [
22
+ "plan-write",
23
+ "plan-review",
24
+ "plan-fix",
25
+ "test-suite",
26
+ "implement",
27
+ "code-review",
28
+ "code-fix",
29
+ "mapping",
30
+ "triage",
31
+ "docs"
32
+ ],
33
+ "policy": "reject"
34
+ }
35
+ }
36
+ ```
37
+
38
+ The effective value is the first-class `harnex run --phase TEXT` value, or
39
+ `--meta.phase` when no first-class override is supplied.
40
+
41
+ - No `phase` configuration: any phase passes.
42
+ - Allowlisted phase: dispatches silently.
43
+ - `policy: "warn"`: prints a warning and dispatches.
44
+ - `policy: "reject"`: exits non-zero before spawn and writes no dispatch row.
45
+
46
+ The allowlist must be an array of non-empty strings. Policy must be `warn` or
47
+ `reject`.
48
+
49
+ ## Events and output retention
50
+
51
+ Per-session event JSONL and output transcripts live under Harnex's local state
52
+ directory and are not the durable dispatch stream:
53
+
54
+ ```text
55
+ ~/.local/state/harnex/events/
56
+ ~/.local/state/harnex/output/
57
+ ```
58
+
59
+ Defaults apply independently to each directory:
60
+
61
+ - maximum age: 45 days;
62
+ - maximum total size: 1 GiB.
63
+
64
+ Override them in repo configuration:
65
+
66
+ ```json
67
+ {
68
+ "retention": {
69
+ "events": {
70
+ "max_age_days": 45,
71
+ "max_bytes": 1073741824
72
+ },
73
+ "output": {
74
+ "max_age_days": 45,
75
+ "max_bytes": 1073741824
76
+ }
77
+ }
78
+ }
79
+ ```
80
+
81
+ Environment variables take precedence:
82
+
83
+ ```text
84
+ HARNEX_EVENTS_MAX_AGE_DAYS
85
+ HARNEX_EVENTS_MAX_BYTES
86
+ HARNEX_OUTPUT_MAX_AGE_DAYS
87
+ HARNEX_OUTPUT_MAX_BYTES
88
+ ```
89
+
90
+ Limits must be positive integers. Harnex deletes only regular files directly
91
+ owned by the `events` and `output` directories: expired files first, then the
92
+ oldest unprotected files until the size cap is met. It never follows paths
93
+ outside those directories. Files for the current session, live registry PIDs,
94
+ and alive uncompleted dispatch-start rows are protected. If protected files
95
+ alone exceed a cap, Harnex reports the directory over cap rather than deleting
96
+ them.
97
+
98
+ A bounded prune runs opportunistically when a dispatch starts. Inspect or force
99
+ the same policy with `doctor`:
100
+
101
+ ```bash
102
+ harnex doctor # sizes, limits, and last-prune status
103
+ harnex doctor --prune --dry-run # preview bounded candidate paths, do not delete
104
+ harnex doctor --prune # apply now
105
+ ```
106
+
107
+ `--dry-run` is valid only with `--prune`. Manual prune bypasses the automatic
108
+ cadence but preserves the same live/current safety rules.
109
+
110
+ Set `HARNEX_STATE_DIR` before launching Harnex when tests or isolated automation
111
+ need a disposable local-state root.