diff --git a/README.md b/README.md index 82a9b13..93eb760 100644 --- a/README.md +++ b/README.md @@ -267,6 +267,9 @@ phases = [ 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 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. --- diff --git a/agents.py b/agents.py index 50ed503..ae57517 100755 --- a/agents.py +++ b/agents.py @@ -599,15 +599,22 @@ def _show_pushed(cfg, repo, path): return r.stdout 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): h = cfg["loop"].get("handoff") if not h: return 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") review_pat = h.get("review_pattern", "^review") _git(repo, "fetch -q origin") @@ -618,15 +625,15 @@ def handoff_check(cfg): elif head != _hand["sha"]: subjects = _git(repo, f"log --format=%s {_hand['sha']}..origin/main").stdout if re.search(claim_pat, subjects, re.M | re.I): - log("handoff: claim commit → pinging reviewer") - ping_session(sub(h.get("claim_pings", "adversary")), + log("handoff: claim commit → pinging reviewer(s)") + _ping_agents(cfg, h.get("claim_pings", "adversary"), "adversary", "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): 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. " - "Pull the review file and act.", submit_key=submit) + "Pull the review file and act.") _hand["sha"] = head inboxes = h.get("inboxes", []) md5 = lambda s: hashlib.md5(s.encode()).hexdigest() @@ -642,9 +649,9 @@ def handoff_check(cfg): hh = md5(content) if hh != _hand[key]: 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"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 else: _hand[key] = ""