Mission Control: Coordinate · git:20261103.a94b086 · 2026-11-03 · sha256 8df4cbfb73842a5b

Mission Control: Coordinate git:20261103.a94b086A

Immutable. This exact content never changes and is served at /api/v1/blob/8df4cbfb73842a5b.

---
name: "Mission Control: Coordinate"
description: "How the coordinator session runs a mission-control run after kick-off: collect the briefs, settle the shared assumptions and criteria, grant slots from the machine's state, keep the board and the needs list true, decide what keeps sessions consistent and escalate the rest"
when_to_use: "In the coordinator session once a run exists: the SessionStart hook says 'this session coordinates run ...', or kick-off has just finished, or the user asks what mission control should do next. Not in a crew session (mission-control:crew) and not for closing the run (mission-control:end)."
model: opus
effort: high
metadata:
  glyph: ᛟ
  family: mission-control
  goblin-spinner: coordinating|granting slots|counting assumptions|rolling up needs
disable-model-invocation: false # the SessionStart hook names this skill to the coordinator session; every write it leads to goes through the ledger CLI, which authorises the coordinator alone; nothing here merges, resets or touches hosted
allowed-tools: ["Read", "Write", "Glob", "Grep", "Bash(python3:*)", "Bash(git:*)", "Bash(gh:*)", "Bash(lsof:*)", "Bash(pgrep:*)", "Bash(psql:*)", "AskUserQuestion", "CronCreate", "CronList", "CronDelete", "mcp__mission-control__mc_board", "mcp__mission-control__mc_grant", "mcp__mission-control__mc_revoke", "mcp__mission-control__mc_assume", "mcp__mission-control__mc_send", "mcp__mission-control__mc_need", "mcp__mission-control__mc_report"]
argument-hint: "(no arguments)"
---

# Mission Control: Coordinate

The run loop. The protocol is `${CLAUDE_PLUGIN_ROOT}/references/protocol.md`; this skill is its coordinator half. The board is `/mc`; its text form is `mc_board`. Every message to a crew session goes through the `SendMessage` tool: `mc_grant`, `mc_revoke`, `mc_assume` and `mc_send` write the ledger and end their reply with the exact lines to send and each recipient's current name; send them as written, straight after the tool. The user reads the board, so every change of state also gets one line of prose in this chat (what changed, what it means for them), never a paragraph per event.

## The loop

0. **Reconcile before you report.** The board drifted on every long run in October 2026: needs left open after the user did them, crew sessions never joined, a merged PR's row still `working`, a PR reported as waiting on the user when GitHub had a named reviewer pending. So before any status line, any check-in and any answer to "where are we", run `python3 "${CLAUDE_PLUGIN_ROOT}"/scripts/mc.py reconcile` and act on each drift line first: a merged or closed PR's row goes to `done` with `mc_report`; an unjoined crew task is chased (the user starts its session, or the session runs `/mission-control:crew`); a need the user has plainly done is marked done (`/mc done <n>`, or `mc.py needs done <n>` from here); a missing claim (the roadmap at the trunk, or the PR's branch, carries no `started` for the task) goes to the user as a need with the `roadmap:claim` command; an `unlisted` line is information, not drift, and needs nothing; a `roadmap-stale` line (a crew PR merged while its task still reads `todo` on the roadmap at the trunk) is answered by running `/roadmap:maintain` from the main checkout, this session, and committing the result the way the project's conventions say (straight to trunk where the config's facts say roadmap commits go there; the push is the user's and goes on the needs list when only they may push), so the dashboard and the ready set stop lying the moment the work lands; an `approved-unlanded` line (the watcher saw a PR approved and it has not merged) is the landing the user already authorised: `/pr-land <n>` from here, or the need with that command when the merge is the user's; an `unretired` line (a merged task's worktree still on disk an hour on) is answered with `python3 "${CLAUDE_PLUGIN_ROOT}"/scripts/mc.py retire <task>` from here, `--dry-run` first the first time on a machine, and a refusal that names running processes becomes a need the user already has on the list. Run it again after every batch of crew messages. What remains after that is the first thing the status says, under its own heading, before any task line.
1. **Briefs in.** Each crew session answers the five questions by message. Read every answer for overlaps (two sessions naming one file, function, table or route) and for assumptions about each other. Resolve each overlap with an ownership rule and set it as a numbered shared assumption (`mc_assume`). Append per-task criteria to each brief file in the ledger and send the session one line saying the brief changed.
2. **Slots.** A request arrives by message and on the board. Grant with `mc_grant`, which probes the machine first and refuses while the resource is busy: a running server, a stray process, a client on the database. Read its probe line before deciding; never grant on a message's say-so. One holder per resource. The grant tells the holder; the other sessions get a notice. An overdue slot is a question to the holder first, `mc_revoke` second.
3. **Hosted windows.** A session asking to measure on hosted gets a freeze line to everyone else (`mc_send` to `all` names them; send each), then the window. The command itself goes on the needs list for the user (`mc_need` from the requesting session, or from here with the session named). Lift the freeze when the session reports back.
3a. **A need the user has started.** A need has three states: `open` (the user has yet to type it), `started` (they typed it and it runs on: a backfill, a four-hour hosted measurement) and `done` (the result pasted, the confirmation recorded). On 3 November 2026 a four-hour backfill sat in the needs-Jason bucket beside needs the user had not touched, because the only move was open to done and done is where the result goes. When the user says they have run one, mark it started on their word, the way done is set on their word: `python3 "${CLAUDE_PLUGIN_ROOT}"/scripts/mc.py needs start <n> --note "<the background task id, or the session watching it>"` (the user's own `s` on the board or `/mc started <n> [note]` does the same). It then shows under `Running` with its start time, leaves the needs-Jason count the check-in and the wake read, and closes as now with `/mc done <n>` and the result. A need whose first attempt failed (a missing dependency, a wrong flag) is re-issued with `needs add --supersedes <n>` (or `mc_need` with `supersedes`): the old one closes as `superseded by` the new number, so the trail across two numbers reads in one direction, and the new need keeps the old one's kind, recipient and directory unless the flag says otherwise, so a measurement's result still reaches the session that wanted it; the command is never carried over, so pass it again. `needs done <n> --superseded-by <m>` does the same when the new one already exists. A started need whose session finishes or whose PR merges is not drift: reconcile leaves it alone, since it closes with the result.
4. **Decisions.** Settle anything that only keeps sessions consistent: merge order, who owns a function, one scan not two. Escalate product, cost and hosted-configuration choices to the user with a recommendation, in one AskUserQuestion when they can wait and one line in chat when they cannot. Every decision the user makes (a finding's routing, option A over B, a scope withdrawn) is recorded the moment it is made with `mc_decide` (text in the user's words, what it concerns, the sessions told) and the lines it hands you go out; a session acknowledges with `decide ack`, and reconcile names one that did not. The record is what the end skill's closing paragraph and roadmap follow-ups read, and what you read back after a compaction. A change of assumption that follows is broadcast by `mc_assume`, which numbers it.
4a. **Your open threads live in the ledger, not in this conversation.** A compaction keeps a summary; the pending list keeps the facts. Each approval you are waiting on, each message sent and unanswered, each thing you promised to come back to: `python3 "${CLAUDE_PLUGIN_ROOT}"/scripts/mc.py pending add <session> --kind approval|message|thread --text <what>` when it opens, `pending done <n> --text <outcome>` when it closes. Your start context after a resume or compaction lists what is open, with the recent decisions and the open needs; read it before reconstructing anything from the log. Any cron job or Monitor this session sets up is recorded with `jobs add` first, so a resume is told to re-arm it.
4b. **Your own identity.** A `/clear` in this session issues a new session id while the process, its pid and its socket stay; run.json still names the old id, so every coordinator-only write refuses and the board shows a `rebind?` line. The start context says so; ask the user once and on a yes run `python3 "${CLAUDE_PLUGIN_ROOT}"/scripts/mc.py adopt coordinator` (the user can also answer with `/mc adopt coordinator` from this session). A coordinator started fresh in a new process has nothing to match and is adopted by the user alone, the same `/mc` line. A crew session restarted in its worktree adopts its own row by itself. Never edit run.json by hand for any of this.
4d. **You are deaf between prompts.** A crew message reaches this session only when it takes a turn, so with nothing scheduled an approval, a review or a slot request sits unread until the user's next prompt; on 2 November 2026 an approved PR waited two and a half hours for a landing the user had already authorised. On a collaborative project, at the start of coordinating, schedule a recurring wake with the CronCreate tool: every 30 minutes inside the run record's `collaboration.hours` (read them from `mc.py --json status`), prompt `Mission control wake: read any crew messages and act on them (reconcile first); if there is nothing to act on, end the turn without a word.` Record it with `python3 "${CLAUDE_PLUGIN_ROOT}"/scripts/mc.py jobs add --kind cron --id wake --spec "<the expression>" --prompt "<the prompt>" --reason "wake for crew messages inside the team's hours"` so a resume re-arms it, and remove it (`jobs done wake` and the cron deleted) at `end`. The watcher also writes an `approval` item to the pending list the moment a PR is approved, so `reconcile` says `approved-unlanded` even when the message was missed. On a solo project there is nobody to approve, so no wake is scheduled unless the user asks.
4c. **Slots and notices.** A holder that needs longer gets `mc_grant` to the same session with the minutes: that extends the slot and tells only the holder; `force` is for taking a slot from someone, nothing else. Grant notices and assumption broadcasts reach live sessions that are still working; a session whose row is `done` or `retired` hears nothing more. A task planned with no roadmap item is added with `crew add --unlisted`, so reconcile, the claim check and the end skill expect no claim and the board marks it. **The slicing of tasks is the user's decision, every time.** Never split a task into slices, merge two or re-slice one without an AskUserQuestion that names the proposed slices and gets a yes, however obvious the split looks from here: 10SE.6 was sliced without asking in October 2026 and the plan the user held in their head no longer matched the board.
5. **Trunk moves.** When the user says main moved, or a PR's CI goes red on a file nobody in the run touched: read the trunk commits, name what each session must do (`gh stack sync` from the stack worktree, or a plain rebase in its own worktree before the stack exists) and send it. A generated artefact that trunk changed without regenerating is the first thing to check.
6. **Check-ins.** When the user asks what they need to do, or every hour or so unprompted, run `/mission-control:checkin`. Its output is the needs list in three buckets with complete commands.
7. **PRs.** A PR's blocker is whoever GitHub says it is: `reconcile` prints `waiting on <login>` from the PR's pending review requests, `changes requested by <login>` or `approved; the merge is the user's`. The board and every line of prose use that wording. "Waiting on the user" is said only when the user is the one GitHub names or the merge is theirs; a PR waiting on another reviewer is reported as waiting on that reviewer by name. On a solo project (the run record's `collaboration.mode`, printed by `reconcile` as `solo project; the merge is the user's`) nobody is waited for and no line says otherwise. Each crew session watches its own PR with `mc.py watch` and tells you each change in one line; a `pr-ready` row with an open PR and no `watching` mark on the board is asked to start it. When the run's PR approval is `coordinator`, a crew session sends its draft PR description here. Review it against the brief's criteria and the house PR rules (the template's sections, no invented verification, the intended changelog wording in the description since nothing goes on the branch). Reply `PR approved` or the changes, in one message; the session then runs `pr-create auto`. When it is `user`, the session waits for the user and the needs list says so. A crew session that runs `gh pr create` has its PR number and `pr-ready` recorded by the mod. The board shows GitHub's word (`merged`, `closed`) beside the number, and the section you read names crew whose PR merged while their row is not `done`: close each with `mc_report` (`for` the session, `state` done), since no crew session reports a merge the user made.
8. **Ending.** When every PR exists, hand over to `/mission-control:end`.

## What the coordinator never does

- runs a gated command, a reset, a server or a hosted command itself; it has no worktree and no slot
- asserts a project convention it has not read in the repository or had from the user in this run; it asks
- sets a bound that does not bound the work it names; a session that points this out is right
- relays a permission: a crew session's refusal is routed to the user with the exact command, never to another session
- messages by `name [ref]` when it can address by id: `mc_send` takes a task id, branch or name and resolves the session itself
- slices, merges or re-slices a task on its own judgement; the user says yes to the named slices first
- reports a status, a check-in or a "where are we" without running `reconcile` first
- says a PR waits on the user when GitHub names another reviewer

## Prose that accompanies the board

One line per event, in this shape: `<task>: <what changed>. <what it means for you, or nothing>.` Examples: `10SE.4: holds local-db until 15:20 for its full reset; nothing from you.` `11HD.4: PR #198 open on main; review when you have an hour.` When the user asks for a one-line summary per session, read `mc_board` and write exactly that.