Skip to content

Engineering archive

These notes describe work at the date shown. See the usage reference for current behavior.

FLOW: review loop, result card, dashboard replies (stages 6.1–6.3)

Date: 2026-09-10. Base: faf6eff437390dc1f996b498f9fb8aae48c6c83d. Revised after root-contract-review-1.md and protocol-decision.md (close fence + PROTOCOL_VERSION 7).

Goal

Make the operator loop explicit: ready for review → follow-up → accepted, without changing default CLI close-on-done compatibility, without auto-merge or laptop shell execution, and without calling agent-reported checks “verified by mac-worker”. Dashboard replies and acceptance go through the same TaskClient::say / TaskClient::close paths the CLI uses, fenced by durable CAS so two clients cannot overwrite each other’s turn.

Architecture

Reuse, do not replace:

  • Task states queued|active|open|closed|abandoned|lost and ClosePolicy::{Done,Never}.
  • TaskOutcome as the agent result (done is not human acceptance).
  • ClientStateStore::update_task_if_current (full-record CAS already used by cancel).
  • Saved task project context (task_project_path) — never request filesystem paths.
  • Existing fetch/import ref refs/remotes/mac-worker/<worker>/task/<id> and fetched_head.
  • Settings POST protections: loopback Host, matching Origin, JSON content-type, bounded body, custom header.
  • TaskClient::say / close (CLI signatures unchanged). Dashboard is another client of those methods.
  • PROTOCOL_VERSION 7 (FLOW version commit). Typed reported_checks on TaskStatus. v6 mismatches at preflight. Spec: docs/superpowers/specs/2026-09-10-protocol-v7.md. Persisted JobMeta/fingerprint identity is not the wire constant; storage split is in /private/tmp/mac-worker-roadmap-jn00iibh/protocol-storage-contract.md (root review before that sweep).

Review projection (derived, not a new durable state):

Durable factsUI review_state
open + close_intentclose_pending
open + needs_inputwaiting_on_you
open + doneready_for_review
open + other terminal outcomeready_for_follow_up
closed + close_policy=never + doneaccepted (human close)
closed + close_policy=done + doneclosed_after_done (legacy auto-close)
other closed/abandoned/lostclosed
queued/activenot_reviewable

Default --close-on done stays. Iterative review: worker task submit --close-on never then worker task say / worker task close. No new worker task accept CLI verb.

Agent result checks and host TaskStatus.reported_checks are the same typed records (source: agent_reported). Parser defaults when omitted; status omits the field when empty so old on-disk records round-trip. Absent → UI not reported. Do not scrape logs for checks.

Constraints

  • No auto-merge, no dashboard command endpoint, no executing review git/shell from the server.
  • UI mutex / stale precheck is not the fence. CAS is update_task_if_current against the caller’s expected snapshot (say_from_expected / close_from_expected) with expected state, last turn id, turn count, head, updated_at. Dashboard must not discard load_matching and let TaskClient reload a fresh CAS base.
  • Production dashboard reply uses DetachedRunnerExecutor. Snapshot and detail skip remote refresh while close_intent is set; review_state stays close_pending until local Closed + intent cleared.
  • rollback_followup may restore only the record this client just CAS-committed (expected = that Active snapshot). A loser must not restore a stale Open record over a winner, and must only delete its own prompt/queue row.
  • Lost HTTP response: retry with the same expected Open revision must not append another turn (CAS miss / 409).
  • Close fence is durable close_intent on still-Open. Remote task_close runs after the fence. Local Closed/accepted only after remote ack (or idempotent Closed retry). Transport failure leaves intent, not Closed.
  • Do not complete close inside reconcile_runners. Skip status refresh while intent is set.
  • serde(default) is one-way (new reader of old disk). New work mismatches at probe preflight (PROTOCOL_MISMATCH) before jobs. Persisted tasks may refuse at the next host request (INCOMPATIBLE_PROTOCOL / DTO validate) before task or lease writes; that path does not need a second probe. Client and helper upgrade together (worker setup).
  • PERF owns DashboardService / snapshot GET lifecycle. FLOW UI must render collection.freshness=stale even on HTTP 200 using original generated_at_millis. 503 DASHBOARD_SNAPSHOT_PENDING is loading.
  • ENV owns batch/project CLI. FLOW exposes declared_acceptance_instructions for ENV to prepend; result checks stay agent-reported.
  • SCHED owns queue dispatch. Do not edit reconcile-repair or auth-incident worktrees.
  • Rebuild src/dashboard/static/app once after UI source is done.
  • Focused tests only; Cargo through the host slot wrapper. No full all-targets.

Interfaces

Dashboard mutations (FLOW):

POST /api/v1/tasks/{task_id}/reply
POST /api/v1/tasks/{task_id}/accept

Headers: Host = listener, Origin = http://{host}, Content-Type: application/json, X-Mac-Worker-Task: 1. Body ≤ 8192 bytes.

Request (no paths):

json
{
  "message": "optional for reply",
  "expected_task_id": "<canonical>",
  "expected_turn_id": "<last turn>",
  "expected_turn_count": 1,
  "expected_head_oid": "<40 hex or null>",
  "expected_updated_at_millis": 123,
  "expected_state": "open"
}

409 TASK_REVISION_CONFLICT on mismatch. say/close public codes otherwise (TASK_BUSY, TASK_CLOSED, FOLLOWUP_LIMIT).

DashboardHttpState.mutation_source is optional (tests stay read-only unless they install a fake). Production command.rs wires MacWorkerTaskMutationSource using existing Config + ClientStateStore + PathLayout and the production DetachedRunnerExecutor. Tests inject a recording executor and an after_expected_check barrier.

Deep links: #/tasks/<id> selects on load and on hashchange (back/forward). No new server routes.

Close tests (two TaskClients): transport failure after fence; remote success + lost response; crash/retry of durable intent; concurrent say during close; stale rollback must not overwrite a winner. Dashboard mutation tests: barrier after load_matching, competing turn completion, 409 with zero enqueue/close/start; reply starts exactly one follow-up; close_intent snapshot+detail skip remote refresh.