← Reports

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.

OFF
auto loop (built, disabled)
none
acceptance gate (when is it done?)
none
round cap (when to give up?)
you
who decides next, today

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.

finished?

Worker says "done" — but nobody checks the tests actually pass or the work matches the ask. It just sits.

asked

Worker has a question. It flips to needs_input and waits — silently — for you to notice.

silent

Worker stalls or produces gibberish. No heartbeat notices. The ticket looks "in progress" forever.

waiting

It depends on another ticket that isn't done. Nothing blocks it cleanly or wakes it up later.

In short: MC has a strong body (it does the work) but no brain (it doesn't decide what the work needs next). The rest of this page is the brain.

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?).

Elmar one voice in · one voice out THE ORCHESTRATOR BRAIN pluggable · swappable Decides what happens to every ticket next — advance, send back, block, escalate, unstick or wait. Uses judgement, not just rules. Hermes ▸ plugged in (MC-Claude · Codex · future) decision contract MISSION CONTROL — the body Workflow + gates Ticket state(Postgres) Dashboard Dispatch + tmuxruntimes Workers — interactive sessions (subscription auth), one tmux pane each: coder qa / review tester (Tessa) designer / validator

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.

Layer 1 · Guardrails
Deterministic. Fast. Free. They catch the obvious so the brain isn't bothered for trivia.
  • 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.
Layer 2 · The LLM decision tree
Judgement. When the rules don't give a clean answer, the brain reads the whole picture and decides.
  • 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.

ADVANCE ›
work passed — go to the next stage
RETURN_FOR_FIXES ›
not done — send back with specifics
BLOCK ›
waiting on another ticket
ESCALATE ›
needs you — a real decision
UNSTICK ›
stalled — re-prompt or re-dispatch
WAIT ›
not ready yet — check again soon

The heartbeat — how it catches a silent ticket

Today nothing notices a stalled ticket. The brain adds a simple pulse:

1 · Watch
every ticket, every few min
2 · Flag
no progress? same action twice? gibberish?
3 · Diagnose
brain reads what went wrong
4 · Act
unstick · escalate · block

A stuck ticket, fixed

Same ticket, same worker, same hiccup. The only difference is whether a brain is deciding. Flip between the two.

Ticket
"Redesign the dashboard header"
Coder finishes, but the tests fail and the work skipped the mobile layout.

    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.

    The contract (the only thing that's fixed):
    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

    Hermes
    decision contract

    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 doesRuntime today (swappable)
    plannerTurn the request into a plan + acceptance criteriaOpus / Codex
    designerUX direction before, screenshot sign-off afterGemini CLI / Kimi
    coderImplement — one writer per checkoutLarry-Codex / Luci-Claude
    qaIndependent review (never the coder)Council / Opus
    testerClick it like a human, desktop + mobileTessa
    validatorFinal evidence gate — the stop-conditionOpus + 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.

    planner
    designer
    coder
    qa
    tester
    validator
    ● on shift: coder (Larry-Codex)
    ↩ cycle: 2 of 3
    next: qa review
    evidence: 3 files · tests green · 1 screenshot

    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.

    Status: parked plan Nothing is being changed in live MC yet. This is the picture to react to. When it unparks, the first real job runs end-to-end through these gates to prove the loop closes itself — with no Telegram nudging.

    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.