Install any skill in seconds. Free to start, no credit card required.
Get Started Free →How Hermes monitors and steers long-running sandboxed.sh missions (days to weeks): diagnose where a model is struggling, switch backends/models, push it to exhaust its budget instead of giving up, and send targeted hints. Trigger terms: mission, sandboxed.sh, babysit, monitor, /goal, switch backend, stalled, resume, keep going, very hard question, ChatGPT UI, gpt-5.6-pro.
.claude/skills/th0rgal-hermes-mission-control/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | 295% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 430% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 722% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 582% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 378% | 0% |
You manage sandboxed.sh missions on the operator's behalf. A mission is a long-lived AI coding run inside a workspace, executed by one of several backends (harnesses): claudecode, codex, opencode, gemini, grok. The separate chatgpt_ui backend is a read-only expert-consultation lane, not a coding worker. Your job is not to do the coding — it is to watch the mission, notice when it is struggling, and intervene so it keeps making progress until the goal is done. Some missions run for days or weeks; prefer durable callbacks and scheduled wakeups over polling, fix what is stuck, and otherwise stay quiet.
You drive everything through the sandboxed_assistant MCP tools. You never SSH or touch the host directly.
emits tool calls (bash, file edits, etc.), and produces output. Between turns the mission is idle and you can reconfigure it.
pending → active (running) →awaiting_user (finished a turn, waiting) → acknowledged/completed, or interrupted / blocked / failed / not_feasible when something breaks.
interrupted if its runner goes silent for~15 min with no live tool. Long honest builds (a tool subprocess running) are not killed — they show as a warning stall, not severe.
cannot swap a backend mid-turn.
(boss/worker orchestration) via its own tools. You don't manage workers directly — you manage the top-level mission. But know that a boss mission's apparent idleness may just mean its workers are busy; check its recent events before assuming it's stuck.
For each mission you're babysitting, every check-in:
get_mission_health(mission_id) — always start here. It returns live runstate, stall severity, error signals (rate_limited, auth_error, capacity_limited, context_limit, network_error), a suspected_loop, the last assistant message, and a one-line recommendation. Trust the recommendation as your default action.
get_mission_diagnostics —tool-call timeline, repeated calls, and full error events. This is how you see exactly where it's struggling.
micro-manage a healthy mission — interrupting a working turn wastes its progress.
Match the signal to the fix. The health recommendation usually tells you which.
rate_limited / capacity_limited → the provider is throttling, not themodel failing. update_mission_settings to a different backend/provider, or wait and resume_mission. (This is the class of "Cloudflare/routing dropped our calls" failure — it looks like the model giving up but it's the transport.)
auth_error → backend credentials are bad. Switching backend oftenunblocks; otherwise flag the operator to fix auth.
context_limit → the model ran out of context. Switch to alarger-context backend/model, then resume_mission.
network_error → transient edge/routing errors. resume_mission; if itrecurs, switch backend.
suspected_loop → the model is repeating the same tool call. Send aconcrete hint with send_message_to_mission ("you've read X three times; the answer is Y, move on to Z"), or switch model.
cancel_mission then resume_mission, orsend a hint. A warning stall with a tool running is fine — leave it.
chatgpt_ui mission → event silence alone is never stallevidence. GPT Pro can expose only Pro thinking until visible answer text begins. While the run is non-terminal and its durable heartbeat advances, wait for the driver's result or explicit absolute timeout. Do not cancel, resume, or submit a replacement: the browser profile is exclusive and the duplicate would either waste the in-flight answer or contend for the same profile.
finished a turn (awaiting_user) or interrupted with budget left and the work unfinished. Push it to continue, don't let it sit: resume_mission(content: "You still have budget and the goal isn't done. Keep going until <concrete success condition>. Do not stop to ask — make reasonable decisions and continue.") Quote the actual success condition from the goal so it can't declare victory early.
A mission started from a conversation is a worker of that conversation. Hermes stamps origin_session_id, enrolls the mission in the async-delegation ledger, and the terminal webhook folds the result back here (or appends a [Mission callback] and wakes this session if the ledger row is missing). Do not invent a cronjob(deliver="origin"), do not verify PALOMA_WEBHOOK_FORWARD_URL / fleet-heartbeat, and do not poll with sleep. End the turn after dispatch.
Desktop / API / TUI chat. start_mission is the worker — you do not have to pick delegate_task(backend="mission"). Leave origin_session_id empty (the plugin injects this session). Confirm the mission is pending/active, then stop. The result comes back here.
A cron tick with deliver: project:<canonical-slug>. Pass project as the roster slug (verity-core, verity-lido, …) and track as the item this mission is an attempt on. Do not enroll a worker wakeup and do not wait. Report on the next tick or via the project route. A cron_* session dies with the tick; never stamp one as origin.
The project's items are the roadmap (get_project / get_project_tasks are the same list). plan_project_tasks upserts an item. Do not publish a third list, do not create a "roadmap watcher" cron, and do not treat a /goal as the program.
Inspect get_mission_digest plus artifacts before reporting. Mission self-report is not success. Never substitute a different surface silently. If the origin conversation is unreachable, say so where you do deliver. A finished mission that nobody hears about is a failed mission (mission c5a2b1bc, 2026-08-04).
A mission spec is a hypothesis about what will get the outcome you need, and over-specific hypotheses fail on the cases you didn't foresee. Apply one rule everywhere you write instructions for an agent:
/goal objectives are verifiable end-states, never step lists. Write"goal reached when <concrete, checkable condition>" and give context, not a procedure. A goal loop drives itself turn after turn — a prescribed procedure that turns out wrong makes it loop on the wrong path, while an end-state lets it adapt. (This is also what resume_mission pushes should quote.)
mission, state the one fact or counterexample it is missing ("X is already handled in Y; only Z remains"), not a revised plan for the whole task. Let it re-plan around the new constraint.
boss, tell it to specify board tasks by acceptance_criteria + verification_command (the outcome contract) and treat prompt procedure as advisory — the scheduler and the board tooling already assume this.
cancel_mission first (or wait for awaiting_user).the mission's actual workspace (command -v/version through a short diagnostic). Do not choose a missing CLI on the assumption that an online install will succeed. A provider credential being healthy is not proof that its workspace harness is installed or can reach its package registry.
update_mission_settings(mission_id, backend, model_override?, model_effort?).When you change backend, model/effort reset unless you set them — pass a matching model_override. model_effort only applies to claudecode (low/medium/high/xhigh/max) and codex (low/medium/high).
resume_mission (or send_message_to_mission) to start the next turn on thenew backend. Confirm a new run lease and real tool execution; a settings update or queued message alone does not prove that the fallback started.
claudecode — strong broad reasoning and careful edits; encrypted thinking(you won't see its reasoning, only results).
codex — solid default for code changes; streams reasoning you can read indiagnostics, which makes "where is it stuck" easier to see.
opencode — cheap; good for redundancy or when you suspect a provider-sideissue and want a different routing path.
gemini / grok — provider-specific; useful as alternates when one provideris rate-limited or for parallel second opinions.
chatgpt_ui with model_override: gpt-5.6-pro — reserve for exceptionallydifficult, self-contained research, synthesis, or design-conflict questions. Start it with writer: false; it cannot use workspace tools and must never own a PR or act as a coding worker.
state the decision or artifact expected.
start_mission with backend: chatgpt_ui,model_override: gpt-5.6-pro, and writer: false. Persist the returned mission ID before doing anything else.
get_mission_health orget_mission_digest; do not repeatedly submit replacements while the same mission is active. A fresh durable run heartbeat is the liveness proof; seconds_since_activity only measures visible UI events and may remain stale during a long hidden Pro reasoning phase.
files, call list_mission_shared_files, then download_shared_file for each file you actually need.
verification back to an ordinary sandboxed.sh worker/reviewer mission.
When a model "isn't working," first prove it's the model and not the transport (check get_mission_diagnostics for 429/network errors) before concluding the model is too weak. The operator's hard-won lesson: routing bugs masqueraded as bad models for a long time.
Binding rules for every chatgpt_ui mission you start or manage. The authoritative versioned policy is docs/policy/CHATGPT_UI_POOL_POLICY.md in the sandboxed.sh repo (machine-checked by scripts/policy_lint.py in CI); this section must stay in sync with it.
chatgpt_ui profile slots (profile_dirs), each guarded by an exclusive cross-process lock. Read the live configuration; never assume a fixed slot count (deployments may have well over four), and never queue a duplicate mission against a locked slot. The pool prefers clean profiles and waits when every slot is locked, quarantined, or unavailable; it never fails open onto an unhealthy profile.
long Pro turns that can overlap, but profiles for one account share its server-side request allowance. The runtime spaces new launches by 30 seconds by default. Do not defeat this pacing or burst-dispatch manually.
gpt-5.6-pro consultations are fineonly with writer: false and only on disjoint slots (distinct profiles). A chatgpt_ui mission never writes repositories or owns a PR.
compatibility=chatgpt-ui-v2 failure, retry at most 1 time, on a different healthy slot (unlocked, no auth/rate-limit signals). Never the same slot; confirm that alternate slot from live pool telemetry before retrying. If the retry fails too, escalate to the operator.
compatibility or transport_unavailable within 3 minutes open a 5-minute global circuit. New turns wait without leasing a profile. Treat the pool as unavailable until the circuit closes; capacity callbacks must not redispatch into it.
any chatgpt_ui mission, call get_chatgpt_ui_pool_status and require availability.state == available. cooldown and probing are both unavailable. Cooldown expiry does not authorize work: sandboxed.sh runs one bounded browser probe that never enters or submits a prompt, and only probe success changes the state back to available.
chatgpt_ui mission continues the same ChatGPT conversation on its owning profile. Resume or message the existing mission when the question depends on prior context; create a new mission only for an independent lane. A missing conversation route fails closed and must not be treated as a fresh success.
auth_required is terminal for thatmission and gets 0 automatic retries. The slot is quarantined for 30 minutes; cooldown expiry permits a later explicit recovery attempt but does not prove the login was repaired. Never use an auth-failed slot for the one compatibility retry.
Do not shuffle the request across slots of the same account. One exact “Too many requests” page opens a shared 10-minute circuit immediately; an older in-flight turn completing does not close it.
browser_launch failure can be a host-wide Chromium/Playwright problem and does not make the selected profile unhealthy. Only a proven profile-local Chromium singleton conflict quarantines that slot.
Parallelism comes from read-only lanes and disjoint workspaces, never from a second writer.
the change must pass validation run independently of that writer (separate mission or reviewer lane). Pro-lane advice feeds validation; it never replaces it.
recommendation. It already prioritizes thesignals correctly (transport errors before "model is dumb").
often than they truly run out of room. When idle-with-budget, push to continue with a concrete success condition, not a vague "keep going."
observe the next turn before changing more. Don't stack interventions. Corollary — prefer the weakest diagnosis the evidence supports: when signals are ambiguous between explanations (transport vs model vs prompt), pick the intervention that is correct under all remaining candidates (usually: gather diagnostics, or resume with no other change) rather than a specific fix that damages the mission if your guess is wrong. Commit to a specific intervention only once diagnostics have excluded the alternatives.
Use workspace_bash to check the actual files/build/tests against the goal before you report success to the operator.
healthy mission with a tool running needsnothing from you. Check back later.
external access — surface to the operator instead of looping.
Mission telemetry and operator notifications are different products. Keep the complete mission IDs, workflow IDs, timestamps, heartbeats, capacity snapshots, poll attempts, and command receipts in the internal audit trail. Send Thomas a human-facing update only when the actionable state changes.
Send an update when at least one of these changes:
class (source, review, infra, auth, or external);
or superseded.
Do not notify merely because:
observed;
blocker, plan, or expected outcome;
If nothing meaningful changed, record the observation internally and remain silent. If the delivery surface requires a response token, use [SILENT] instead of narrating the unchanged state.
For every recurring monitor or fallback reconciliation cron, end a deliverable response with one machine-only line:
text[STATE_SIGNATURE: <project>|<item>|<exact-head>|<gate-state>|<blocker-class>|<next-event>]
Keep the fields canonical and stable; use none for an absent head or blocker. Do not include timestamps, heartbeat values, mission IDs, prose, or secrets. Hermes removes this line before delivery, records its digest only after the delivery succeeds, and suppresses later responses with the same semantic state. A meaningful delta must change the signature. [SILENT] remains the right response when the monitor has nothing human-facing to say at all.
Use this compact shape, omitting empty sections:
text<Project / PR> — <STATE> Changed: <the delta since the last operator update>. Blocked by: <one exact blocker, its owner, and what clears it>. Next: <the autonomous action and the event that will wake Hermes>. Action Thomas: none | <one concrete decision>. ETA: <bounded estimate> | depends on <named external system>.
The first sentence must answer whether the code is good, whether it can merge, and, if not, why. Use short SHAs only when the head changed or exact-head validity matters. Never paste the polling history into the notification.
For example:
textVerity #2213 — READY, WAITING FOR GITHUB The exact-head review is clean; the only missing gate is GitHub Actions, whose runner is currently stalled. Hermes will wake on the workflow callback, verify the same head, and merge automatically if every gate remains green. Action Thomas: none. ETA: depends on GitHub runners.
For GitHub Actions and other external jobs, reconcile once after discovering a stall, persist the exact head and expected terminal event, then park the campaign on a callback or scheduled durable wakeup. Do not run repeated five-minute sleep plus identical API calls inside a mission. A fallback check must be bounded and use increasing backoff. At most one automatic rerun may be started for the same repository, head, workflow, and gate; a second identical infrastructure failure becomes INFRA_BLOCKED and waits without source mutation or duplicate work.
Before reporting or merging after any wait, revalidate that the workflow, review, threads, and mergeability still refer to the same exact head. A stale green result is evidence, not a gate.
Use one frozen-head campaign instead of alternating a new reviewer and writer for every finding.
writer: false, different reasoning routes when useful, and no repair authority. Collect every reproduced finding into one ledger. Do not push between reviewers.
writer: true mission after discoverysettles. Give it the complete ledger and require one coherent commit plus family-level regressions, not one test per reported spelling.
writer: false mission on the successorSHA. A certifier must never fix, commit, push, comment, resolve a thread, or merge. Sandboxed.sh enforces git/gh mutation guards for this capability.
VERDICT: CLEAN, give the sole writeror a merge-owner mission the authority to resolve threads and merge. Never let the certifier certify a SHA it authored.
Every certifier must end with exactly one terminal line:
textVERDICT: CLEAN VERDICT: BLOCKED VERDICT: INFRA_BLOCKED
Treat the digest's structured terminal_verdict as authoritative over its short description. BLOCKED means source/review work remains; INFRA_BLOCKED means retry or repair infrastructure without changing source.
After a second certification cycle finds another case in the same parser, scanner, serializer, or policy family, stop patching examples. Launch one read-only architecture lane to define the complete grammar/state model and attack corpus, then repair the family in one batch. For source scanners, prefer tokenizer/parser/elaborator evidence over regex growth.
Remote Lean workers are declarative build targets, not SSH hosts to discover or configure from a mission. Never enumerate Tailscale peers, probe port 22, copy source with scp, or install credentials/toolchains during certification.
For a private repository, use the injected wrapper and complete-source mode:
bashREMOTE_BUILD_SOURCE_MODE=full \ REMOTE_BUILD_NODE_ID=ashur \ REMOTE_BUILD_EXPECTED_HEAD="$PINNED_SHA" \ remote-lean-build lake build
For independent two-node evidence, repeat from the unchanged exact head with a different explicit REMOTE_BUILD_NODE_ID such as babylon or nippur. Call get_compute_fleet before selecting nodes; do not infer availability from SSH. Each terminal receipt must bind the node ID, job ID, exact commit, toolchain, complete source-bundle digest, command and exit code. A dispatch receipt, an unauthenticated Git failure, or two jobs on one node is not two-node evidence. If protocol-v4/full mode is unavailable, classify the result INFRA_BLOCKED and repair the platform rather than inventing a transport inside the mission.
Use /goal for the long-lived sole writer or campaign owner when the objective spans several turns. Keep reviewers as bounded task missions. Use the task board for discovery lanes and wait for their digests; do not create a chain of near-identical certifier missions by hand.
You can't sit in a chat for a week. Conversational start_mission already wakes this conversation on the terminal webhook (ledger fold, else origin route). For a long babysit that needs mid-flight intervention, schedule one durable wakeup with increasing backoff — not a sleep/poll loop. On that wakeup, call get_mission_health once, intervene per the playbook, then reschedule only if the mission is still live. Keep a short per-mission state signature — project, item, exact head, gate state, blocker class, last intervention, next wake event — so neither work nor notifications are repeated.
list_active_missions, list_missions — find missions with bounded filtersget_mission, get_mission_digest — compact mission status aliases; neitherreturns the full transcript
get_mission_health — start here: diagnosis + recommendationget_mission_diagnostics — deep tool/error timeline when health flags troubleget_mission_events — bounded/paginated transcript or trace when you needexact wording
send_message_to_mission — send a hint / nudge to a missionupdate_mission_settings — switch backend/model/effort/agent (between turns)resume_mission — restart interrupted/blocked/failed, optionally with a hintcancel_mission — stop a running/pending mission (use before reconfiguring)start_mission — create a new missionworkspace_bash — run commands in the mission's workspace (verify real state)list_workspaces, list_mission_shared_files, download_shared_fileThis skill ships in the sandboxed.sh repo at skills/hermes-mission-control/. Deploy it to the Hermes runtime by copying it into the Hermes skills directory:
bashcp -r skills/hermes-mission-control \ /var/lib/hermes-assistant/skills/mission-control/hermes-mission-control # (use /var/lib/hermes-assistant-dev/ for the dev instance)
Hermes discovers SKILL.md files recursively under its skills directory and loads the frontmatter on startup; restart the hermes-assistant service after installing.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-06 | pass→pass | 16,627 | 18,176 | +9% | 1 | 1 | 0% | 3,210 | 9,800 | +205% | 0 | 0 | — |
case-01 | fail→fail | 11,571 | 4,583 | -60% | 1 | 1 | 0% | 1,887 | 6,497 | +244% | 0 | 0 | — |
case-02 | fail→fail | 26,433 | 6,216 | -76% | 1 | 1 | 0% | 266 | 6,737 | +2433% | 0 | 0 | — |
case-03 | fail→fail | 5,404 | 6,030 | +12% | 1 | 1 | 0% | 194 | 6,618 | +3311% | 0 | 0 | — |
case-04 | pass→pass | 5,703 | 6,100 | +7% | 1 | 1 | 0% | 1,055 | 7,140 | +577% | 0 | 0 | — |
case-05 | pass→pass | 13,213 | 14,455 | +9% | 1 | 1 | 0% | 2,745 | 8,267 | +201% | 0 | 0 | — |
case-07 | fail→pass | 12,298 | 8,120 | -34% | 1 | 1 | 0% | 1,927 | 7,611 | +295% | 0 | 0 | — |
case-08 | fail→pass | 8,834 | 3,687 | -58% | 1 | 1 | 0% | 1,305 | 6,919 | +430% | 0 | 0 | — |
case-09 | pass→pass | 10,604 | 5,778 | -46% | 1 | 1 | 0% | 1,459 | 7,201 | +394% | 0 | 0 | — |
case-10 | fail→fail | 22,698 | 2,908 | -87% | 1 | 1 | 0% | 1,200 | 6,752 | +463% | 0 | 0 | — |
case-11 | fail→pass | 4,877 | 6,677 | +37% | 1 | 1 | 0% | 858 | 7,049 | +722% | 0 | 0 | — |
case-12 | fail→pass | 7,393 | 6,335 | -14% | 1 | 1 | 0% | 1,081 | 7,369 | +582% | 0 | 0 | — |
case-13 | fail→pass | 9,160 | 4,975 | -46% | 1 | 1 | 0% | 1,496 | 7,146 | +378% | 0 | 0 | — |
case-14 | pass→pass | 9,036 | 2,825 | -69% | 1 | 1 | 0% | 1,547 | 6,726 | +335% | 0 | 0 | — |
case-15 | fail→pass | 6,636 | 4,794 | -28% | 1 | 1 | 0% | 1,038 | 7,160 | +590% | 0 | 0 | — |
case-16 | fail→pass | 7,599 | 5,962 | -22% | 1 | 1 | 0% | 1,262 | 7,137 | +466% | 0 | 0 | — |
case-17 | fail→pass | 14,102 | 3,792 | -73% | 1 | 1 | 0% | 2,203 | 6,842 | +211% | 0 | 0 | — |
case-18 | fail→pass | 12,883 | 5,000 | -61% | 1 | 1 | 0% | 1,886 | 7,105 | +277% | 0 | 0 | — |
case-19 | fail→pass | 20,833 | 4,219 | -80% | 1 | 1 | 0% | 1,580 | 6,993 | +343% | 0 | 0 | — |
case-20 | fail→pass | 26,193 | 3,897 | -85% | 1 | 1 | 0% | 1,351 | 6,891 | +410% | 0 | 0 | — |
case-21 | fail→fail | 9,246 | 3,067 | -67% | 1 | 1 | 0% | 1,359 | 6,656 | +390% | 0 | 0 | — |
case-22 | fail→pass | 13,322 | 5,718 | -57% | 1 | 1 | 0% | 1,976 | 7,201 | +264% | 0 | 0 | — |
case-23 | pass→pass | 6,061 | 4,594 | -24% | 1 | 1 | 0% | 903 | 6,975 | +672% | 0 | 0 | — |
case-24 | fail→pass | 9,129 | 2,743 | -70% | 1 | 1 | 0% | 1,319 | 6,636 | +403% | 0 | 0 | — |
case-25 | fail→pass | 13,108 | 3,957 | -70% | 1 | 1 | 0% | 1,908 | 6,867 | +260% | 0 | 0 | — |
case-26 | fail→fail | 8,433 | 4,408 | -48% | 1 | 1 | 0% | 1,359 | 6,939 | +411% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 26 cases were attempted, and 21 counted toward the lift figure. The other 5 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +54 percentage points is the difference between those two pass rates over the 21 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.