Cadora's walk-away review, step by step: a gated run parks with exit code 75 and the laptop closes, the phone gets an ntfy notification, the reviewer triages from the dashboard through a tunnel, the decision is stored in parked-decisions.json bound to document SHA-256s and a declared identity, cadora resume applies it headlessly without re-running or re-paying the agent, drifted or unauthorized decisions are refused, a park-mode resume sweeps the decided gates and re-parks the rest, and the manifest records who decided what, when, over which bytes.

Cadora · walk-away review

A review gate on your schedule, not the laptop's

A blocking gate holds the conductor process hostage to the reviewer's calendar. Walk-away review inverts that: the run parks — agents' finished work archived, process ended cleanly — your phone buzzes, you decide from wherever you are, and a later cadora resume applies the decision without re-running or re-paying a thing.

Surface · CLI + dashboard + phoneRole · operator / reviewerCaptured · fixture run, pipeline-1
01

Park the run, close the laptop

--on-review park changes what a review gate does with the process. When design reaches its gate, the run doesn't block — it lets the wave drain (the sibling test-plan finishes and records; nothing is stranded in memory), writes one self-contained park record with every pending gate, and terminates cleanly.

zsh — cadora run · --on-review park
$ cadora run pipeline.yaml --executor claude --cwd ws --hitl \ --on-review park --reviewers "yves" \ --notify-url https://ntfy.sh/my-runs --yes ▶ wave · 2 independent nodes running concurrently (max 2) ⏸ 'design' awaits human review — parking after this wave ✓ test-plan $0.6100 integrity:ok ⏸ parked — 1 gate(s) awaiting review: design the agents' completed work is archived; nothing re-runs on resume. decide from the dashboard while parked (it applies at resume), or live at resume. continue with: cadora resume runs/pipeline-1 $ echo $? 75

Exit code 75 is EX_TEMPFAIL: "waiting for a human" is not a failure, and wrappers and schedulers can tell the two apart. The park record (runs/pipeline-1/park.json) embeds the topology, the gate specs, and the execution contract — a resume depends on nothing but the archive. The manifest stays "ok": null; in flight is the truth. The laptop closes.

02

The phone buzzes

--notify-url is an ntfy-style webhook: one POST, the body is the message. Exactly two moments reach your pocket — a gate starts waiting for review, and the run parks. Everything else is dashboard material, not a buzz.

ntfy.sh/my-runs
cadora
pipeline-1 — design awaits your review
now
cadora
pipeline-1 parked — awaiting review: design
now

The webhook is fire-and-forget — a daemon thread with a short timeout that swallows every failure. A dead endpoint never delays a node, corrupts telemetry, or fails a run. The webhook is a courtesy; the archive is the record.

03

Triage from the phone

The dashboard binds loopback (127.0.0.1:8765) and stays there — from the phone you reach it through a tunnel, e.g. tailscale serve 8765. The run wears a parked pill, and the run page opens the ⏸ Parked triage panel: each pending gate as a card — node id, cost so far, and the changed documents as links.

mba.tailnet.ts.net/runs/pipeline-1
pipeline-1claude · 4 nodes
parked
⏸ Parked1 gate(s) await review — decisions apply at cadora resume
Your name — recorded in the evidence with this decision
design$1.8342 so far
Comments — required to request changes
ApproveRequest changesAbort
✓ stored: approve — applies at resume

Mobile is triage + decide — gates you already understand. Deep reading and annotation stay on desktop, where the full review surface lives. And the dashboard's staleness check knows the difference: parked is the one state where "untouched for days" is healthy, not a zombie.

04

The decision is stored, not lost

A parked run has no live process to receive a verdict, so the dashboard stores it in the archive: runs/pipeline-1/parked-decisions.json. The binding is the point — the decision is tied to (a) that one node, (b) the SHA-256 of the exact bytes the reviewer saw, hashed at decision time, and (c) the declared identity, re-checked at resume against the recorded allowlist.

runs/pipeline-1/parked-decisions.json
{ "design": { "decision": "approve", "comments": "ship the API as drafted", "reviewer": "yves", "method": "dashboard", "decided_at": "2026-07-22T09:14:07+00:00", "documents": [ { "path": "aidlc-docs/inception/application-design/design.md", "kind": "new", "sha256": "ff70…" } ] } }

Written atomically, one entry per node. A second decision for the same gate is refused with a conflict — one gate, one stored verdict.

05

Back at the laptop: cadora resume

Headless — no reviewer present, none needed. The resume rebuilds the run from the park record, applies the stored decision, and drives the downstream nodes to completion.

zsh — cadora resume
$ cadora resume runs/pipeline-1 --yes resuming 'pipeline-1': 1 parked gate(s) — design ▸ applying the decision made while parked: approve by yves via dashboard ✓ design $1.8342 integrity:ok ✓ construction $2.4031 gate:tests ok integrity:ok ✓ run complete -> runs/pipeline-1

What the resume does and does not repeat: the parked node's agent work is not re-run and not re-paid — $1.8342 is the same dollar, recorded once. The deterministic gate is re-checked, and the workspace fingerprint is verified (drift refused unless --allow-drift, and recorded in the evidence either way). Completed outputs reload verbatim from the archive, so downstream prompts render byte-identical to a never-parked run. And the parked downtime lands in review_wait_seconds — never in the node's signed duration_seconds.

06

The rails, shown

A stored decision is honored only if it still describes reality. Edit a document after the decision and the SHA binding shows its teeth — the verdict is discarded loudly (telemetry event parked_decision_discarded) and the gate re-asks rather than certify bytes nobody reviewed.

zsh — a document changed after the decision
$ cadora resume runs/pipeline-1 --yes resuming 'pipeline-1': 1 parked gate(s) — design ✗ discarding the decision made while parked for 'design': document 'aidlc-docs/inception/application-design/design.md' changed after the decision was made — the gate re-asks

Identity faces the same wall. A stored decision whose reviewer is not in --reviewers is rejected at resume exactly like a live impostor — including abort: an unlisted abort cannot kill a run. A rejection never re-runs the agent and never consumes a revision; the gate simply remains open.

zsh — an identity outside the allowlist
✗ decision by mallory via dashboard rejected — not in the --reviewers allowlist; the gate remains open

Every stored decision is consume-once: it is taken from the file before it is judged, so a rejected verdict cannot lurk and retry forever. And the allowlist itself is not overridable at resume — the policy recorded when the run parked is the policy that governs it.

07

The triage sweep

Waves can park more than one gate — two reviewed siblings park once, together, in a single record. When only some are decided by morning, resume in park mode: --on-review park applies whatever was decided and re-parks the rest.

zsh — the sweep, when a park holds two gates
$ cadora resume runs/pipeline-1 --on-review park resuming 'pipeline-1': 2 parked gate(s) — design, threat-model ▸ applying the decision made while parked: approve by yves via dashboard ✓ design $1.8342 integrity:ok ⏸ 'threat-model' awaits human review — parking after this wave ⏸ parked — 1 gate(s) awaiting review: threat-model the agents' completed work is archived; nothing re-runs on resume. decide from the dashboard while parked (it applies at resume), or live at resume. continue with: cadora resume runs/pipeline-1 $ echo $? 75

Exit 75 again, and park.json now holds only the undecided gates. Re-parking is stable: an undecided gate parks again with nothing executed and nothing re-charged — a park is idempotent until a human actually decides.

08

The evidence remembers

The manifest's human_reviews carries the whole story: who decided, through which surface, over exactly which bytes — and when the human decided, not when the resume got around to applying it. review_policy records the allowlist that was in force.

runs/pipeline-1/manifest.json — the design node
"human_reviews": [ { "decision": "approve", "comments": "ship the API as drafted", "timestamp": "2026-07-22T09:14:07+00:00", // the moment the human decided "reviewer": "yves", "method": "dashboard", "documents": [ { "path": "aidlc-docs/inception/application-design/design.md", "kind": "new", "sha256": "ff70…" } ] } ], … "review_policy": { "reviewers": ["yves"] }

Both duration_seconds and review_wait_seconds flow into the signed evidence pack — the agent's work and the human's deliberation, each honestly its own number. The pack itself is the evidence-pack journey.

The frames above reproduce real cadora run / resume output and the live dashboard's parked triage panel — rendered as editable HTML rather than raster captures, so they can be lifted into Figma or iterated directly. Walk-away review is the durable form of the human-in-the-loop gate: same decisions, same evidence, no laptop held hostage. Flags and file formats are detailed in the user manual.