Files
box/bin/swarm_worker/mainloop_notify.py
T

88 lines
3.1 KiB
Python
Raw Normal View History

#!/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 <swarm-id> <slot> \"<message>\" [--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")