type: meta

slug: harness-gated

topic: Harness sub-project deliverables requiring Plan A landed (control-api + adapter catalog/validator + Web UI)

exec_mode: ship

scheduler: dag-parallel

land_mode: merge-to-main

base_branch: main

gate0_cmd: bash run-tests.sh

status_enum: BLOCKED PENDING WORKING WRITTEN REVIEWED COMMITTED

truth_rule: this file is ground truth for INTENT (goals/directions/task list); git is ground truth for DONE-NESS — reconcile statuses against git, never trust a COMMITTED line with no commit

refresh_how: native Read of THIS file — NEVER ctx_execute/ctx_search

refresh_what: meta + every goal/methodology/int/direction record + all gated records + all WORKING and BLOCKED tasks (skip COMMITTED lines)

refresh_when:
wave/phase boundary
any task status change
after a compaction system-reminder
before calling advisor

update_rule: on status change, rewrite that task's single line in place; append new tasks/session_memory as new lines

report_rule: at session end emit brief: done / blocked / deferred / canceled — NO next action


type: goal

text: Build the three Plan-A-dependent harness tracks: control-api (per-wrapper session continuity + --health/--list-models, events normalizer, liveness sidecar, run-registry pointer, --supervised control plane, Node-stdlib HTTP+SSE server), adapter catalog+validator cross-check (presets/adapters.json + spec/adapters.schema.json + presets/_validate.mjs), and the full Web UI (Astro+React scaffold, gateway endpoints, run-store, SSE hook, DAG island, Overview/Job/Agent/Adapter/OKF pages).


type: methodology

text: Contract-level TDD per task: failing tests from the pinned seam, minimal body, one acceptance check, commit. Control-api = Node stdlib zero-dep; wrappers = POSIX shell; validator = ajv-free JSON-Schema 2020-12; web = Astro/@astrojs/node standalone + React + Tailwind + xyflow + zustand. Fail-closed everywhere. Wrapper edits target repo-vendored canonical wrappers/*.sh (NOT global ~/.claude/skills). One editor per wrapper file (session-continuity + adapter modes merged into ca-T1/T2/T3 to avoid same-wave file overlap).


type: intent

text: Everything here builds on Plan A's landed artifacts (wrappers/*.sh, src/runner.js, src/dynwf-driver.js, presets/, presets/_validate.mjs). The Web UI additionally consumes bin/okf + bin/harness-adapter from the harness-nongated plan.


type: direction

verbatim: all the gated specs and tasks for after plan a lands


verbatim: No stubs/placeholders/workarounds in DELIVERABLE code — full functionality. A contract/spec/plan that pins the seam + leaves the body to the implementer is the CORRECT shape, NOT a stub.


verbatim: NODE_AUTH_TOKEN for GitHub Packages auth is NEVER committed; .npmrc carries no literal token, sourced from developer env at install; .gitignore excludes any token file.


verbatim: control-api binds 127.0.0.1 only, no auth, single-user; ?token= shared-secret hook STUBBED for eventual non-local case.


type: deferred

text: Attachment-send in the Agent steer composer

reason: Non-goal v1 per web-ui spec — text-only steer for now.


type: gated

category: policy

needs: Plan A (mega-plan-harness) merged to main

why: Every task here edits or builds on Plan A's landed artifacts (wrappers/*.sh, src/runner.js, src/dynwf-driver.js, presets/, presets/_validate.mjs). Running before Plan A lands = editing files that do not yet exist / colliding with Plan A's live integration branch and worktrees.

status: RESOLVED

answer: proceed

resolved_by: agent-verified

source: author


needs: Plan harness-nongated landed (bin/okf + bin/harness-adapter on main)

why: The Web UI gateway (ui-T13) shells bin/okf and bin/harness-adapter; the OKF page (ui-T21) and Adapter page (ui-T20) depend on them transitively. Those binaries are deliverables of the harness-nongated plan, not this one — cross-plan dependency expressed as a gate, not a task->task deps edge.

answer: proceed


type: anchor

what: spec (adapter-config; component 4 = --health/--list-models)


type: task

desc: na.sh — session continuity (--session-id, stream-json flip, session_id status line) + --interactive stub + --health/--list-models modes

status: COMMITTED

blocker: g1: Plan A merged to main


desc: ca.sh — create-chat session continuity (chatId pre-step, --resume bind, session_id status line) + --health/--list-models modes


desc: codex.sh — exec->--json, thread_id capture from first thread.started event, verdict reparse from item.completed, steer-forbidden/kill-allowed + --health/--list-models modes


desc: lib/normalize-events + spec/events.schema.json — 3 dialects (claude/cursor/codex) -> events/v1, position-derived deterministic seq, garbled line -> one error event, stateless per line


desc: lib/liveness — liveness/v1 ephemeral sidecar runstate/<slug>.live.json, set/get/reap, kill -0 reap, journal-wins on conflict (never source of truth)


desc: runner.js + dynwf-driver.js — additive global run-registry pointer write at run start (runId=sha256(repoRoot\0worktree)[:12]--slug -> ~/.harness/runs/<runId>.json), both planes, reap-on-read. Moved to wave 1 (file-disjoint from all other wave-1 tasks).


desc: adapter catalog (presets/adapters.json, adapters/v1) + spec/adapters.schema.json — additionalProperties:false, id+wrapper required, wrapper-path-UNIQUE invariant, no arbitrary-exec field (listModels bool gates --list-models)


desc: WRAPPER-CONTRACT.md + _contract-probe.sh — add session-continuity section + --health/--list-models section (additive; exit-code/flag contract untouched); probe asserts session_id on completion + both modes


desc: presets/_validate.mjs binding<->catalog cross-check — binding.wrapper resolves to a catalog adapter (unique path), binding.model in adapter.models; author/CI-time error, runtime dispatch unchanged

note: transitively gated on g1 via deps->adpg-T6 (wave-1 frontier carries requires_decision g1); engine won't reach wave 2 until wave 1 clears the gate


desc: src/supervisor.js — --supervised control plane (default OFF): liveness registration, unix socket $HARNESS_HOME/sock/<runId>.sock, kill/steer/pause/resume verbs each journaled, state-machine leased->implemented->gated->[reviewed]->committed, codex steer-forbidden, DynWF inbox plane


desc: src/control-api.js — Node stdlib http bind 127.0.0.1, runId-keyed route table (GET /runs, /runs/:id, /tasks/:tid, /stream SSE; POST steer/kill/pause/resume), Last-Event-ID replay at seq, fail-closed 409 on unsupervised run


desc: Scaffold web/ app — package.json + astro.config (@astrojs/node standalone + @astrojs/react) + .npmrc (GitHub Packages, no committed token) + tailwind.config; deps pinned (ui-primitives/tokens/editor, xyflow, elk/dagre, react-markdown+remark-gfm, zustand)


desc: Gateway endpoints web/src/pages/api/** — runId-keyed relay fronting run sockets + okf + harness-adapter; two SSE tiers (multiplexed summary + per-run), forwards Last-Event-ID upstream, fail-closed 502/404/500. Cross-plan: shells bin/okf + bin/harness-adapter (gate g2).

blocker: g2: plan harness-nongated landed (bin/okf + bin/harness-adapter)


desc: Client run-store web/src/lib/run-store.ts (zustand) — single client-truth, apply-by-seq idempotent dedupe (ignore seq <= last applied), only place SSE frames apply


desc: SSE client hook web/src/lib/use-run-stream.ts — native EventSource('/api/runs/:runId/events'), parse frame -> dispatch into run-store, relies on browser Last-Event-ID reconnect (no custom backoff)


desc: DAG island web/src/islands/Dag.tsx — @xyflow/react + elk/dagre, WaveNode/TaskNode/edge shapes, status->color, reused by Job (interactive) + Overview (fitView non-interactive mini-preview)


desc: Overview page index.astro + Overview.tsx — tile grid over not-done runs, ALL badges from ONE /api/runs/events summary stream, status->CTA map verbatim, DAG mini-preview per tile


desc: Job page runs/[runId].astro + Job.tsx — metadata panel (7 registry fields + land_mode, NO type field) + full interactive DAG + per-wave badges; opens per-run detail stream on mount


desc: Agent view runs/[runId]/agents/[agentId].astro + AgentChat.tsx — react-markdown timeline filtered to agentId, detail rail, steer composer disabled when steer-forbidden(codex)/committed with 409 reason inline, killswitch all seats


desc: Adapter page adapters.astro + Adapters.tsx — enable/disable + per-adapter config + granular model select + health via /api/adapters; descriptor {id,kind,enabled,models,availableModels?,config,health}. Cross-plan: adp-T9 adapter CLI (gate g2) + adpg-T6 catalog (this plan).


desc: OKF KB page okf.astro + OkfKb.tsx — progressive index tree, concept read/edit, sync; write failing okf doctor rejected with output shown (atomic). Cross-plan: bin/okf from harness-nongated (gate g2).


note: ca-T9 was authored in scratch at wave 2 but moved to wave 1: its files (src/runner.js, src/dynwf-driver.js) are disjoint from every other wave-1 task, so serializing it wasted parallelism. It blocks ca-T10/ca-T11 (later waves) — no wave-1 conflict.


note: Hazard solved at authoring: control-api session-continuity edits AND adapter --health/--list-models modes both touch wrappers na.sh/ca.sh/codex.sh. Merged into ONE task per wrapper (ca-T1/T2/T3 — one editor per file) to avoid same-wave file overlap.


note: Cross-plan deps (okf-T6, adp-T9, from harness-nongated) feed ui-T13/ui-T20/ui-T21 but are NOT task->task deps edges here — those ids live in the other plan's graph. Expressed as gate g2 + task-desc notes. requires_decision holds ONE gated id: ui-T13->g2; g1 reaches downstream control-api/adapter tasks transitively via deps from the g1-gated wave-1 frontier.