| name | fleet-commands |
| version | 1.6.0 |
| category | devops |
| description | Send operational commands to fleet agents via the PGMQ bus — message format, delivery verification, bus_access expectations, schema-validated EXEC payloads, and cleanup. |
| platforms | ["linux"] |
| author | moses |
| metadata | {"hermes":{"tags":["bus","fleet","commands","messaging","hc","pgmq"],"related_skills":["cortex-bus","hermes-cortex-deployment"]}} |
Fleet Commands — Agent Commanding via the Bus
Overview
Send structured operational commands from the orchestrator (Moses) to any fleet agent's inbox queue on the PGMQ bus. Messages land immediately and are consumed according to each agent's bus_access (host/client).
Architecture rule — three consumption modes:
| Mode | Who | How | Covers |
|---|
| In-session | Moses (orchestrator) | Directly reads its inbox via MCP tools + hc CLI during active sessions. | Skill reports, EXEC_RESULT, health pings, incoming requests. |
| Out-of-session | Moses | cortex-bus-workday/evening/overnight LLM crons process inbox_moses using the Inbox Message Decision Framework. | Same as in-session, but when no user is chatting. |
| Fleet handler | Esther, Joseph, Gisu, Kustos | agent-message-handler.py cron (every 5 min, no_agent script) processes their inbox. | UPDATE_REQUEST, EXEC, ROLLBACK_REQUEST, GIT_AUTH_CHECK from orchestrator. |
Corrected 2026-08-02: Moses DOES run agent-message-handler.py on itself (cron list shows it every 5 min, thousands of runs). It processes inbox_moses — *_RESULT replies, silent noise subjects (DOCTOR_TEST/STATUS_REQUEST/HEARTBEAT/PING), and any registered data-subjects. Unknown subjects get ARCHIVED + an error response sent within minutes of landing. So any NEW inbound message type to inbox_orchestrator (fleet agents sending data TO the orchestrator) MUST get a handler branch first, or the payload is destroyed from the live flow. Example: "Skill Stub Recovery" messages (full skill content from agents) were eaten as unknown until the handler was patched to stage them to state/skill-stub-recovery/ (commit 8a38e486). Design: agents CANNOT write the repo — they send payloads to inbox_orchestrator; the handler stages them to a state dir; the orchestrator evaluates/copies later (store-first, evaluate-later).
Primary tool: hc send <agent> <subject> <body> (admin CLI, operates via docker exec into Postgres)
Round-trip tool: hc exec <agent> <script> [args...] (sends EXEC, polls up to 5min for EXEC_RESULT)
The COMMAND: subject prefix is for human-readable messages (logged/archived, no automated response). For structured task delegation, use the agent-message-handler protocol below.
Structured Command Protocol (agent-message-handler)
Fleet agents run agent-message-handler.py as a cron (*/5 * * * *). Source: ops/scripts/agent/agent-message-handler.py.
Message format (sent via hc send or bus.send):
{
"from": "moses",
"to": "<agent>",
"topic": "fleet-update",
"subject": "EXEC",
"correlation_id": "<unique-uuid>",
"body": "{\"command\": \"script-name.py\", \"params\": [\"--flag\"], \"timeout\": 60}"
}
correlation_id is required for idempotency. Messages without one may be silently skipped if "" is in the handler's processed set.
Supported Subjects
| Subject | Action | Response | Body Fields |
|---|
EXEC | Run script under ~/.hermes-cortex/scripts/ | EXEC_RESULT | command, params[], timeout |
UPDATE_REQUEST | Run cortex-update.sh | UPDATE_RESULT | target_sha, target_version, run_doctor |
UPDATE_REQUEST processing is now DETACHED (2026-08-18). The fleet
handler no longer runs pull+cortex-update+doctor synchronously inside its
cron tick: on slow hosts the combined runtime (~390s worst case) exceeded
the tick budget, the handler was killed mid-processing after the
early-archive, and UPDATE_RESULTs were silently lost (fleet dispatch for
14b18f0b returned 0/5 results while the updates themselves landed).
The handler now spawns a detached worker
(~/.hermes-cortex/state/pending-update-results/<corr>.json), returns in
seconds, and a per-tick sweep sends the result when the worker finishes —
or a timeout error result if the worker dies (stale .running marker
older than 30 min). Expect UPDATE_RESULTs 3-10 min after the request on
slow hosts. When verifying a dispatch: poll bus.archives for
UPDATE_RESULT (subject filter), and if a host consumed the request but
no result appears within ~10 min, probe it directly with
hc exec <agent> cortex-doctor.py --quiet (the EXEC path is unaffected)
— the update may have completed with only the receipt lost.
| ROLLBACK_REQUEST | Git checkout previous SHA | ROLLBACK_RESULT | target_sha, reason |
| GIT_AUTH_CHECK | Verify git can ls-remote | GIT_AUTH_RESULT | expected_url |
| DIAGNOSTIC_REQUEST | Run agent-diagnostic.py | DIAGNOSTIC_RESULT | check, respond_to_queue |
Round-trip with hc exec (Recommended)
For EXEC commands, hc exec handles the full lifecycle:
hc exec esther cortex-doctor.py --json
hc exec joseph cortex-doctor.py --quiet
hc exec gisu cortex-doctor.py --quiet
⚠️ hc exec <agent> -- <raw-command> does NOT work — the -- separator is NOT supported. The EXEC handler only runs scripts that exist in ~/.hermes-cortex/scripts/ on the target; a -- "raw command" attempt returns Script not found: -- (exit -1). Verified 2026-08-11: hc exec titus -- "python3 -c ..." failed exactly this way. Use --output-schema RAW (which skips RESULT validation) with a real deployed script — the RAW schema is about validation, not about raw shell execution. To run an arbitrary probe on a fleet agent, ship it as a script in the repo + register it in cortex-update.sh, or use an existing deployed probe (cortex-doctor.py, agent-diagnostic.py).
It generates a correlation_id, sends the EXEC, then polls inbox_moses every 15s for up to 5min waiting for EXEC_RESULT.
Schema-validated EXEC (S2)
Since v4 of the agent registry, every EXEC payload is validated against the
EXEC JSON Schema before sending and the result against EXEC_RESULT by default.
Use --output-schema to change the expected result schema:
# Default: validates result against EXEC_RESULT schema
hc exec kustos cortex-doctor.py --json
# Custom output schema: validates against WAVE_RESULT
hc exec esther setup-push-metrics-cron.sh --output-schema WAVE_RESULT
# RAW mode: skip result validation (use a deployed script — `--` raw commands don't work)
hc exec moses cortex-doctor.py --output-schema RAW
Available schemas: EXEC, EXEC_RESULT, WAVE_RESULT, UPDATE_REQUEST,
UPDATE_RESULT. See ops/scripts/lib/handoff_schema.py.
If validation fails, hc exec prints the violations before the result summary.
Manual round-trip verification
When using hc send instead:
- Send:
hc send <agent> "<subject>" '<json-body>'
- Confirm pending: check queue state
- Wait: handler runs
*/5 * * * *
- Confirm processing → archived
- Check
inbox_moses for response
hc send safety gates (2026-08-10)
hc send refuses to create known-bad messages. Both gates apply to fleet
sends only (self-sends skip — you see your own queue) and are bypassed with
--force (never for the --self-tested rule):
- Liveness — reads
agent-registry.json and probes the target's
health_url (5s, any HTTP response = alive). Offline agent → REFUSED.
health_method: "inbox" agents (Titus) can't be verified online → warn
and proceed so the message waits for them.
- Dedup — peeks the target queue first; the same
correlation_id, or
the same subject AND body, already pending → REFUSED. Matching both
subject and body (not subject alone) lets parallel EXECs with different
payloads coexist while identical UPDATE_REQUESTs still get caught.
A caller-supplied correlation_id inside a JSON body is preserved on the
envelope (previously overwritten with send-{uuid}).
Failure pattern (2026-08-10): repeated UPDATE_REQUEST sends to offline Titus
piled up 3 identical pending messages; identical EXECs were tripled by the
bus forwarder round-trip. The gates refuse the operator-side pile-up; the
forwarder fix stops the infrastructure-side one.
When to Use
- Sending health-check requests to fleet agents
- Triggering doctor runs on remote agents
- Requesting disk/report/service status from a specific agent
- One-shot operational commands that don't need a full workflow
- Testing bus connectivity to a specific agent
Precondition: Clean Bus Before Send
Critical rule: the bus must be clean before sending new commands. Stale/stuck messages from previous rounds (especially processing state messages from crashed handlers) interfere with new commands — the handler crashes trying to process old ones instead of the fresh command.
Before every fleet command round, check for stale UPDATE_REQUESTs, EXECs, and diagnostic messages. Archive them before sending new ones.
Failure pattern (2026-07-21):
- Send UPDATE_REQUEST to all 5 agents
- Esther's handler crashes — message stuck in
processing
- Clean up and resend to 4 agents (excluding Titus)
- Esther's NEW request lands but old stuck one is still there — now 2 stuck messages
- User: "Clean the bus before you send"
Fix: Before any for agent in ...; do hc send "$agent" ..., run a bus check and archive stale messages.
Workflow
1. Survey the fleet
Check agent registry for bus_access and role:
cat ~/.hermes-cortex/state/agent-registry.json | python3 -c "
import json,sys; reg=json.load(sys.stdin)
for k,v in reg.get('agents',{}).items():
caps = v.get('capabilities', {})
ba = caps.get('bus_access', '?')
role = v.get('role', '?')
print(f' {k}: bus_access={ba} role={role}')
"
2. Send the command
hc send <agent> "COMMAND:<action>" "<descriptive body>"
Returns a msg_id on success. Save it for verification and cleanup.
Standard command subjects (human-readable, no automated response):
| Subject | Purpose |
|---|
COMMAND:health-check | Full system health — CPU/memory/disk/services |
COMMAND:doctor-report | Run cortex-doctor.py, report failures |
COMMAND:disk-check | Disk usage — report partitions above 85% |
COMMAND:ping-test | Test ping — acknowledge receipt |
COMMAND:update-repo | Pull latest, deploy, verify |
NOTE: COMMAND: messages with no structured JSON body are NOT processed by agent-message-handler.py. They land in the queue and are archived as "unknown subject" after the handler reads them. For structured execution, use the EXEC protocol above.
3. Verify delivery
sg docker -c "docker exec mycortex-postgres psql -U mycortex -d mycortex -t -c \"
SELECT queue_name, state, COUNT(*) as count
FROM bus.messages
WHERE queue_name = 'inbox_<agent>'
GROUP BY queue_name, state;
\""
| State | Meaning |
|---|
pending | Message landed, waiting for agent |
processing | Agent popped it (visibility timeout active) |
| Empty | Already archived or consumed |
4. Peek message content
⚠️ Body is double-encoded JSON. The PGMQ body column stores the entire message as a JSON string, not a JSON object. Using body->>'subject' returns null because body itself is a string value, not a JSON object with a subject key. Always use the double-parse pattern (body::jsonb #>> '{}')::jsonb to unwrap:
sg docker -c "docker exec mycortex-postgres psql -U mycortex -d mycortex -t -c \"
SELECT msg_id::text,
(body::jsonb #>> '{}')::jsonb->>'from' as sender,
(body::jsonb #>> '{}')::jsonb->>'subject' as subject,
(body::jsonb #>> '{}')::jsonb->>'correlation_id' as corr,
enqueued_at::timestamptz(0)
FROM bus.messages
WHERE queue_name = 'inbox_<agent>' AND state = 'pending'
ORDER BY enqueued_at;
\\\""
Quick reference — always use the double-parse pattern:
| Wrong (returns null) | Correct |
|---|
body->>'subject' | (body::jsonb #>> '{}')::jsonb->>'subject' |
body->'body'->>'success' | ((body::jsonb #>> '{}')::jsonb->'body')->>'success' |
body->>'correlation_id' | (body::jsonb #>> '{}')::jsonb->>'correlation_id' |
5. Confirm consumption
Check audit log for reads on the target queue:
sg docker -c "docker exec mycortex-postgres psql -U mycortex -d mycortex -t -c \"
SELECT action, agent_name, queue, COUNT(*) as cnt,
MIN(created_at)::timestamptz(0) as first_seen
FROM bus.audit_log
WHERE queue = 'inbox_<agent>'
AND created_at > NOW() - INTERVAL '5 minutes'
GROUP BY action, agent_name, queue
ORDER BY queue, action;
\""
6. Clean up test messages
After test or one-shot commands, archive to prevent DLQ cycling:
sg docker -c "docker exec mycortex-postgres psql -U mycortex -d mycortex -c \"
SELECT bus.archive('inbox_<agent>', '<msg_id>'::uuid, '<cleanup-label>');
\""
Labels like moses-test-cleanup help identify sources in the audit log.
Host-Aware Bus Queries (backup orchestrator — Esther)
The sg docker -c "docker exec mycortex-postgres psql ..." examples in this
skill query the LOCAL postgres. On Moses' host that IS the authoritative bus.
On Esther the local mycortex-postgres is a REPORTS MIRROR — queue and
archive state there is stale/incomplete (2026-08-21: a fleet update verified
0/6 UPDATE_RESULTs from the mirror while all 6 were present on the
authoritative bus within minutes of send — a false "fleet didn't respond"
alarm that cost a probe round).
Query bus state from Esther via SSH to the bus host:
ssh -o BatchMode=yes mosesaaron 'sg docker -c "docker exec mycortex-postgres psql -U mycortex -d mycortex -t -A -c \"<sql>\"'
Rule: when a verification query returns nothing but the fleet should have
responded, re-run it on the authoritative bus BEFORE concluding a failure.
The archive/git_sha_after discipline above still applies: results are
archived silently by handlers within ~5 min, and git_sha_after == target
is the proof of install, not the success flag.
Consume vs Peek — Inspection Rule (HARD RULE)
Inspecting a queue must NEVER consume it. Use hc inbox <agent> or
bus_peek() — both are non-destructive reads that leave messages pending.
bus_read() is a consume operation: it pops the message into processing
with a visibility timeout (VT), stealing it from the target agent's handler.
If the inspector never processes what it read, the message either times back
to pending (noise) or, if the handler already read it with its own VT, it
is gone from the live queue entirely.
Failure (2026-08-10): orchestrator inspection drained inbox_titus by
polling with bus_read() while Titus was offline. His UPDATE_REQUESTs that
should have stayed pending (waiting for his handler) were consumed into
processing by the inspector instead — the very messages the fleet update
needed to deliver. Sends kept landing while the inspector chewed the
originals, so duplicates piled up.
Rule: if you want to LOOK at a queue, use hc inbox <agent> / bus_peek().
Only code that intends to PROCESS a message calls bus_read() — that is the
agent's own handler. When in doubt, peek.
bus_access Delivery Matrix
| Access | Received? | How | Agents |
|---|
host | ✅ Yes | Runs bus server + polls. Consumes within watch interval (~10m). | moses, esther |
client | ✅ Yes (eventually) | Polls the shared bus. Consumes during next watch cycle. | joseph, kustos, gisu, titus |
Note: Poll agents use vt=0 peek (non-destructive SELECT) to check for messages. A separate handler cron pops and processes pending messages. If a message stays pending across multiple reads, it's waiting for the handler — not stuck.
Testing protocol: prove it on yourself first
Hard rule: Never send a command to a fleet agent until you've proven the identical flow works on yourself (Moses → Moses → your inbox). A test that skips the local step is not a test — it's a gamble.
Local test checklist:
- Send the EXEC/command to
inbox_moses with a unique correlation_id
- Run the handler manually:
python3 scripts/agent-message-handler.py --once
- Verify handler output shows it consumed and processed the message
- Query
inbox_moses for the EXEC_RESULT with matching correlation_id
- Parse the result — confirm exit_code, stdout, success flag all present
- Archive the test message
- Only then send to a fleet agent
Verifying a Specific Agent's Bus Connectivity
When you need to test whether a specific agent (e.g. Esther) can receive and process bus commands, follow this diagnostic flow:
Step 1 — Check if the agent is alive on the bus
sg docker -c "docker exec mycortex-postgres psql -U mycortex -d mycortex -t -c \"
SELECT action, queue, created_at::timestamptz(0)
FROM bus.audit_log
WHERE agent_name = '<agent>' AND created_at > NOW() - INTERVAL '10 minutes'
ORDER BY created_at DESC LIMIT 5;
\""
- If you see
read actions → agent's handler is running and polling
- If you see
archive → agent consumed messages recently
- If empty → agent's handler is offline or not connected to this Postgres
Step 2 — Check inbox state
sg docker -c "docker exec mycortex-postgres psql -U mycortex -d mycortex -t -c \\"
SELECT state,
(body::jsonb #>> '{}')::jsonb->>'subject' as subject,
(body::jsonb #>> '{}')::jsonb->>'correlation_id' as corr
FROM bus.messages WHERE queue_name = 'inbox_<agent>'
ORDER BY enqueued_at DESC LIMIT 3;
\\\""
pending → message landed but handler hasn't read it yet
processing → handler picked it up (VT active). Check timeout_at. Old processing = stuck handler.
- Empty → clean slate, send fresh
Step 3 — Send a known-good command
Use UPDATE_REQUEST (tests full read→process→respond→archive cycle) or EXEC with agent-diagnostic.py (deployed everywhere). Do NOT use echo, whoami, df — these are PATH commands, not scripts. Exit code -1 = "script not found".
TOKEN=$(grep CORTEX_BUS_TOKEN ~/hermes-cortex/.env | cut -d= -f2)
CORR="test-<agent>-$(date +%s)"
curl -s -X POST http://127.0.0.1:8903/api/pgmq/send \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d "{\"queue\":\"inbox_<agent>\",\"message\":{\"from\":\"moses\",\"to\":\"<agent>\",\"topic\":\"fleet-test\",\"subject\":\"EXEC\",\"correlation_id\":\"$CORR\",\"body\":\"{\\\"command\\\": \\\"agent-diagnostic.py\\\", \\\"params\\\": [], \\\"timeout\\\": 30}\"}}"
Step 4 — Wait for consumption
The handler checks every 5 min on fleet agents. After ~5 min, re-check audit log for read + archive actions. A Telegram notification like 📥 [agent] Received EXEC from moses confirms pickup.
Step 5 — Verify response (check live queue AND archives)
Results may be consumed by your own handler within 5 minutes. Always check both bus.messages (live) and bus.archives (history).
# Check live queue first
sg docker -c "docker exec mycortex-postgres psql -U mycortex -d mycortex -t -c \\"
SELECT queue_name, state,
(body::jsonb #>> '{}')::jsonb->>'subject' as subject,
(body::jsonb #>> '{}')::jsonb->>'correlation_id' as corr
FROM bus.messages WHERE queue_name = 'inbox_moses'
ORDER BY enqueued_at DESC LIMIT 5;
\\\""
# Check archives if live queue is empty
sg docker -c "docker exec mycortex-postgres psql -U mycortex -d mycortex -t -c \\"
SELECT archived_at::timestamptz(0),
(body::jsonb #>> '{}')::jsonb->>'subject' as subject,
(body::jsonb #>> '{}')::jsonb->>'from' as sender,
(body::jsonb #>> '{}')::jsonb->>'correlation_id' as corr
FROM bus.archives
WHERE archived_at > NOW() - INTERVAL '15 minutes'
ORDER BY archived_at DESC LIMIT 5;
\\\""
Look for EXEC_RESULT or UPDATE_RESULT with matching correlation_id.
If the agent reads but never responds (audit shows read but no result in inbox_moses):
- Check if
bus.send() from the agent to inbox_moses works (see "half-connectivity" pitfall)
- The agent may have the wrong CORTEX_BUS_URL or auth
- The handler may crash before
send_bus_result() (check for Telegram ❌ notification from agent)
Timeline (Esther, 2026-07-23):
- 12:05 UTC — UPDATE_REQUEST sent, read at 12:05, processed (cortex-update failed on her machine)
- 12:06 UTC — EXEC echo sent, read at ~12:08, exit=-1 (script not found)
- 12:11 UTC — EXEC agent-diagnostic.py sent, result pending
- Key finding: bus transport works (read+archive), handler processes, script execution depends on whether target script exists on that agent
⚠️ Use a script deployed on ALL agents, not a one-off.
The EXEC handler runs commands from ~/.hermes-cortex/scripts/ on the target machine. Standard PATH commands like echo, whoami, df are NOT available there. If you test yourself with a locally-created custom script but send a PATH command to fleet agents, the test is asymmetric and they will fail with exit=-1 "Script not found".
Always self-test with the EXACT same script you will send to fleet agents:
agent-diagnostic.py — deployed everywhere via cortex-update.sh, works on every agent
- Any script listed in
cortex-update.sh's register() calls
Failure pattern (2026-07-23): Self-tested with a locally-created bus-ping-test.sh (exit=0). Sent echo to Joseph, Kustos, Gisu — all returned exit=-1 "Script not found" because echo isn't in their scripts directory. The bus transport was fine; the command was wrong. Fixed by re-sending agent-diagnostic.py — all three returned exit=0.
The 6-checkpoint rule (proven full-cycle):
- Send → message in target queue (pending, correct subject/correlation_id)
- Consume → transitions pending → processing → archived
- Process → command output in handler logs
- Respond → EXEC_RESULT in inbox_moses with matching correlation_id
- Read → orchestrator queries inbox, extracts structured result
- Inbox-verify → orchestrator independently confirms the response by querying
inbox_moses BEFORE reporting results to the user
Missing any one checkpoint means the test is incomplete. The user will notice.
Critical: Telegram is for the user, NOT for you. The handler sends Telegram notifications to Luke's DM so HE sees fleet activity in real time. When you see/hear about a Telegram notification, that is NOT a substitute for verifying the bus yourself. The Telegram tells you something happened — your own inbox query proves it. Do not report "agent X responded" based on a Telegram you heard about secondhand. Query your inbox, read the EXEC_RESULT/UPDATE_RESULT, then report.
Testing technique: run handler manually while hc exec polls — when testing on your own machine, you don't need to wait for the 5-min cron tick. Send the EXEC via hc exec in background mode, then immediately run the handler manually:
hc exec moses cortex-doctor.py --json
# In another terminal or shell:
cd ~/.hermes-cortex && python3 scripts/agent-message-handler.py --once
The hc exec polls every 15s for up to 5min. The handler processes within seconds. This is the fastest test cycle.
Fleet Agent Issue Detection & Response
When you discover messages stuck processing on a fleet agent (never completes, state never transitions), the orchestrator MUST act immediately — do not wait to be asked or wait for the user to notice.
The bus is effectively broken for that agent (their handler is crashing/hanging), so you cannot reach them via EXEC either. Send diagnostic instructions via Telegram (the fallback channel):
Run a full diagnostic and report back:
1. Check handler cron exists and is enabled
2. Read handler state file (~/.hermes-cortex/state/agent-handler-state.json)
3. Check system load / memory / disk
4. Check latest handler output log
5. Run doctor
Return everything as raw output, don't summarize.
Critical rule: send SEPARATE messages per agent, not one combined message. Never combine instructions for multiple agents into one message. Intermingled instructions confuse them and produce no useful results. Each agent gets its OWN standalone diagnostic message. This is a hard rule — the user explicitly corrected this: "Next time give me separate messages. The agents get confused when they are intermingled."
After they report back, fix the identified issue (corrupt state file, handler crash, missing cron) and verify the bus recovers before proceeding.
Pitfalls
-
Reads succeed while sends fail — _bus_post Bearer→Basic cascade was gated not fallback (FIXED 2026-08-30, commit 0302c267). When the primary bus was unreachable and the fallback proxy required Basic auth (nginx validates Basic, ignores Bearer), bus_send/hc send failed with 401 while bus_list_queues/hc status still worked (_bus_get had no gate). The backup orchestrator could read the fleet bus but never dispatch — exactly the failover path it exists for. Regression test: test_bus_post_fallback_retries_basic_auth (tests/test_bus_outbox.py). Symptom to recognize: hc status says "Bus ok" but hc send returns Fallback bus also failed: HTTP Error 401. Both _bus_post and _bus_get now retry Basic on primary AND fallback.
-
UPDATE_REQUEST on a host with a stale/off-main repo silently succeeds without deploying (2026-08-21). A host whose repo is (a) on a non-main branch (doctor Repo branch WARN — e.g. a leftover titus/* PR branch) or (b) pulling from a stale origin (Repo sync PASS with origin/main pinned at an old SHA) will report UPDATE_RESULT success=true with git_sha_before == git_sha_after — the handler's git pull origin main doesn't move a divergent branch, and cortex-update deploys whatever the working tree already has. Check the target's doctor state BEFORE dispatching: hc exec <agent> cortex-doctor.py --quiet and read Repo branch / Repo sync / Deploy sync. If the target won't move, don't trust a same-SHA UPDATE_RESULT.
-
EXEC bootstrap chicken-egg: a host whose repo is stale can't run tools that only exist in newer commits. hc exec titus git-main-sync.sh → Script not found (exit -1) because the EXEC handler runs ~/.hermes-cortex/scripts/ from the DEPLOYED tree, which predates the new tool. New recovery tooling can't reach the host it's meant to fix. Remediation for a stale-origin/off-main host is a one-liner ON the host (git remote set-url origin <correct-url> if the origin is stale; git checkout main; git pull --rebase origin main) — out of bus reach by design; escalate to Luke with the exact commands.