#!/usr/bin/env python3 """Swarm worker -> main-loop visibility bridge (Bridge Builder 5/5). After the worker records a slot result via `box swarm-report` (see reporter.py), this posts a short human-readable completion note to the slot's own sidechat. The note deliberately carries NO `[JOB ` tag, so self_main_loop.py's check_sidechats() does not filter it: the next main-loop tick picks it up, includes it in the agent's digest, and the completion becomes visible in the same surface as everything else. That is the "answering via the main loop" path. Design doc: bin/swarm_worker/mainloop-bridge.md Runs on bl. Never auto-wired into any loop; call explicitly from the worker daemon after post_result() succeeds. Dry-run mode posts nothing. """ import subprocess import sys BIN = "/home/super/Projects/NetVM/bin" DM_PY = BIN + "/dm.py" NOTE_CHARS = 200 def notify_via_mainloop(swarm_id, slot_index, message, worker_id="swarm-worker", dry_run=False): """Post a swarm-slot completion note visible to the main loop. Args: swarm_id: e.g. "sw-20261005-151307-a222" slot_index: int slot number message: human-readable summary (first NOTE_CHARS chars used) worker_id: sender identity for dm.py --agent dry_run: if True, print what WOULD be sent without sending. Returns: True on success (or dry-run), False on failure (logged, not raised). """ target_name = "sw-%s-s%s" % (swarm_id, slot_index) summary = (message or "").strip().replace("\n", " ")[:NOTE_CHARS] note = "[SWARM-DONE %s/%s] %s" % (swarm_id, slot_index, summary) try: sys.path.insert(0, BIN) import dm uuid = dm.resolve_sidechat_target(target_name) except Exception as e: print("notify_via_mainloop: target resolve failed for %s: %s" % (target_name, e), file=sys.stderr) return False if not uuid: print("notify_via_mainloop: no UUID for %s" % target_name, file=sys.stderr) return False cmd = [sys.executable, DM_PY, "send", "--agent", worker_id, "--to", worker_id, "--target", uuid, note] if dry_run: print("DRY-RUN would run: %s" % " ".join(cmd)) return True try: r = subprocess.run(cmd, capture_output=True, text=True, timeout=120) except Exception as e: print("notify_via_mainloop: dm.py exec failed: %s" % e, file=sys.stderr) return False if r.returncode != 0: tail = ((r.stderr or "") + (r.stdout or "")).strip()[-300:] print("notify_via_mainloop: dm.py rc=%d: %s" % (r.returncode, tail), file=sys.stderr) return False return True if __name__ == "__main__": live = "--live" in sys.argv args = [a for a in sys.argv[1:] if a != "--live"] if len(args) < 3: print("usage: mainloop_notify.py \"\" [--live]") print("default is DRY-RUN (posts nothing).") sys.exit(2) ok = notify_via_mainloop(args[0], args[1], args[2], dry_run=not live) print("notified" if ok else "FAILED")