feat(handoff): claim_pings/review_pings accept a list — ping every reviewer

Multi-reviewer setups (e.g. a correctness + a readability adversary) can now have
the watchdog ping ALL reviewers on a claim, each in its own session with its own
submit key. A bare string still works (single agent). _ping_agents() helper.
This commit is contained in:
2026-06-22 00:24:41 +00:00
parent 781db071dd
commit 98d198baa9
2 changed files with 21 additions and 11 deletions

View File

@ -267,6 +267,9 @@ phases = [
subject matches `claim_pattern` / `review_pattern`, and watches the two `inboxes` files. When a subject matches `claim_pattern` / `review_pattern`, and watches the two `inboxes` files. When a
claim lands it pings the `claim_pings` agent; a review pings `review_pings`; an inbox change claim lands it pings the `claim_pings` agent; a review pings `review_pings`; an inbox change
pings the relevant side. This is how the Builder and Adversary coordinate purely through git. pings the relevant side. This is how the Builder and Adversary coordinate purely through git.
`claim_pings` / `review_pings` may be a single agent name **or a list** — e.g.
`claim_pings = ["correctness-adversary", "readability-adversary"]` pings every reviewer on a claim
(each in its own session), for multi-reviewer setups.
--- ---

View File

@ -599,15 +599,22 @@ def _show_pushed(cfg, repo, path):
return r.stdout return r.stdout
return "" return ""
def _ping_agents(cfg, value, default, msg):
"""Ping one or more agents. `value` is an agent name, a LIST of names, or falsy (→ default).
Each target is pinged in its own session with its own backend's submit key — so a handoff can
notify multiple reviewers (e.g. claim_pings = ["correctness-adversary", "readability-adversary"])."""
names = value if isinstance(value, list) else [value or default]
for name in names:
agent = cfg["agents"].get(name)
session = agent["session"] if agent and agent.get("session") else (cfg["session_prefix"] + str(name))
submit = backend_of(cfg, agent).get("submit_key", "Enter") if agent else "Enter"
ping_session(session, msg, submit_key=submit)
def handoff_check(cfg): def handoff_check(cfg):
h = cfg["loop"].get("handoff") h = cfg["loop"].get("handoff")
if not h: if not h:
return return
repo = handoff_repo(cfg) repo = handoff_repo(cfg)
sub = lambda name: cfg["agents"].get(name, {}).get("session", cfg["session_prefix"] + name)
builder_name = h.get("review_pings", "builder")
submit = (backend_of(cfg, cfg["agents"][builder_name]).get("submit_key", "Enter")
if builder_name in cfg["agents"] else "Enter")
claim_pat = h.get("claim_pattern", "^claim") claim_pat = h.get("claim_pattern", "^claim")
review_pat = h.get("review_pattern", "^review") review_pat = h.get("review_pattern", "^review")
_git(repo, "fetch -q origin") _git(repo, "fetch -q origin")
@ -618,15 +625,15 @@ def handoff_check(cfg):
elif head != _hand["sha"]: elif head != _hand["sha"]:
subjects = _git(repo, f"log --format=%s {_hand['sha']}..origin/main").stdout subjects = _git(repo, f"log --format=%s {_hand['sha']}..origin/main").stdout
if re.search(claim_pat, subjects, re.M | re.I): if re.search(claim_pat, subjects, re.M | re.I):
log("handoff: claim commit → pinging reviewer") log("handoff: claim commit → pinging reviewer(s)")
ping_session(sub(h.get("claim_pings", "adversary")), _ping_agents(cfg, h.get("claim_pings", "adversary"), "adversary",
"watchdog ping: the other loop pushed a gate CLAIM commit. " "watchdog ping: the other loop pushed a gate CLAIM commit. "
"Pull and verify the claimed gate now.", submit_key=submit) "Pull and verify the claimed gate now.")
if re.search(review_pat, subjects, re.M | re.I): if re.search(review_pat, subjects, re.M | re.I):
log("handoff: review commit → pinging builder") log("handoff: review commit → pinging builder")
ping_session(sub(h.get("review_pings", "builder")), _ping_agents(cfg, h.get("review_pings", "builder"), "builder",
"watchdog ping: the other loop pushed a verdict/finding commit. " "watchdog ping: the other loop pushed a verdict/finding commit. "
"Pull the review file and act.", submit_key=submit) "Pull the review file and act.")
_hand["sha"] = head _hand["sha"] = head
inboxes = h.get("inboxes", []) inboxes = h.get("inboxes", [])
md5 = lambda s: hashlib.md5(s.encode()).hexdigest() md5 = lambda s: hashlib.md5(s.encode()).hexdigest()
@ -642,9 +649,9 @@ def handoff_check(cfg):
hh = md5(content) hh = md5(content)
if hh != _hand[key]: if hh != _hand[key]:
log(f"handoff: {fname} changed → pinging {target}") log(f"handoff: {fname} changed → pinging {target}")
ping_session(sub(target), _ping_agents(cfg, target, target,
f"watchdog ping: the other loop pushed {sub_dir}/{fname} — pull, read it, " f"watchdog ping: the other loop pushed {sub_dir}/{fname} — pull, read it, "
f"act, then delete the file (commit + push) to mark it consumed.", submit_key=submit) f"act, then delete the file (commit + push) to mark it consumed.")
_hand[key] = hh _hand[key] = hh
else: else:
_hand[key] = "" _hand[key] = ""