watchdog: signal handoffs off claim()/review() commit prefixes (robust) + codify the convention

Replaces the brittle markdown prose-match ("Gate: … CLAIMED, awaiting Adversary") with detection of
the loops' conventional commit prefixes on origin/main: a new `claim(...)` commit pings the
Adversary; a new `review(...)` commit pings the Builder. Edge-triggered on the origin/main SHA
(append-only — no force-push), no file parsing, can't mis-route. The loops already use these
prefixes consistently; codified as a load-bearing contract in plan.md §6.1 + both prompts so it
stays reliable. INBOX detection unchanged (pushed-state, file-routed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-29 03:10:12 +01:00
co-authored by Claude Opus 4.8
parent e0e60bc2bc
commit ae83a8120d
4 changed files with 41 additions and 39 deletions
+28 -34
View File
@@ -225,45 +225,39 @@ heal_orchestrator() {
_wd_fetch_origin() { git -C "$1" fetch -q origin 2>/dev/null || true; }
_wd_show_pushed() { git -C "$1" show "origin/main:machine-docs/$2" 2>/dev/null || git -C "$1" show "origin/main:$2" 2>/dev/null || true; }
_wd_awaiting=""; _wd_baselined=""; _wd_last_review=""
_wd_adv_inbox_seen=""; _wd_builder_inbox_seen=""
handoff_reset() { _wd_awaiting=""; _wd_baselined=""; _wd_last_review=""; _wd_adv_inbox_seen=""; _wd_builder_inbox_seen=""; } # call on phase transition
_wd_last_sha=""; _wd_adv_inbox_seen=""; _wd_builder_inbox_seen=""
handoff_reset() { _wd_last_sha=""; _wd_adv_inbox_seen=""; _wd_builder_inbox_seen=""; } # call on phase transition
# Signal handoffs off the loops' CONVENTIONAL COMMIT PREFIXES on origin/main — NOT by parsing
# free-form markdown prose (brittle). The loops consistently prefix every gate claim `claim(...)`
# and every verdict/finding `review(...)`. So: a new `claim(` commit pushed => ping the Adversary;
# a new `review(` commit => ping the Builder. Edge-triggered on the origin/main SHA (append-only —
# the loops never force-push), so it can't double-fire or mis-route. INBOX files are detected
# separately (which file changed routes the ping). All reads are of the PUSHED state (what the
# receiver pulls).
handoff_check() {
local idx status review adv_inbox builder_inbox now added ids cur h
idx="$(cur_idx)"
local head subjects adv_inbox builder_inbox h
_wd_fetch_origin "$BUILDER_DIR"
status="$(_wd_show_pushed "$BUILDER_DIR" "$(phase_status "$idx")")"
review="$(_wd_show_pushed "$BUILDER_DIR" "$(phase_review "$idx")")"
head="$(git -C "$BUILDER_DIR" rev-parse origin/main 2>/dev/null || true)"
if [[ -n "$head" ]]; then
if [[ -z "$_wd_last_sha" ]]; then
_wd_last_sha="$head" # baseline silently on first observation / restart
elif [[ "$head" != "$_wd_last_sha" ]]; then
subjects="$(git -C "$BUILDER_DIR" log --format='%s' "${_wd_last_sha}..origin/main" 2>/dev/null || true)"
if printf '%s\n' "$subjects" | grep -qiE '^claim'; then
log "handoff: new claim(...) commit on origin/main -> pinging Adversary"
ping_session "$ADV_SESSION" "watchdog ping: the Builder pushed a gate CLAIM (claim(...) commit). Pull and verify the claimed gate now."
fi
if printf '%s\n' "$subjects" | grep -qiE '^review'; then
log "handoff: new review(...) commit on origin/main -> pinging Builder"
ping_session "$BUILDER_SESSION" "watchdog ping: the Adversary pushed a verdict/finding (review(...) commit). Pull REVIEW and act — proceed if it PASSes your gate, address it if it's a finding."
fi
_wd_last_sha="$head"
fi
fi
adv_inbox="$(_wd_show_pushed "$BUILDER_DIR" "ADVERSARY-INBOX.md")"
builder_inbox="$(_wd_show_pushed "$BUILDER_DIR" "BUILDER-INBOX.md")"
# Gate claims: match ONLY a FORMAL claim line ("Gate: <id> … CLAIMED, awaiting Adversary"), and
# edge-trigger on a genuinely-NEW such line (compared whole, so prose mentions / historical
# "CLAIMED detail" lines / id mis-parsing can't fire it). The id is just a best-effort label.
now="$(printf '%s\n' "$status" | grep -iE 'gate[: ].*claimed.*awaiting[[:space:]]+adversary' | sort -u || true)"
if [[ -n "$_wd_baselined" ]]; then
added="$(comm -13 <(printf '%s\n' "$_wd_awaiting") <(printf '%s\n' "$now") | grep -vE '^$' || true)"
if [[ -n "$added" ]]; then
ids="$(printf '%s\n' "$added" | grep -oiE 'gate:?[[:space:]]*[A-Z]{1,3}[0-9]+(\.[0-9]+)*' | sed -E 's/^[Gg]ate:?[[:space:]]*//' | tr '\n' ' ' | tr '[:lower:]' '[:upper:]' || true)"
[[ -z "$ids" ]] && ids="a gate"
log "handoff: NEW pushed gate claim [$ids] -> pinging Adversary"
ping_session "$ADV_SESSION" "watchdog ping: the Builder pushed a gate claim [$ids] in $(phase_status "$idx") — awaiting your verification. Pull and verify now."
fi
fi
_wd_awaiting="$now"; _wd_baselined=1
# REVIEW change (pushed) -> ping Builder.
if [[ -n "$review" ]]; then
cur="$(printf '%s' "$review" | md5sum | awk '{print $1}')"
if [[ -n "$cur" && "$cur" != "$_wd_last_review" ]]; then
[[ -n "$_wd_last_review" ]] && {
log "handoff: $(phase_review "$idx") changed (pushed) -> pinging Builder"
ping_session "$BUILDER_SESSION" "watchdog ping: the Adversary pushed an update to $(phase_review "$idx") (a verdict or finding). Pull and act now — if it PASSes your gate, proceed; if it's a finding, address it."
}
_wd_last_review="$cur"
fi
fi
# INBOX side-channel (§6.1), detected on the pushed state. Receiver deletes after consuming =>
# absent on origin/main => re-arm so the next write re-pings.
if [[ -n "$adv_inbox" ]]; then