The Receipt Protocol — Detection-Only Delivery Confirmation for Dispatched Tasks
The Broader Goal at the End of the Previous PR
Issue #720 identified three deferred sub-projects growing out of the dispatch fail-closed
work (design doc: docs/superpowers/specs/2026-08-07-dispatch-fail-closed-task-validation-design.md).
Sub-project 1 — fail-closed empty-task validation — shipped in PR #759: synlynk dispatch
now refuses to send an empty or whitespace-only task string to an agent, closing a class of
silent no-op dispatches. That left two deferred pieces: a receipt protocol confirming an
agent actually received the task text it was dispatched, and (still outstanding) a third
sub-project not covered by this PR.
The gap sub-project 1 didn't close: fail-closed validation only guards against Synlynk sending nothing. It says nothing about whether the agent's own CLI harness actually delivered that non-empty task to the model before doing work — a truncated prompt pipe, a CLI flag mismatch, or a race in headless invocation could all still result in an agent starting "work" against a task it never actually read.
Strategic Shifts in This PR (if any)
None. This PR is exactly the sub-project 2 scope described in #720 and the approved design
spec (docs/superpowers/specs/2026-08-07-receipt-protocol-design.md): cross-CLI
task_received confirmation between Synlynk and each headless harness, with digest matching
and a new task_delivery_failed job status. No scope was added or cut during implementation.
What This PR Shipped
The core mechanism is deliberately dumb: detection-only, no protocol negotiation with the
agent CLI. Synlynk prepends a fixed instruction to every dispatched prompt asking the agent
to print SYNLYNK_TASK_RECEIVED: <task_sha256> as its literal first line of output, then
Synlynk greps for that line in the captured log after the fact. No handshake, no blocking
wait, no changes to any agent CLI's own protocol — just an echo Synlynk can verify or fail to
find.
Seven tasks landed, each dispatched independently to Codex or Agy via synlynk dispatch <agent> --task "..." --force-agent --context-mode full --base chore/receipt-protocol-design,
verified against its own diff and test run before merging locally into the feature branch
(consolidating into this one PR rather than the per-task auto-opened PRs, which were closed
as superseded):
_render_task_receipt_instruction(task_sha256)insynlynk/dispatch.py— builds the prompt-injection text; wired into_format_prompt_for_agent()for all three prompt-shape branches (Claude, Codex, generic) so it's agent-agnostic by construction._check_task_receipt(log_text, task_sha256)insynlynk/jobs.py— classifies a job's captured log intook(marker is the literal first non-blank line),late(marker present but not first),mismatch(a receipt marker is first but the digest is wrong), orabsent._classify_task_delivery(receipt_status, has_corroborating_activity)— the false-positive guard. A receipt failure alone doesn't hard-fail a job: if the job's worktree shows real git activity (has_activityorremote_has_activity), the job is downgraded to a non-blocking WARN instead oftask_delivery_failed. This mirrors the existingpermission_deniedfalse-positive lesson from job-b88e0f92 — a status label should never override direct evidence of real, correctly-scoped work.- Waitpid-reaped reconciliation wiring — the classification is applied in the
if waitpid_reaped:branch of_reconcile_jobs(), settingjob["status"] = "task_delivery_failed"on hard failure or writing aTASK_RECEIPT_WARNsentinel alert on soft failure. - Dead-pid reconciliation wiring — the structurally parallel edit in the
except ProcessLookupError:branch, since a job whose process died before Synlynk could reap it needs the same classification applied independently (different code path, same log-based evidence). live_agent_receipt_check()insynlynk/fleet.py— a new Tier-2selftest --matrixcell (live_receipt:<agent>) that runs one real headless CLI turn per Core-4 agent (Claude, Codex, Grok, Agy) with a fixed test digest and checks compliance live, not just in unit tests against captured logs. Wired intorun_matrix_live()'s existing budget-loop pattern (mirroring thelive_agent_smokecell'smock=Truestub andspent/budget_usdaccounting rather than introducing a divergent loop shape).- README documentation — a short paragraph after the existing fail-closed guard section
explaining the receipt marker, the
task_delivery_failedstatus, and the WARN corroboration fallback.
Test coverage: 80 tests across tests/test_dispatch.py and tests/test_jobs.py covering the
receipt-detection state machine (ok/late/mismatch/absent/none), the corroboration
classification (hard-fail/warn/clean), and both reconciliation branches (waitpid-reaped and
dead-pid) with a real local-git-repo fixture for the activity-corroboration case; plus 4 new
tests in tests/test_fleet_operability.py for the live matrix cell. Full combined suite: 108
passing.
Two operational lessons surfaced during execution, both resolved without losing work: a
permission_denied status on the dead-pid-branch task (job-70a6ef9a) and again on the
selftest-matrix task (job-47fe2f30) turned out to be false positives — direct inspection of
each job's worktree diff and a live test run confirmed correctly-scoped, spec-matching
commits in both cases, consistent with the repo's standing "never trust synlynk jobs status
alone" memory. Separately, a merge/push cycle for one task silently no-op'd because the Bash
tool's working directory had persisted inside a job's own worktree from a prior cd — caught
immediately via git log origin/<branch> --oneline -1 disagreeing with the expected tip, and
recovered by re-verifying pwd/git branch --show-current before retrying the merge.
Brainstorm Visuals Used
None — this sub-project's design was scoped directly in the #720 spec and design doc from a prior brainstorming session; no new visual companion session was run for this PR.
What This Achieved on the Path to Autonomy
A dispatched job's completion status can now distinguish "the agent did the work" from "the agent's CLI never actually saw the task" — without that distinction, a silently-dropped prompt looks identical to a successful no-op job, which is exactly the kind of failure mode autonomous dispatch loops can't self-correct from if nothing flags it. The WARN corroboration guard keeps this from becoming a new source of false-positive noise: a missing receipt marker next to a real, verifiable commit is a signal worth surfacing, not a reason to discard correct work.
Strategic Note: The Goal at the End of This PR
Sub-project 2 of #720 is complete. Sub-project 3 (not yet scoped in this PR) remains
outstanding per the original #720 breakdown — the next PM pass should confirm what it covers
and whether it's still needed given the receipt protocol and fail-closed guard now both
shipped. Separately, the selftest --matrix receipt cell adds real (budgeted) API cost per
run across all four Core-4 agents — worth watching in project-docs/costs.md once this ships
and starts running against CI or scheduled selftest invocations.