# Harness v2 Parity & Daily-Use Design

audience: AI coding agents first. Owner-readable plain English required in Behavior lines.

**Goal:** ONE harness (v2), source-controlled in overdeck, feature-complete enough for daily use: owner queues a chain of dependent plans in the morning and relaxes.

**Status legend:** LANDED / IN-FLIGHT / APPROVED-TODO / PENDING-AUDIT (awaiting owner approval of audit findings).

---

## 1. Ground rules (bind every task below)

- v1 is DEAD (owner order 2026-07-29, hard cutover). NEVER port v1 code bodies; re-implement behavior in v2 idiom. Deny-gate blocks v1 tokens.
- **No daemon.** State lives on disk (registry `~/.harness/v2/runs/`, journals `runstate/v2/`), NEVER inside a long-lived process. Background behavior = systemd units/timers firing short idempotent scripts that read disk, act, exit.
- Fail-closed everywhere: unknown preset → refuse launch; failed plan → dependents never start.
- Canonical source = overdeck `modules/harness` (post-consolidation). Deployed engine bundles are immutable releases cut FROM overdeck; never hand-edit a bundle except during this transition.
- Implementation via codex (`cdx exec`); Claude orchestrates + verifies.

## 2. Already decided + in motion

### 2.1 Consolidation — IN-FLIGHT (codex job running)
Import bundle 0.1.84 v2 content (bin lib presets skills spec v2 web wrappers VERSION) into overdeck `modules/harness`, replacing stale v1 snapshot 0.1.27. TUI source (v2-only, mega-plan-harness@edb8b4a) → `modules/harness/tui`. Retire harnessd + harnessd-daily units (v1 daemon). `runplan tui` subcommand in the PATH shim → `~/.local/bin/runplan-tui`. Strip legacy paragraph from `runplan` help. Land via ship.sh, deploy.

### 2.2 Flatten codex preset — APPROVED-TODO (queued behind 2.1)
`presets/codex.json` still v1 tiered shape (`seats.coder.{low,medium,high}`); v2 resolves `seats.<seat>.wrapper` flat → `--preset codex` fails: "seat coder wrapper must be a non-empty path".
**Contract:** flatten to one variant per seat per owner cost policy — coder `wrappers/codex.sh`+`gpt-5.6-terra-medium`, reviewer `gpt-5.6-sol-low`, fixer `gpt-5.6-terra-medium`, resolver `gpt-5.6-sol-medium`. Timeouts: coder/fixer 3600, reviewer/resolver 1200. Edit BOTH deployed bundle (chmod u+w, restore) and overdeck copy.
**Acceptance:** `runplan <any-plan> --preset codex` passes preset load (fails later only for plan-specific reasons); v2 seats tests pass.

### 2.3 Plan queue — APPROVED-TODO (owner: "queuing might actually be a good idea… long queue of plans and execute and relax")
**Contract:**
- Queue file `~/.harness/v2/queue.jsonl` — one JSON record per line: `{repo, slug, preset?, account?, added_at, status: pending|running|done|failed|skipped}`. File is sole state; no queue process between runs.
- CLI: `runplan queue add <slug> [--repo-root <path>] [--preset <n>] [--account <s>]` (append) · `runplan queue` (list with statuses) · `runplan queue start` (work the queue) · `runplan queue skip <slug>` (mark skipped, owner-deliberate) · `runplan queue rm <slug>` (pending entries only).
- `queue start`: strictly sequential — launch entry as a normal v2 run (same unit machinery as direct `runplan`), wait for terminal state, on success proceed to next, **on failure STOP** (dependents never start). Idempotent + resumable: re-running `queue start` resumes at first pending entry; crash/reboot loses nothing.
- Surfacing: queue state visible in `runplan queue`, TUI, and overdeck plans panel (registry records carry a `queue` marker or collector reads queue file — implementer picks the lean seam).
- Independent plans stay launchable directly in parallel; queue is for dependency chains only. No DAG — linear order IS the dependency model (YAGNI).
**Acceptance:** integration test — queue 3 toy plans where #2 fails its gate → #1 done, #2 failed, #3 still pending; fix #2, `queue start` → resumes at #2, completes to #3.

## 3. Known v1→v2 gaps (confirmed today; owner decides keep/drop after audit)

| Gap | v1 behavior | v2 today |
|---|---|---|
| Per-task model tiers | plan task `tier` low/med/high picked coder model per task | flat single coder seat; tier ignored |
| Seat fallback chains | failed seat retried on named fallback (e.g. reviewer.high→reviewer.medium) | unverified, likely absent |
| Daemon services | harnessd: queueing/watching/daily maintenance (exact scope PENDING-AUDIT) | none; §2.3 queue + timer-based reconcile replace the needed parts |
| `--runconfig` per-run config file | per-run config override | only `--preset`/`--account` flags |
| Rich run timeline in API | per-run step timeline embedded in /runs (73MB bloat — also v1's disease) | lean registry records; TUI/UI thinner |

Timeline note: if richness is needed, serve per-run timeline from a per-run endpoint/file on demand, NEVER embedded in the list payload (that bloat is a v1 trap, do not reproduce).

## 4. Watching / auto-resume — PENDING-AUDIT
If audit confirms harnessd auto-resumed dead runs: implement as systemd timer → stateless reconcile script: read registry+journals, find runs whose unit died mid-run, relaunch `runplan <slug>` (v2 resume semantics), exit. No state of its own.

## 5. Audit findings — APPROVED-TODO
Owner approved numbered contracts below from completed v1→v2 parity audit. Implement only these entries; audit findings without an approved entry remain out of scope.

<!-- APPEND approved audit items below as numbered contracts (seam + behavior + acceptance), same shape as §2. -->

### Phase 1 — Trust core

Build trust controls before unattended operation. Every later phase assumes a task cannot pass without independent review, bounded repair, and durable failure accounting.

#### 5.1 Review phase + independent reviewer — APPROVED-TODO

**Seam:** `modules/harness/v2/run.js` adds a post-dispatch, pre-gate review phase; `modules/harness/v2/seats.js` resolves a read-only `reviewer` binding independently from the coder binding; `modules/harness/v2/journal.js` persists review attempts and verdicts.

**Behavior:** Send high-risk and unknown-risk task diffs to `reviewer`. Give reviewer read-only worktree access and require a schema-valid `PASS|FAIL` verdict with findings. Prefer a different provider/model from coder; refuse self-review when another healthy binding exists. Invalid, missing, or non-passing verdicts fail closed and never reach commit.

**Acceptance:** `node --test --test-name-pattern='review' modules/harness/v2/test/run-quality.test.js` passes: independent reviewer selected, write attempt rejected, malformed verdict blocked, `FAIL` blocked, `PASS` proceeds.

#### 5.2 Gate-fix escalation ladder — APPROVED-TODO

**Seam:** `modules/harness/v2/quality.js` owns ordered quality transitions; `modules/harness/v2/run.js` invokes it after each gate red.

**Behavior:** Stop at first green rung: deterministic dependency repair → normal `fixer` → stronger `fixer`. Re-run exact failed check, then full strict gate, after every rung. Restore unauthorized fixer edits before one constrained retry. Exhaustion quarantines task with evidence; it never weakens gate policy or edits protected check configuration.

**Acceptance:** `node --test --test-name-pattern='gate-fix' modules/harness/v2/test/run-quality.test.js` passes: rungs execute in order, each green stops ladder, repeated red escalates, scope escape restores files, exhaustion quarantines.

#### 5.3 Ordered seat fallback chains — APPROVED-TODO

**Seam:** `modules/harness/v2/seats.js` exports fail-closed binding-chain resolution from `spec/presets.schema.json` `fallback` values.

**Behavior:** Follow inline or referenced bindings only for engine-down or two repeated wrapper timeouts. Preserve declared order, reject missing targets, detect cycles, cap chain at four hops, and journal every attempted binding. Never fall back on task/code/gate failures. Reviewer fallback still enforces §5.1 independence when a healthy independent binding exists.

**Acceptance:** `node --test modules/harness/v2/test/seats-quality.test.js` passes: ordered success, ineligible failure rejection, missing reference rejection, cycle rejection, four-hop cap, independent reviewer constraint.

#### 5.4 Durable retry ledger + repeated-failure stop-loss — APPROVED-TODO

**Seam:** `modules/harness/v2/journal.js` projects retry state keyed by `{runId,taskId,failureClass,failureFingerprint,baseHead,taskHead}`; `modules/harness/v2/run.js` consults it before dispatch.

**Behavior:** Preserve counts across coordinator restarts. Default to three identical attempts across all quality/fallback paths. Do not redispatch an identical quarantined failure on unchanged integration/task heads. Head or fingerprint change starts a distinct ledger entry. Stop-loss is mandatory and fail-closed; no run setting disables it.

**Acceptance:** `node --test --test-name-pattern='retry|stop-loss' modules/harness/v2/test/journal-quality.test.js modules/harness/v2/test/run-quality.test.js` passes across journal reopen, changed-head reset, third-attempt exhaustion, and unchanged-head suppression.

#### 5.5 Rate-limit parking — APPROVED-TODO

**Seam:** `modules/harness/v2/dispatch.js` handles wrapper exit `75`; `runstate/v2/<slug>/<run>/wake` is external wake input; `modules/harness/v2/run.js` journals parking heartbeats and resumes.

**Behavior:** Park task until provider reset time, wake-file signal, or bounded probe. Emit heartbeat while parked. Preserve attempt/worktree state. Allow at most eight parks per ledger key, then quarantine. Parking consumes no coder/fixer attempt and never blocks healthy fallback providers.

**Acceptance:** `node --test --test-name-pattern='rate-limit|park' modules/harness/v2/test/dispatch-parking.test.js modules/harness/v2/test/run-quality.test.js` passes: reset wake, external wake, heartbeat, fallback admission, restart-safe count, ninth-park quarantine.

#### 5.6 Flatten remaining tiered presets — APPROVED-TODO

**Seam:** `modules/harness/presets/{anthropic-less,canary,grok}.json` expose one flat binding per seat.

**Behavior:** Flatten `anthropic-less.coder` to current `medium` binding (`wrappers/ca.sh`, `composer-2.5`); flatten `canary.coder` and `canary.reviewer` to their identical current `medium` bindings; flatten `grok.coder` to current `medium` binding (`wrappers/grok.sh`, `grok-composer-2.5-fast`, timeout `1200`). Preserve already-flat fixer/resolver bindings. Remove tier maps and tier-only fallbacks.

**Acceptance:** `node modules/harness/presets/_validate.mjs && node modules/harness/v2/test/index.js` passes; all three presets resolve every declared seat through v2 flat binding semantics.

### Phase 2 — Supervision, recovery, watchdog, resource limits

Phase 2 follows Phase 1: automatic restart without stop-loss and durable retry state would amplify repeated failures.

#### 5.7 Per-run supervision + automatic crash recovery — APPROVED-TODO

**Seam:** template `modules/harness/systemd/harness-run@.service`; short idempotent `modules/harness/v2/bin/reconcile-runs.js`; `modules/harness/systemd/harness-reconcile.timer` invokes reconcile and exits.

**Behavior:** One systemd unit owns one run; no monolithic daemon. Reconcile registry+journal state, adopt an already-live matching generation, mark dead coordinators crashed, and resume with 30s/2m/8m durable backoff. Stop after three restarts. Unknown ownership or incomplete identity fails closed.

**Acceptance:** integration test kills coordinator across three generations and reboots timer state: run resumes at durable phase, backoff receipts survive process exit, fourth restart is refused, unrelated runs continue.

#### 5.8 Hung-run watchdog — APPROVED-TODO

**Seam:** short idempotent `modules/harness/v2/bin/watchdog.js` reads heartbeat and attempt-ownership receipts; systemd timer invokes it.

**Behavior:** Detect stale heartbeat, verify PID start time/process group/cgroup/generation, terminate full owned process tree, journal evidence, and hand recovery to §5.7. PID-alive alone is never ownership proof. Identity mismatch fails closed without signaling process.

**Acceptance:** integration test hangs a worker with a descendant: owned tree dies and run recovers; reused/mismatched PID survives and incident is journaled.

#### 5.9 Worker resource limits — APPROVED-TODO

**Seam:** `modules/harness/v2/child.js` launches each worker in a transient user systemd scope and records scope identity.

**Behavior:** Apply configured memory, CPU, process, and file-size limits. Refuse unattended launch when systemd scope creation fails; foreground fallback may use `setsid` only when explicitly supported and still records ownership. Resource-limit termination classifies as infrastructure evidence, not task success.

**Acceptance:** integration test exceeds each supported limit: scope contains descendants, journal names tripped limit, retry waits for teardown, no descendant survives.

### Phase 3 — Durable queue + run windows

Phase 3 follows supervision: queue advancement must rely on durable terminal receipts and automatic recovery.

#### 5.10 Linear durable plan queue — APPROVED-TODO

**Seam:** `~/.harness/v2/queue.jsonl`; `runplan queue add|list|start|skip|rm`; short idempotent queue worker launched by systemd.

**Behavior:** Use linear order as dependency model. Advance only after prior entry has durable success/landing receipt. Failure stops queue with later entries pending. Re-run resumes first pending/failed-retry entry without duplicating a live run. Direct independent runs remain available.

**Acceptance:** queue three toy plans with #2 failing: states become `done,failed,pending`; fix #2 and restart: #2 then #3 complete exactly once.

#### 5.11 Run windows — APPROVED-TODO

**Seam:** queue entries accept named/explicit local-time window; `runplan queue start --now` is explicit bypass; queue worker uses persisted next-eligible timestamp.

**Behavior:** Hold pending entries outside window, survive timezone/DST/reboot, and launch at most once when eligible. Running work continues past window end. `--now` bypass is journaled.

**Acceptance:** fake-clock tests cover before/inside/after window, DST transition, reboot, and `--now`; each eligible entry launches once.

### Phase 4 — Telegram notifications + reports

Phase 4 follows durable terminal/recovery state so notifications and rollups derive from receipts, not process lifetime.

#### 5.12 Telegram via Botmaster — APPROVED-TODO

**Seam:** extend Botmaster existing `/api/bots/:id/messages` route with authenticated `POST {chat_id,text,idempotency_key}`. Botmaster resolves OverdeckBot credentials and performs its existing Telegram call: `POST https://api.telegram.org/bot${BOT_TOKEN}/sendMessage` with `{chat_id,text}`. Harness `modules/harness/v2/notify.js` addresses bot `OverdeckBot` and configured channel `Overdeck`; harness never reads bot token.

**Behavior:** Send lifecycle failure, stop-loss, decision-required, disk-floor, terminal, and daily-rollup messages. Persist outbox records before send; retry three times with backoff; deduplicate by idempotency key; channel failure never mutates run outcome. Current Botmaster has GET/DELETE only on this route, so POST is required before harness integration.

**Acceptance:** contract test posts twice with same key: Botmaster performs one Telegram `sendMessage`; failure persists retryable outbox; success marks receipt; harness fixture contains no bot token.

#### 5.13 End-of-run report + daily rollup — APPROVED-TODO

**Seam:** per-run `report.json` beside journal; short idempotent daily timer aggregates terminal receipts and calls §5.12.

**Behavior:** Report outcome, task/review/fix/retry/parking evidence, landing receipt, unresolved failures, and exact artifact paths. Daily rollup groups completed, failed, stopped, parked, and queued runs without embedding full event streams.

**Acceptance:** golden tests derive identical report after restart; daily timer rerun produces one rollup and one Telegram receipt for same date.

### Phase 5 — Control API, mutable config, UI, revisions, stream

Phase 5 follows supervision and queueing because lifecycle mutation requires a durable owner outside the HTTP request.

#### 5.14 Full run lifecycle control API + Overdeck UI — APPROVED-TODO

**Seam:** authenticated loopback API exposes `POST /runs`, `/runs/:id/{kill,pause,resume,steer}`; Overdeck web plans panel surfaces same actions through its server gateway.

**Behavior:** Validate identity and revision, write durable intent, return idempotent receipt, and let per-run unit/reconcile script enact it. Kill full owned tree; pause/resume systemd scope; steer queues text for next prompt and optionally restarts current wrapper safely. UI shows pending/applied/failed intent; no optimistic success.

**Acceptance:** API+Playwright tests exercise kill/pause/resume/steer; duplicate requests reuse receipt; PID reuse cannot target foreign process; UI reflects journal-confirmed state.

#### 5.15 Mutable run-settings API + Overdeck UI — APPROVED-TODO

**Seam:** `GET/PATCH /config`, `GET/PATCH /runs/:id/config`; revision via `ETag`/`If-Match`; Overdeck settings panel renders schema/catalog.

**Behavior:** Change future defaults and mutable operational settings for not-yet-started attempts. Reject unknown, immutable, secret, stale-revision, or invalid fields atomically. Never expose secrets. Apply concurrency/timeouts/retry-safe settings only at named phase boundaries.

**Acceptance:** API+UI tests cover successful patch, stale `412`, atomic invalid rejection, redaction, restart persistence, and phase-boundary application.

#### 5.16 Plan revisions — APPROVED-TODO

**Seam:** `PATCH /runs/:id/plan` with expected revision; append-only revision archive beside copied run plan.

**Behavior:** Add/remove/update only unstarted tasks. Started task edits become explicit follow-up tasks; completed task history is immutable. Validate graph, waves, file claims, and session task pointers before atomic swap. Recover interrupted edit from archive.

**Acceptance:** tests edit pending tasks mid-run, reject started/completed mutation, reject invalid graph without partial write, and recover pre-swap crash.

#### 5.17 Global live event stream — APPROVED-TODO

**Seam:** authenticated `GET /stream` SSE over global registry+journal tail; `Last-Event-ID` replay; Overdeck consumes one stream.

**Behavior:** Broadcast run snapshots, journal events, queue changes, rate-limit parks, control intents, and heartbeats. Use monotonic durable event IDs, bounded replay, reconnect, and slow-client eviction. List endpoints remain lean.

**Acceptance:** SSE tests disconnect/reconnect without loss or duplication; slow client cannot block writers; Overdeck updates without polling.

### Phase 6 — Maintenance + provider health

Phase 6 follows supervision because cleanup and circuit probes require authoritative leases and ownership receipts.

#### 5.18 Safe worktree GC — APPROVED-TODO

**Seam:** short idempotent GC script + systemd timer reads Git worktree registry, run leases, and journal state.

**Behavior:** Reap only old, clean, unoccupied worktrees. Preserve dirty trees and precious ignored files. Archive unmerged commits before removal. Unknown lease/ownership blocks deletion. Emit recoverable archive path.

**Acceptance:** fixture matrix proves clean expired removal, dirty/leased/active preservation, unmerged archive, idempotent rerun, and fail-closed unknown state.

#### 5.19 Disk-space floor — APPROVED-TODO

**Seam:** admission probe before new run/task plus short disk timer; durable cooldown alert receipt.

**Behavior:** Refuse new work below red free-space floor, track 24-hour loss trend, leave active work untouched unless its own write fails, and notify once per cooldown through §5.12.

**Acceptance:** fake-filesystem tests cover threshold, trend, cooldown dedupe, recovery, and active-run non-interference.

#### 5.20 Provider circuit breaker — APPROVED-TODO

**Seam:** durable provider-health projection from journal; short two-minute probe timer; dispatch admission consults circuit state.

**Behavior:** Open after repeated infrastructure failures, park only affected provider, permit healthy ordered fallback, probe every two minutes, and close after verified recovery. Task/code failures never trip circuit. Restart reconstructs exact state from disk.

**Acceptance:** tests open/park/fallback/probe/close across restart; unrelated provider continues; task failure leaves circuit closed.

### Phase 7 — Landing modes

Phase 7 follows trust, recovery, and reports because publication requires durable quality and delivery receipts.

#### 5.21 Repository `ship.sh` delegation — APPROVED-TODO

**Seam:** `modules/harness/v2/land.js` invokes repository `.claude/scripts/ship.sh` through a versioned adapter contract and persists intent/receipt.

**Behavior:** Delegate guarded landing policy to repository script. Pass immutable integration/base/run identity; require machine-readable receipt; serialize publication lease; replay crash between intent and receipt without duplicate publication. Missing/invalid script fails closed.

**Acceptance:** integration tests cover successful merge, rejected lease, crash replay, malformed receipt, and no direct push when delegation selected.

#### 5.22 `deploy-verify` landing mode — APPROVED-TODO

**Seam:** plan `land_mode:"deploy-verify"` selects §5.21 adapter; receipt includes published commit, deployment target, and verification evidence.

**Behavior:** Require trust-core pass, deploy, and verification of exact published tree before terminal success. Verification failure enters bounded landing repair; product-semantic failure stops for explicit owner input. Never report done from push/deploy receipt alone.

**Acceptance:** end-to-end fixture proves exact-tree deploy+verify success, mismatched tree failure, bounded repair, crash replay, and terminal report containing verification receipt.
