Engineering archive
These notes describe work at the date shown. See the usage reference for current behavior.
Herdr reporter implementation plan
For agentic workers: Use superpowers:subagent-driven-development or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox syntax for tracking.
Goal: Show every pool turn in the operator's herdr sidebar with live state and a rendered log pane, notify the MacBook's herdr when a turn ends, and make herdr availability a worker fact that doctor, setup, and workers report.
Architecture: A synchronous Unix-socket client for herdr's JSON-lines protocol inside the worker binary. On the worker, a stateless reporter driven from three existing choke points: the supervisor after the durable Running handoff, the turn terminal hook, and the task close paths. A hidden read-only host command renders a turn's log inside the herdr pane. On the laptop, a notifier at the runner's single terminal convergence point. A herdr fact collected by refresh-facts under the existing TTL.
Tech Stack: Rust 2024, std::os::unix::net::UnixStream, serde_json, existing ProcessRunner, RedactionBoundary, TaskStore, Supervisor, TurnRunner, AgentFacts, DoctorService, Installer, and CommandOutput.
Spec: docs/superpowers/specs/2026-09-08-herdr-reporter-design.md
Global Constraints
- The reporter and the notifier are best effort: no error, timeout, or absent socket may change a turn's outcome, exit code, status bytes other than the new
herdrobject, lease handling, or a runner's exit. Every socket call has a connect deadline of 500 ms and a response deadline of 3 s; the per-event budgets are 10 s at turn start, 5 s at terminal, close, and gc, and 2 s for a notification. - Nothing that crosses the socket may contain a prompt, an environment or profile value, a model string, or a filesystem path.
cwdis always the account home,follow-turnargv carries identifiers only, and messages come from the redaction boundary. - The supervisor's process-group proof, descriptor ceiling sweep,
O_CLOEXECdiscipline, closed turn-directory layout, and stdout accounting are untouched. Reporter calls run only afterdrop(guard)and inside the terminal hook, never while the supervisor guard is held before the Running handoff. - One protocol bump,
PROTOCOL_VERSION5 to 6, in Task 2 and nowhere else;SUPERVISION_VERSIONstays at 3. Every request struct keepsdeny_unknown_fields. - Reuse the existing shapes:
SystemProcessRunnerfor theherdr --versionprobe,RedactionBoundaryfor every message,SetupWarningfor the setup line,render_worker_health_with_labelsfor the per-worker line, and thetask logsrenderer for the pane. Do not add a daemon, a config file on the worker, or a state file for the reporter. - Do not touch
src/agent/*, the scheduler, the dashboard UI underui/, or the v1 batch job path except where a shared renderer moves.
Task 1: Herdr socket client and fake server
Files: create src/herdr.rs, tests/herdr_client.rs, tests/support/fake_herdr.rs; modify src/lib.rs.
- [x] Add
tests/support/fake_herdr.rs: aUnixListenerat<home>/.config/herdr/herdr.sockin a temporary home, a queue of canned JSON responses keyed by method, an optional per-method stall, and a recording of every request line. Exposerequests()so later suites can assert payload contents. - [x] Add failing tests in
tests/herdr_client.rs: request framing{"id","method","params"}plus newline; the response with the matching id is returned and a mismatched id is an error;errorbodies becomeHerdrError::Server { code, message }; an absent socket returnsHerdrError::Absentwithin the connect deadline; a stalling server returnsHerdrError::Timeoutwithin the response deadline; the descriptor isO_CLOEXECwhile open and closed after the call. - [x] Implement
src/herdr.rs:HerdrSocket::default_for_home(home)andfrom_env_or_home(env, home)(honouringHERDR_SOCKET_PATH),HerdrClient::request(method, params) -> Result<Value, HerdrError>, typed wrappers forping,workspace_list,workspace_create,tab_list,tab_create,tab_close,pane_process_info,pane_send_input,pane_report_agent,pane_report_metadata,pane_release_agent, andnotification_show, idsmac-worker:<millis>:<counter>,seqas nanoseconds, a hand-writtenDebugthat omits params. - [x] Keep the relevant subset of
herdr api schema --json(protocol 22) undertests/fixtures/herdr/schema-subset.jsonand add a test that every typed wrapper's params validate against it. - [x] Run
cargo test --locked --offline --test herdr_clientand fix regressions.
Task 2: Configuration, wire fields, and the protocol bump
Files: modify src/config.rs, config.example.toml, src/turn.rs, src/task.rs, src/task_store.rs, src/agent_facts.rs, src/protocol.rs, docs/usage.md; tests tests/project_config.rs, tests/init_command.rs, tests/task_model.rs, tests/task_turn.rs, tests/agent_facts.rs, tests/job_protocol.rs.
- [x] Add failing tests:
WorkerEntry.herdrparses and defaults tofalse;[notifications] herdrparses and defaults totrue; the example configuration parses; an unknown key in either table is still rejected;worker initround-trips a file that carries both keys. - [x] Add failing tests:
TurnSectionserializesherdr_reporteronly when true and its absence keeps canonical bytes of an existing fixture identical; the turn summary's optionalherdrobject round-trips{state, pane_id}and is absent for old records;AgentFacts.herdrround-trips every state and isNonefor oldfacts.json;PROTOCOL_VERSIONis 6 and a protocol 5 probe is refused withPROTOCOL_MISMATCH. - [x] Implement the fields with
#[serde(default, skip_serializing_if = ...)], theHerdrFactStateandHerdrTurnStateenums with snake_case tags, and bumpPROTOCOL_VERSIONto 6; updateProbeResponse::fixture()and every test fixture that pins the version. - [x] Update
config.example.tomlwith both keys and one comment each, and add a short Configuration note todocs/usage.mdnaming them;worker initkeeps writing neither, since both defaults hold. - [x] Run
cargo test --locked --offline --test project_config --test init_command --test task_model --test task_turn --test agent_facts --test job_protocol --test cli_helpand fix regressions.
Task 3: Worker reporter, follow-turn, and the hooks
Files: create src/herdr_reporter.rs, src/turn_log.rs, tests/herdr_reporter.rs, tests/follow_turn.rs; modify src/supervisor.rs, src/turn.rs, src/task_store.rs, src/gc.rs, src/cli.rs, src/lib.rs, src/task_client.rs; tests tests/supervisor.rs, tests/task_turn.rs, tests/task_gc.rs, tests/cli_help.rs.
- [x] Move the
task logsevent renderer fromsrc/task_client.rsintosrc/turn_log.rswith no behaviour change, and add a test thattask logsoutput for the recorded Codex, Cursor, and OpenCode fixtures is byte-identical before and after the move. - [x] Add failing tests in
tests/herdr_reporter.rsagainst the fake server:startfinds an existingmac-workerworkspace and never creates a second;startcloses every tab whose label starts withtask <id12>and no other tab;startcreatestask <id12> · turn <n>withfocus: falseandcwdequal to the account home;startpollspane.process_infountil the foreground is only the shell, then sends exactlyexec ~/.local/bin/worker host follow-turn <project_id> <worktree_id> <job_id>plus Enter, and gives up after 5 s without failing;startsendsreport_agentwithsource = "mac-worker", the adapter's herdr kind,working, and the title, thenreport_metadatawith title,display_agent, state labels, and the tokenstask,turn,mw_title,mw_agent,mw_outcome;terminalmaps everyTaskOutcometo the state and message of spec section 7 and re-creates a missing tab first;closereleases and sweeps;sweep_orphanscloses only tabs whose task directory is gone; every budget holds against a stalling server; every failure returnsOk(HerdrTurnState::Unavailable)and logs one line. - [x] Add a privacy test over the fake server's recordings: no prompt bytes, no environment or profile value, no model string, no
/Users/,/home/, or~in any request. - [x] Implement
src/herdr_reporter.rswithHerdrReporter::start,terminal,close, andsweep_orphans, reading the title throughTaskStore::load_meta, taking the redacted summary and questions from the finished task status, and writing diagnostics to the turn'ssupervisor.log. - [x] Hook the supervisor: call
startimmediately afterdrop(guard)inrun_turn_after_payloadwhensection.herdr_reporteris true, and store the returnedherdrobject in the turn summary; callterminalfromTurnTerminalHook::invokeafterfinish_turnfor everyTerminalPath; callclosefromTaskStore::close_lockedandclose_for_retentionafter the workspace removal; callsweep_orphansfromHostGc::rununder--apply. - [x] Add failing tests in
tests/supervisor.rsandtests/task_turn.rs: a turn withherdr_reporter = trueand no socket produces a status identical to the reference exceptherdr: {state: "unavailable"}and onesupervisor.logline; host cancel and lost reconciliation reachterminal; the agent's process group membership, the pump's stdout accounting, and the terminal log length validation are unchanged. - [x] Add the hidden
HostCommand::FollowTurn { project_id, worktree_id, job_id }: validate identifiers like the other host commands, open the job directory read-only, refuse a batch job and malformed ids (64,70), write the OSC 0 titletask <id12> · <title>, followstdout.logandstderr.logwith the offset readslog-chunkuses, render throughturn_log, print the outcome line at terminal, then block untilSIGTERMorSIGHUP. - [x] Add failing tests in
tests/follow_turn.rsfor the rendering parity, the outcome line, the log cap and tail path, the signal exit, the refusals, and that no file in the job directory is opened for writing; extendtests/cli_help.rsso the command is hidden from--helpand present in the grammar test. - [x] Run
cargo test --locked --offline --test herdr_reporter --test follow_turn --test supervisor --test task_turn --test task_gc --test cli_help --test task_commandand fix regressions.
Task 4: Laptop notifier
Files: modify src/turn_runner.rs, src/lib.rs; create src/herdr_notify.rs; tests tests/turn_runner.rs, tests/task_conversation.rs.
- [x] Add failing tests: a detached runner and an inline
submit --waitrunner each send exactly onenotification.showat terminal with titletask <id12>: <outcome>, the task title and the redacted summary or first question as body, and the sounddonefordone,requestforneeds_inputandblocked,noneotherwise;finish_publication_failurenotifies withfailed;[notifications] herdr = falsesends none; an absent socket sends none and adds less than 2 s;HERDR_SOCKET_PATHwins over the default path; the notification body contains no path or prompt. - [x] Implement
src/herdr_notify.rswithnotify_turn_finished(config, env, home, record, outcome)over the Task 1 client and call it inTurnRunner::runafterexecutereturns, for bothOkand the recorded failure, after the durable record is written and before the runner exits. - [x] Run
cargo test --locked --offline --test turn_runner --test task_conversation --test task_commandand fix regressions.
Task 5: Herdr facts in refresh-facts, doctor, setup, and workers
Files: modify src/agent_facts.rs, src/probe.rs, src/doctor.rs, src/install.rs, src/output.rs, src/protocol.rs; tests tests/agent_facts.rs, tests/agent_probe.rs, tests/doctor_command.rs, tests/setup_command.rs, tests/workers_command.rs, tests/dashboard_source.rs.
- [x] Add failing tests:
collect_agent_factsresolvesherdron the controlled host paths, records--versionthroughSystemProcessRunnerunder a 5 s deadline, checks the default socket, and pings it under the client deadlines, producing each ofavailable,not_installed,no_socket, andno_response; the probe hot path only reads the cached fact. - [x] Add failing rendering tests:
worker workers,worker doctor, andworker setupprint theherdr:line of spec section 5.3 for every state in text and carry the fact in JSON; stale or missing facts printunknown;doctoremitswarning [HERDR_UNAVAILABLE]only for aherdr = trueworker whose fact is notavailable, and never a blocker;setupcarries the same asSetupWarningCode::HerdrUnavailablewhile still reportinginstalled;dashboard_sourcepasses the fact throughproject_agent_factsunchanged. - [x] Implement the collection, the
HERDR_UNAVAILABLEissue inworker_issues, the setup warning after the verification probe inInstaller::install, and the line inrender_worker_health_with_labels. - [x] Run
cargo test --locked --offline --test agent_facts --test agent_probe --test doctor_command --test setup_command --test workers_command --test dashboard_sourceand fix regressions.
Task 6: Documentation and live acceptance
Files: modify README.md, docs/usage.md, docs/setup-macos-worker.md, docs/superpowers/specs/2026-09-03-agent-task-pool-design.md; create docs/herdr-reporter-validation.md.
- [x]
docs/usage.md: a short Herdr subsection under Dashboard describing the sidebar rows, the notifications, the two config keys, and thedoctorline. README: the "More documentation" list gains the validation record; nothing else in the README changes. - [x] Setup guide (
docs/setup-macos-worker.mdas rewritten ind162379): an optional section after "4. Install and log in to one agent" on enabling herdr on a worker (herdrrunning as the worker account,herdr status server), theherdr = truekey, rerunningworker setup, and an optional note on adding$mw_titleor$tasktokens to a custom sidebar row. - [x] v2 spec: confirm sections 22 and 23 carry the cross-reference to the herdr reporter design.
- [x] Run the live acceptance of spec section 13.2 on mac1 and record sanitized evidence in
docs/herdr-reporter-validation.md, including thetask status --jsonexcerpt with theherdrobject, thedoctorandsetuplines, and the privacy check over the fake-server recordings. - [x] Run
cargo fmt --all --check,cargo test --locked --offline --all-targets, andcargo clippy --locked --offline --all-targets -- -D warnings.
Gate
The plan is done when all six tasks are checked, the full gate is green, worker setup has been rerun on the three workers at protocol 6, and docs/herdr-reporter-validation.md records items 1 to 8 of spec section 13.2 with sanitized evidence.