Web UI
harn web up boots a Next.js dashboard that reads .harnery/ directly from disk. There’s no daemon, no server-side database, and no API key to manage. The intended audience is the operator sitting in front of the monorepo.
What you see
Section titled “What you see”- Dashboard (
/): every active and stale agent as a card with name, age, platform, last-tool, model, task, and turn summary. File claims table at the bottom. - Per-agent page (
/agents/<id>): full heartbeat detail, claimed files, parsed journal entries newest-first, last 60 events emitted by this agent. - Councils (
/councils+/councils/<id>): active / closed / archived council manifests with members, rounds, and per-round contributions rendered inline. - Workflows (
/workflows+/workflows/<run-id>): live and terminal workflow runs, proof packets, policy decisions, and validated workspace allocation, verification, integration, conflict, and cleanup state. - Events (
/events): last 200 lines ofevents.ndjsonwith type-filter chips and per-instance drill-through.
Realtime
Section titled “Realtime”Each page renders server-side from the latest .harnery/ state. A tiny client component subscribes to /api/stream (Server-Sent Events) and calls router.refresh() on any change. The server-side stream watches active/, councils/, journal/, and events.ndjson via fs.watch, debounces a 250 ms burst, and pushes a refresh event. A 2 s filesize poll on events.ndjson backs the watcher up: a directory fs.watch does not fire on a plain append to an existing file on Linux/WSL (inotify reports create/rename/delete for a dir watch, not a child’s modify), so without it a fresh append (a new subagent.start, say) could go unseen until an unrelated heartbeat write churned a watched directory. The poll fires only on actual growth, so idle streams cost nothing.
If the connection drops, the client backs off exponentially (1 s → 30 s) and reconnects.
Write actions
Section titled “Write actions”The dashboard is mostly read-focused, but exposes three operator escape hatches:
- Release claim: drop a file claim from any heartbeat. Useful when a session ended mid-Edit and a peer is now blocked on a path the original agent no longer cares about. Hits
POST /api/actions/release-claim. - Ping agent: write a
handoffentry to a peer’s journal, prefixedfrom web-ui:. HitsPOST /api/actions/ping. - End session: unlink a stuck heartbeat (operator-only, same effect as the stale sweep but immediate). Hits
POST /api/actions/end-session. Requires confirmation.
These actions write directly to .harnery/ files. The bash coord layer’s flock dance is for the agent-vs-agent race; the web UI is the operator’s escape hatch, single-user and low-frequency, so it needs no locks.
Production mode
Section titled “Production mode”By default harn web up runs next dev with HMR. For a long-running operator panel:
harn web build # next buildharn web start # next startOr use --prod on up once a build exists:
harn web up --prodNetwork exposure
Section titled “Network exposure”Localhost-only by design. To reach the dashboard from a different machine, use harn tunnel (IP-gated Cloudflare quick tunnel); see harn tunnel for the full setup.
Implementation notes
Section titled “Implementation notes”- All read paths live in
harnery/web/lib/coord-reader.ts. Single source of truth for the JSON shapes returned by/api/*. - The webpack
extensionAliasinnext.config.tsteaches the bundler that.tsre-exports can import.js-suffixed paths (Harnery’s source uses the ESM-spec convention; bun handles it natively, but webpack needs the hint). - The reader caches its
.harnery/root after the first resolve to avoid repeated walks. SetHARNERY_COORD_ROOTto override (and to test against a fixture).