#!/usr/bin/env python3 """prompt_envelope.py — wraps every dispatched job prompt so agents DO work. Why: agents treated job DMs as informational and replied with prose; swarms and timers never got created. The envelope puts an executable subagent-spawn tool call at the TOP (before the task) and repeats it at the BOTTOM (after the task), with a work-first mandate and explicit tool/curl surface in between. Profiles tune the spawn recipe per job family; add new ones to PROFILES. """ import json import re BOX_API = "https://box.muse-dev.online/api/box" # Verified box-namespace private keys on bl (pubkey matches the agent's entry # in /srv/board/allowed_signers). Agents without a verified key fall back to # the [RESULT] line (harvester) until super provisions their box key. BOX_KEY_PATHS = { "dev": "/home/super/.ssh/id_dev", "pip": "/home/super/.ssh/id_pip", "646": "/home/super/.ssh/id_646", "opm": "/home/super/.ssh/id_opm", } def _response_rule(): return ( "\nRESPONSE RULE: in your reply, every line that is not work output" " MUST be a curl tool call against https://box.muse-dev.online/." " Fenced bash blocks are executed as tool calls and their output" " returns to you; do not narrate the calls.\n" ) def _box_surface_block(agent, job_name, job_id, target): """Thread + runtime + direct curl URL + respond command. READ: GET /api/box/jobs/ (signed, box namespace; live) RESPOND: POST /api/box/job/result (signed, box namespace, own-scope; ships with the frontdoor publish -- 404 until then) """ header = ( "\n---- BOX RUNTIME ----\n" "THREAD: " + str(target) + "\n" "RUNTIME: https://box.muse-dev.online/\n" "JOB: " + str(job_id) + "\n" "AGENT: " + str(agent) + "\n" ) key = BOX_KEY_PATHS.get(agent) if not key: return ( header + "\nNOTE: no box signing key is provisioned for '" + str(agent) + "' yet, so curl commands against the box API cannot authenticate." + " Ask super to provision your box key; until then the [RESULT]" + " line is your respond path (the harvester records it).\n" + _response_rule() ) read_cmd = ( "```bash\n" "TS=$(date +%s)\n" "SIG=$(printf '%s\\njobs/" + str(job_name) + "' \"$TS\"" " | ssh-keygen -Y sign -f " + key + " -n box" " | python3 -c 'import sys,urllib.parse;" " print(urllib.parse.quote(sys.stdin.read().strip()))')\n" "curl -s \"" + BOX_API + "/jobs/" + str(job_name) + "?identity=" + str(agent) + "&ts=$TS&sig=$SIG\"\n" "```" ) respond_cmd = ( "```bash\n" "TS=$(date +%s)\n" "export BOX_TS=$TS\n" "export BOX_SIG=$(printf '%s\\njob/result\\n" + str(job_id) + "' \"$TS\"" " | ssh-keygen -Y sign -f " + key + " -n box)\n" "python3 - <<'PYEOF' | curl -s -X POST " + BOX_API + "/job/result" " -H 'Content-Type: application/json' -d @-\n" "import json, os\n" "print(json.dumps({\n" " \"identity\": \"" + str(agent) + "\",\n" " \"ts\": os.environ[\"BOX_TS\"],\n" " \"sig\": os.environ[\"BOX_SIG\"],\n" " \"job_id\": \"" + str(job_id) + "\",\n" " \"success\": True,\n" " \"summary\": \"[RESULT " + str(job_id) + "] \",\n" "}))\n" "PYEOF\n" "```" ) return ( header + "\nREAD your work order any time:\n" + read_cmd + "\n" + "\nRESPOND when done -- emit this exact command as a tool call:\n" + respond_cmd + "\n" + _response_rule() ) UUID_RE = re.compile(r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}") # profile -> (spawn count, followup minutes, spawn task hint) PROFILES = { "pulse": (2, 30, "execute your scope's standing work: set timers, verify them, report per-slot verdicts"), "health": (2, 60, "run the prescribed health gates read-only and report per-gate verdicts"), "swarm": (2, 20, "sweep swarms and sidechats read-only and report stale or unarchived items"), "work": (2, 15, "split the task above into two independent halves and complete each"), } def pick_profile(job_name): n = job_name or "" if n.startswith("autonomy-pulse"): return "pulse" if "health" in n: return "health" if "swarm" in n: return "swarm" return "work" def _tool(op, args): # no ']' inside the JSON: the harvester's [TOOL ...] regex stops at the first one return "[TOOL %s %s]" % (op, json.dumps(args, separators=(", ", ": "))) def spawn_call(job_id, job_name, profile): count, _, hint = PROFILES[profile] # no brackets in the task text: the harvester's [TOOL ...] regex stops at the first ']' task = "Subagent for job %s (%s): %s." % (job_id, job_name, hint) return _tool("swarm.spawn", {"count": count, "task": task[:900], "label": (job_name or "job")[:60]}) def native_followup_call(job_id, job_name, profile, agent): """Muse-native one-shot cron (cron.create runonce) - bridged to followup.create.""" _, mins, _ = PROFILES[profile] return _tool("cron.create", { "kind": "runonce", "agent": agent, "in_m": mins, "prompt": "Continue job %s (%s): verify with cron.runs and swarm.list then act or close." % (job_id, job_name)}) def followup_call(job_id, job_name, profile, agent): _, mins, _ = PROFILES[profile] return _tool("followup.create", { "agent": agent, "in_m": mins, "prompt": "Continue job %s (%s): check swarm results with swarm.list then act or close." % (job_id, job_name)}) def thread_url(target): t = str(target) return "https://box.muse-dev.online/%s/%s" % ("thread" if UUID_RE.fullmatch(t.lower()) else "sidechat", t) def wrap(job_name, job_id, agent, target, rendered): """Return the full DM body (without the [JOB id] prefix).""" profile = pick_profile(job_name) spawn = spawn_call(job_id, job_name, profile) follow = native_followup_call(job_id, job_name, profile, agent) top = ( "WORK ORDER - ACTION REQUIRED, NOT INFORMATIONAL.\n" "Your FIRST output must be tool calls, not prose. Start now:\n" " 1) %s\n" " 2) %s\n" "Each tool call returns a result message; keep going until the task is done.\n" "---- TASK ----\n" % (spawn, follow) ) has_result = "[RESULT" in rendered bottom = ( "\n---- END TASK ----\n" "REMINDER (importance: critical): work is only real if a tool call ran. " "If you have not yet spawned subagents or set a follow-up timer, do it now:\n" " %s\n" " %s\n" "Native tools (preferred): cron.create (runonce/interval), cron.status, cron.view, cron.runs " "(truth about what ran), cron.run, subagent spawn; hooks.dry_run before hooks.enable. " "Native cron form: %s\n" "Box fallback tools: [TOOL health.check {}] [TOOL swarm.list {}] [TOOL swarm.status {\"swarm_id\": \"\"}] " "[TOOL swarm.results {\"swarm_id\": \"\"}] [TOOL quality.check {}]\n" % (spawn, follow, native_followup_call(job_id, job_name, profile, agent)) ) bottom += _box_surface_block(agent, job_name, job_id, target) if not has_result: bottom += "Close with: [RESULT %s] : \n" % job_id else: bottom += "Close using the [RESULT ...] line specified in the task, summarising tool outputs.\n" return top + rendered.strip() + "\n" + bottom