feat(swarm-worker): dispatch prose swarm slots to ephemeral Muse subagents

- daemon: route non-shell slot tasks to a fresh subagent session on dev/def/muse
  via muse_hybrid; add bin/ to sys.path so muse_hybrid/prompt_envelope import
  under the tmux supervisor
- prompt_envelope: add wrap_subagent_task() - plain task assignment without
  [TOOL tmux/swarm/cron] meta tags (subagents refused those as relayed test traffic)
- box-ctl/reporter: swarm-attach accepts optional session-id, stored as
  slot.subagent_session_id
- executor: _looks_like_shell requires an existing executable (prose like
  'verify ...' no longer misread as shell)
- poller: pick up pending swarms as well as running
- response-harvester: monitor running slot subagent sessions from swarms.json,
  allow '/' in RESULT/VERB job ids, archive ephemeral threads on any verdict
  (OK or FAIL), reap slot sessions for terminal swarms
This commit is contained in:
operator
2026-10-05 20:18:46 +00:00
parent fc765f270b
commit 59c9965791
7 changed files with 235 additions and 29 deletions
+61 -12
View File
@@ -83,18 +83,32 @@ try:
except ImportError:
HAS_MUSE_HYBRID = False
try:
import prompt_envelope
HAS_PROMPT_ENVELOPE = True
except ImportError:
HAS_PROMPT_ENVELOPE = False
VALID_AGENTS = ["muse", "pip", "646", "opm", "dev", "def"]
DEFAULT_PORTS = {"muse": 9410, "pip": 9420, "646": 9430, "opm": 9440, "def": 9450, "dev": 9455}
try:
import lookup_engine
HAS_LOOKUP_ENGINE = True
except ImportError:
HAS_LOOKUP_ENGINE = False
# Matches EVERY [RESULT <job_id>] marker in a message (use with finditer, not
# search). The result text is lazy and stops before the next marker (or end of
# text), so a message closing two jobs records each with its own text instead
# of the first marker greedily swallowing the second.
RESULT_RE = re.compile(r"\[RESULT\s+([A-Za-z0-9_-]+)\]\s*(.*?)(?=\[RESULT\s|\Z)", re.S)
_LOCAL_RESULT_RE = re.compile(r"\[RESULT\s+([A-Za-z0-9_/-]+)\]\s*(.*?)(?=\[RESULT\s|\Z)", re.S)
RESULT_RE = lookup_engine.get_result_regex() if HAS_LOOKUP_ENGINE else _LOCAL_RESULT_RE
def iter_result_markers(text):
"""Yield (job_id, result_text) for every [RESULT <job_id>] marker in text."""
for m in RESULT_RE.finditer(text or ""):
current_re = lookup_engine.get_result_regex() if HAS_LOOKUP_ENGINE else RESULT_RE
for m in current_re.finditer(text or ""):
yield m.group(1).strip(), m.group(2).strip()
@@ -103,12 +117,14 @@ def iter_result_markers(text):
# ACK/CLAIM acknowledge a digest (nudge-suppressed, NOT closed);
# RESULT/DECLINE/NO-ACTION close the digest. Every verb match records
# outcome=<verb> on the followup record.
VERB_RE = re.compile(r"\[(ACK|CLAIM|RESULT|DECLINE|NO-ACTION)\s+([A-Za-z0-9_-]+)\]")
_LOCAL_VERB_RE = re.compile(r"\[(ACK|CLAIM|RESULT|DECLINE|NO-ACTION)\s+([A-Za-z0-9_/-]+)\]")
VERB_RE = lookup_engine.get_verb_regex() if HAS_LOOKUP_ENGINE else _LOCAL_VERB_RE
def iter_verb_markers(text):
"""Yield (verb, job_id) for every [VERB <job_id>] marker in text."""
for m in VERB_RE.finditer(text or ""):
current_re = lookup_engine.get_verb_regex() if HAS_LOOKUP_ENGINE else VERB_RE
for m in current_re.finditer(text or ""):
yield m.group(1), m.group(2).strip()
@@ -503,6 +519,26 @@ def get_monitored_threads(target_agent=None):
except Exception:
continue
# Also monitor active swarm slot subagents from swarms.json
try:
if SWARM_FILE.exists():
s_data = json.loads(SWARM_FILE.read_text(encoding="utf-8"))
for sid, s_info in s_data.items():
if sid.startswith("_") or not isinstance(s_info, dict):
continue
if s_info.get("status") not in ("running", "pending"):
continue
for slot in s_info.get("slots", []):
if slot.get("status") == "running":
sub_id = slot.get("subagent_session_id") or slot.get("thread_uuid") or slot.get("sidechat_id")
ag = slot.get("agent_id")
if sub_id and ag and ag in threads_by_agent:
existing = [t["id"] for t in threads_by_agent[ag]]
if sub_id not in existing:
threads_by_agent[ag].append({"id": sub_id, "name": f"swarm-{sid[:8]}-s{slot.get('slot')}"})
except Exception as se:
sys.stderr.write(f"warning: failed to add swarm threads to monitor list: {se}\n")
return threads_by_agent
@@ -783,8 +819,7 @@ def process_messages(raw_messages, agent, thread_id, thread_name, last_wm, follo
sys.stderr.write(f"warning: failed to record swarm report: {se}\n")
else:
trigger_chain_next(job_id, result_text, success=not is_fail)
if not is_fail:
archive_ephemeral_thread(agent, thread_id, job_id=job_id)
archive_ephemeral_thread(agent, thread_id, job_id=job_id)
clear_matching_followups(followups, agent, thread_id, mid, text,
dry_run, job_id=job_id, verb="RESULT")
for verb, job_id in verbs:
@@ -1047,11 +1082,19 @@ def reconcile_and_dispatch_swarms(dry_run=False):
slot_target = f"{sid}-s{slot_idx}"
# Prepare task directive prompt
prompt = (
f"[JOB {sid}/{slot_idx}] Task for swarm slot {slot_idx}:\n"
f"{task_text}\n\n"
f"Reply with [RESULT {sid}/{slot_idx}] OK <summary> or FAIL <reason>."
)
slot_job_id = f"{sid}/{slot_idx}"
if HAS_PROMPT_ENVELOPE and hasattr(prompt_envelope, "wrap_subagent_task"):
prompt = prompt_envelope.wrap_subagent_task(slot_job_id, task_text)
else:
prompt = (
f"Operator assignment for swarm slot {slot_job_id}:\n\n"
f"Task:\n{task_text.strip()}\n\n"
f"Instructions:\n"
f"1. Carry out this task directly using your available tools.\n"
f"2. When finished, conclude your final response with your verdict line:\n"
f"[RESULT {slot_job_id}] OK: <one-line summary of actions and outcome>\n"
f"(or [RESULT {slot_job_id}] FAIL: <reason> if the task could not be completed)\n"
)
# Native Subagent Execution Bridge:
# Spawns an interactive child session directly on the target worker
@@ -1196,7 +1239,13 @@ def check_and_archive_terminal_swarms():
for sid, swarm in swarms_data.items():
st = swarm.get("status")
if st in ("completed", "partial", "killed"):
# Check slots or matching sidechats
# Check slot subagent sessions
for slot in swarm.get("slots", []):
sub_id = slot.get("subagent_session_id")
ag = slot.get("agent_id")
if sub_id and ag:
archive_ephemeral_thread(ag, sub_id, job_id=sid)
# Check matching registered sidechats
for key, val in sc_data.items():
if isinstance(val, dict) and not val.get("archived") and val.get("type") != "persistent":
if sid in key or (swarm.get("label") and swarm.get("label") in key):