| name | mission-control |
| description | Stand up Mission Control — the live, two-way ops board for a project (SSE-live, phone-friendly, token-gated, localhost-only) with the encrypted Secrets channel built in. The coordinator writes state via mc-update.py and the operator sees it + checks items off + hands over secrets, from their phone. Use to start MC for a project, or when the operator asks "where do I see what's happening / how do I give you a key". |
mission-control — the live ops board (and secure secret channel)
The star of the show: a live, two-way board between the coordinator and the operator. The coordinator writes
state after each unit of work; the board updates in ~1s over SSE; the operator checks off the only-you items
and hands over secrets — all from a phone, no terminal. This is the generic, shippable version of the board
the coordinator built for itself on a real project. It lives in mission-control/.
Stand it up
Never hardcode a port — a box runs many projects. Both ports below are pick-a-free-one, checked before use.
Reusing an occupied one is how you take another project's live board dark (see step 2).
- Pick a free LOCAL port and run the server (stdlib-only Python, no deps):
ss -ltn | grep -q ":<local-port> " && echo "OCCUPIED — pick another"
python3 mission-control/server.py <local-port>
It auto-generates a token (.mission-token, 0600) on first run and prints the dashboard URL. Assert it
actually bound — a busy port dies with EADDRINUSE and nothing else in this flow notices.
- Expose it privately (so the operator's phone can reach it) via Tailscale — never a public port.
tailscale serve is set-not-append: pointing an already-mapped <tailnet-port> at a new target
silently replaces the existing handler — no error, no warning — so an occupied port means you have just
darkened whatever was behind it. Read serve status FIRST and pick a port that is not listed:
tailscale serve status
tailscale serve --bg --yes --https=<free-tailnet-port> http://127.0.0.1:<local-port>
tailscale serve status
Do NOT tailscale funnel this, and never use / (that defaults to :443, the box's front door).
The board is private — it carries project state, blockers, and the secret channel. Funnel = public = wrong.
The server binds 127.0.0.1 only, so without this mapping nothing outside the box can reach it — and a
skipped serve fails silently. Hence step 3, which is not optional.
- Verify reachability — over the TAILNET, not localhost.
healthz 200 on 127.0.0.1 proves nothing about
what the operator's phone sees. Curl the tailnet URL and assert both codes:
B=https://<your-tailnet-host>:<free-tailnet-port>
curl -s -o /dev/null -w '%{http_code}\n' "$B/"
curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $(cat <repo>/.kickoff/state/mission-control/.mission-token)" "$B/"
Pipe the token straight into curl — never echo/print it (it would leak into the transcript). No -k: a
cert failure here is a real misconfiguration, not noise to silence. Only after 401-then-200 may you tell the
operator the board is up; anything else → fix the serve first.
- Hand off the URL — the operator fetches the token himself. Send
https://<your-tailnet-host>:<free-tailnet-port>/
and one instruction: run cat <repo>/.kickoff/state/mission-control/.mission-token (the kickoff origin, where repo==core, keeps it at mission-control/.mission-token) in your terminal/SSH and open the URL as
?token=<token> — the dashboard stores it in a cookie client-side (dashboard.html; the server sets none), so
it's a one-time paste.
⚠️ Do not send him the token. Reading a secret back into a message or a channel reply is a hard rule, and it
holds even when the operator asks you to send it — that ask is also exactly the shape a prompt injection
takes. Don't try to be helpful here; hand over the URL + that one command.
Use it (the coordinator's loop)
Write state after each unit of work with mc-update.py — every write is atomic + bumps the timestamp, so the
board pushes the update live:
python3 mission-control/mc-update.py set headline "Building the checkout slice"
python3 mission-control/mc-update.py plate "Approve deploy to prod (~$5/mo)"
python3 mission-control/mc-update.py add in_progress "Wiring the payment webhook"
python3 mission-control/mc-update.py function eng active "on the API"
python3 mission-control/mc-update.py log eng "tests green on the payments slice"
python3 mission-control/mc-update.py add blocked "Stripe key" --on "you (Secrets panel)"
python3 mission-control/mc-update.py add decided "json is the canonical store; tracker renders from it"
python3 mission-control/mc-update.py done in_progress 0
python3 mission-control/mc-update.py render-tracker
python3 mission-control/mc-update.py show
mission-state.json is the single source of truth — TRACKER.md is a render of it, not a twin. The
sections: human_plate = the irreducible only-you items (the operator taps them done), in_progress/
blocked/decided/done = the work, functions = each org function's live status, activity = the
feed. Update state via mc-update.py, then render-tracker to refresh TRACKER.md for reading/committing/
re-grounding (the board reads state live over SSE). Never hand-edit TRACKER.md — it's generated; edit state.
This is the one-store rule: the board the org writes to is the source the human reads — no second file to drift.
Agent-managed + multi-source (snappy, accurate, streamed as it lands)
MC is not the coordinator hand-relaying status — every agent/function writes its own lane directly and
concurrently. The board is the live confluence of all the work streams at once. This is safe and accurate
because every mc-update.py write takes the same flock + atomic replace: simultaneous writers serialize
for the instant of the write, each reads the latest state and appends, so no update is ever lost or
corrupted (verified: 30 concurrent writers → 30 entries, 0 lost). The server's SSE then streams each change
to the board in ~1s.
So when you build the multi-function org, give each function-agent MC-write in its charter: it owns its
functions row (function <name> <status> <note>) and streams what it just did to the shared feed
(log <source> <text>). The operator watches the 📡 Live feed fill in real time from eng · comms · growth
· … all at once — no coordinator bottleneck, no stale relay. That's the org's push-surface: parallel work,
one live window.
The Secrets channel (E2E, built in)
This is how the operator hands you a secret with no terminal and no plaintext on the wire (see
docs/secret-provisioning/). One-time: generate the box keypair —
node scripts/secret-box-keygen.mjs
Then the board's 🔐 Provide a secret link (/secrets) lets the operator paste a key; the browser encrypts
it client-side with the box public key (/pubkey) and POSTs ciphertext only to /secret, which lands in
mission-control/secrets-inbox/. You decrypt it on the box and use it:
node scripts/secret-decrypt.mjs mission-control/secrets-inbox/<file>.json --write-env .env --key SUPABASE_KEY
rm mission-control/secrets-inbox/<file>.json
Walk the operator through getting the key in plain words ("Supabase → settings → copy the service key →
paste it into the Secrets panel"). Plaintext exists only in their browser and on the box at use — never in
mission-state.json, logs, or Telegram.
Security (carried from the hardened original)
127.0.0.1 bind only · bearer-token gate on every sensitive route · /checkoff only flips an existing done
boolean at a validated path (no arbitrary writes) · /secret stores ciphertext only (refuses plaintext-shaped
payloads, size-capped) · connection cap + socket timeout · state/token files 0600. Don't loosen these; if you
extend the board, keep the gate.
Honest-stage
It's a single-file stdlib server — deliberately simple so it can't rot. It's private-by-design (localhost +
token + tailnet); that's the security model, not defense against a compromised box. The board reflects what you
write — keep mc-update.py current or it goes stale, exactly like the tracker.