Why tickets get stuck — and what's actually missing
Mission Control is great at starting work and running a worker. What it can't reliably do is decide what happens to a ticket after the worker stops. So tickets stall, and you end up nudging them by hand on Telegram. This is the one thing we're fixing.
The machinery is built — the decisions aren't
The review workflow exists in code (dev_review_qa phases, app.py:4473), and the "send it back for fixes" button exists (return_for_fixes). But the auto-progression is switched off (app.py:6150 should_auto_workflow), there's no machine check for "is this actually done", and no limit on how many times it loops. So the loop only moves if a human keeps pushing it — which never holds up.
The four ways a ticket goes quiet today
None of these resolve themselves right now. Each one waits for you.
Worker says "done" — but nobody checks the tests actually pass or the work matches the ask. It just sits.
Worker has a question. It flips to needs_input and waits — silently — for you to notice.
Worker stalls or produces gibberish. No heartbeat notices. The ticket looks "in progress" forever.
It depends on another ticket that isn't done. Nothing blocks it cleanly or wakes it up later.
Where the orchestrator fits
The fix is to split two jobs that are tangled together today: the body (Mission Control — runs workers, holds ticket state, shows the board) and the brain (one decision-maker that, after every step, answers a single question: what happens to this ticket next?).
The brain is not a worker
It never writes code or runs tests. It only decides. Think conductor, not musician.
MC stays the system of record
Tickets, state, gates, dashboard, the tmux runtimes — all stay in MC. We're adding a brain, not replacing the engine.
The contract is the only seam
Brain and body talk through one fixed contract. That seam is what lets us swap the brain later (see "Not locked to Hermes").
How the brain decides
Two layers. Cheap rules handle the obvious calls instantly and for free. The LLM brain handles the judgement calls — the ones that need it to actually read the situation. This is the intelligence that's missing today.
- Tests missing or failing → send back, automatically.
- Reviewer is the same agent who wrote it → reject (you can't review your own work).
- Looped too many times → stop and escalate.
- Depends on an unfinished ticket → block until it clears.
- Reads: the ticket, the evidence, the worker's output, comments, history, dependencies.
- Returns: one decision + a plain reason.
- Example: "tests pass but the work ignored half the ask → send back with these 3 points."
- Example: "the worker's been silent 20 min and repeated itself → re-prompt with a sharper brief."
The six decisions the brain can make
Click each one. Every decision point in MC resolves to exactly one of these.
The heartbeat — how it catches a silent ticket
Today nothing notices a stalled ticket. The brain adds a simple pulse:
A stuck ticket, fixed
Same ticket, same worker, same hiccup. The only difference is whether a brain is deciding. Flip between the two.
Not locked to Hermes
You said it: Hermes can be the brain for now, but you don't want to be married to one harness. So the brain plugs into a fixed contract. Anything that can speak that contract — make a decision and give a reason — can be the brain. Swapping it is a config change, not a rewrite.
decide( ticket + evidence + history ) → { decision, reason }where decision is one of the six. That's the whole seam. Everything behind it is swappable.
Pick a brain — same contract, different engine behind it
Why Hermes first
It already has real decision powers and is running well on the Mac. Fastest path to a working brain — no need to build decision intelligence from scratch.
Why we still abstract it
If a better/cheaper decision engine shows up — or Hermes changes — you switch by pointing the contract at the new brain. MC, the gates, the dashboard, the workers don't change.
The lanes, the gates, and what we actually build
Roles are job lanes with stable names; the runtime that fills a lane can change anytime. A ticket flows through the lanes as gates — and you watch it move on the dashboard, instead of asking Telegram.
Two-axis roles
The lane is the job (stable). The runtime is who's on shift today (swappable). The brain assigns the runtime to the lane via a small registry.
| Lane (job) | What it does | Runtime today (swappable) |
|---|---|---|
| planner | Turn the request into a plan + acceptance criteria | Opus / Codex |
| designer | UX direction before, screenshot sign-off after | Gemini CLI / Kimi |
| coder | Implement — one writer per checkout | Larry-Codex / Luci-Claude |
| qa | Independent review (never the coder) | Council / Opus |
| tester | Click it like a human, desktop + mobile | Tessa |
| validator | Final evidence gate — the stop-condition | Opus + Atlas (for system changes) |
What you'll see — the phase ribbon
A live strip on the dashboard for every campaign ticket. At a glance: where it is, who's on it, what's blocking. No Telegram round-trip.
What's reused vs what's new
Roughly 80% already exists in MC. We're crystallising the missing decision layer — not rebuilding.
Reuse (already in MC)
Runtime profiles · the dev_review_qa workflow + return_for_fixes · the worker signals (done/review/question) · interactive tmux runtimes · the orchestrator inbox · Postgres ticket state · Tessa + council gates.
Build (the new pieces)
① the brain contract + Hermes adapter · ② the acceptance gates + stop-condition (turn auto-loop on, safely) · ③ a first-class evidence packet · ④ the phase ribbon on the dashboard · ⑤ consolidate to one ticket-runtime path.
Design grounded in the live MC scan (reports/mc-architecture-report.html, 2026-05-24: auto-loop off at app.py:6150, phases at app.py:4473) and code inspection 2026-05-26 (ticket_runtime.py, mc_pickup.py, models.py workflow actions). Companion to the parked campaign plan for MC-4207. Hermes worker playbook: docs/hermes-worker-orchestration-playbook.md.