# Hermes task — deploy the Calypso→Hermes bridge endpoint into malin.py

**Goal:** add ONE synchronous endpoint to `C:\malin\malin.py` so Calypso (Mac) can dispatch a task to you via the existing `:1237` broker, you run it with `hermes chat -q`, and the stdout goes straight back. This is the "Malin-hub" bridge — it kills the Jun-copy-paste relay.

**Apply as a SURGICAL patch — do NOT overwrite the whole file** (the live file has Jun's manual pose additions in EXPLICIT_DEFAULTS; leave those untouched). Three insertions, all in the bridge code:

### 1. Constants — insert right AFTER the line `ASK_CALYPSO_RE = re.compile(r"\[ask calypso:\s*(.*?)\]", re.I | re.S)`

```python
# ---- Calypso->Hermes bridge (the REVERSE direction, the "Malin-hub" first mission) ----
# Calypso (Mac) dispatches a task to the on-box Hermes agent WITHOUT Jun relaying:
# she POSTs {task} to /bridge/hermes on this same :1237 server (Mac-only IP guard),
# we shell out to `hermes chat -q "<task>"` and hand back its stdout. Synchronous —
# Mac->5090 works fine, so the POST simply blocks until hermes returns. Single-shot
# dispatch only; hermes NEVER auto-triggers Calypso back (no autonomous loop).
HERMES_CMD     = "hermes"                    # on-box Hermes CLI (set to full path if not on PATH)
HERMES_TIMEOUT = 300                         # seconds to let one Hermes task run before giving up
```

### 2. Helper — insert right BEFORE the line `class _BridgeHandler(http.server.BaseHTTPRequestHandler):`

```python
def run_hermes_task(task):
    """Dispatch ONE task to the on-box Hermes agent and return its stdout.
    Calypso (Mac) hits POST /bridge/hermes; we just shell out to `hermes chat -q
    "<task>"` and hand back what it prints. Returns {"ok": bool, "output": str}."""
    try:
        proc = subprocess.run([HERMES_CMD, "chat", "-q", task],
                              capture_output=True, text=True, timeout=HERMES_TIMEOUT)
        out = (proc.stdout or "").strip()
        if not out and proc.returncode != 0:
            err = (proc.stderr or "").strip()[:500]
            return {"ok": False, "output": err or f"(hermes exited {proc.returncode})"}
        return {"ok": True, "output": out}
    except subprocess.TimeoutExpired:
        return {"ok": False, "output": f"(hermes task exceeded {HERMES_TIMEOUT}s)"}
    except FileNotFoundError:
        return {"ok": False, "output": "(hermes CLI not found on PATH — set HERMES_CMD to its full path)"}
    except Exception as e:
        return {"ok": False, "output": f"(hermes dispatch error: {e})"}
```

### 3. Endpoint — inside `_BridgeHandler.do_POST`, insert the `/bridge/hermes` branch right AFTER the IP-guard and BEFORE the `if self.path != "/bridge/answer":` line

So the top of `do_POST` becomes:
```python
    def do_POST(self):
        if self.client_address[0] != BRIDGE_MAC_IP:
            return self._deny()
        if self.path == "/bridge/hermes":
            # Calypso->Hermes dispatch: run one `hermes chat -q` task, return stdout.
            n = int(self.headers.get("Content-Length", 0))
            try:
                data = json.loads(self.rfile.read(n).decode("utf-8"))
                task = (data.get("task") or "").strip()
                if not task:
                    return self._deny(400)
                return self._ok(run_hermes_task(task))
            except Exception:
                return self._deny(400)
        if self.path != "/bridge/answer":
            return self._deny(404)
```
(`subprocess` is already imported at the top of malin.py, so no new imports.)

### Then:
1. **Syntax-check** before restarting: `python -c "import py_compile; py_compile.compile(r'C:\malin\malin.py', doraise=True)"` → must print nothing / no error.
2. **Restart Malin cleanly** — via the tray/watchdog (Restart Malin), NOT a hand-started `py malin.py` (that spawns a rogue duplicate). The bridge server re-binds :1237 on start.
3. **Verify two things and report back:**
   - `hermes chat -q "reply with exactly: BRIDGE OK"` from a shell on the 5090 → confirm it returns clean stdout (so `HERMES_CMD="hermes"` resolves; if not, tell me the full path to the hermes exe and we'll set HERMES_CMD).
   - Confirm malin.py came back up (Telegram still answers + the watchdog shows it running).

Once you confirm, I'll fire a live `ask_hermes.py "<test>"` from the Mac end-to-end. One caveat we'll handle only if it bites: a dispatched task that needs file-write approval may hit your safety gate — for the first test I'll send a read-only task, so no `/yolo` needed yet.
