Skip to content

Fleet Mission Control — What It Is, In Plain English

This is the "explain it to someone who doesn't write code" version. For the technical version with file:line citations, see ../technical/ARCHITECTURE.md. Both stay in sync — if one changes, so does the other.

What is this thing?

Fleet Mission Control is a mission-control screen for a small fleet of home computers that run automated coding assistants ("agents"). It shows worker lights, a queue of jobs waiting to run, a live scrolling feed of what just finished, and a few gauges for "are we about to run out of budget." From the same screen, an operator on the machine itself can also launch a new job, cancel a queued one, and tell a computer "don't send me anything heavy right now, I'm gaming."

The 5-step flow

flowchart LR
    A["1. You open the cockpit<br/>in a browser (or an IDE panel)"] --> B["2. It asks the backend<br/>'what's happening right now?'<br/>once, at startup"]
    B --> C["3. It opens a live connection<br/>and just... waits"]
    C --> D["4. The backend checks the fleet's<br/>status files every 5 seconds and<br/>pushes ONLY what changed"]
    D --> E["5. Your screen redraws<br/>just that one panel —<br/>no refresh, no re-ask"]
    E -.->|"loop forever"| C

It's built as a website (so it opens in a browser), but it's also installable as an app on your phone or desktop (a "Progressive Web App," or PWA) because the frontend registers a service worker and ships an install manifest.

The two halves

There are two programs that make up this system, and they talk to each other constantly:

  1. The backend — a small server (written in C#) that knows how to read the actual files and status information the fleet produces: which workers are up, what jobs are queued, how much of the usage budget is left, what tickets are on the board, and — new since the first version — which repos exist where, and whether each machine is currently available. It writes back to a couple of narrow places when told to (launch/cancel a job, toggle a machine's availability), but nothing about the fleet's status is ever invented — every number on screen traces back to a real file the fleet already writes.

  2. The frontend — the actual screen you look at, built with Angular. It asks the backend for the current picture when it first loads, then keeps a live connection open so the backend can push updates the instant something changes, without the screen having to keep re-asking.

flowchart LR
    You(["You, looking at a browser tab"]) --> Screen["The cockpit screen<br/>(Angular app)"]
    Screen -- "asks once at startup,<br/>and again every minute for<br/>the board and the queue tree" --> Server["The backend server<br/>(reads fleet status,<br/>gated writes)"]
    Server -- "pushes updates the instant<br/>something changes" --> Screen
    Server -- "mostly reads;<br/>a few narrow, gated writes" --> Files["Fleet status files,<br/>a budget file, the ticket board,<br/>a repo list, availability flags"]

How a live update actually reaches your screen

Every 5 seconds, the backend checks the fleet's status files and the usage budget file. If nothing changed since the last check, it stays quiet. If something did change — a job finished, a worker's queue grew, the usage tier flipped from green to yellow — it immediately pushes that one piece of news down the open connection to every screen that's watching. Your screen doesn't have to refresh or ask again; the update just arrives and the relevant panel redraws itself. When you launch a job yourself, that one skips the 5-second wait entirely — the backend tells every screen "a job just started" the instant it accepts your request.

If your screen briefly loses its connection (Wi-Fi hiccup, laptop sleeps), it notices, shows a "reconnecting" indicator, and once it's back it re-asks the backend for everything fresh rather than trying to guess what it missed.

The panels on screen

The screen is made of one outer shell plus several smaller pieces, each responsible for one part of the picture:

  • Status wall — the shell: header bar, connection light, and the layout that arranges everything else.
  • Worker list — which listener computers are up, busy, or down, and whether each one is currently marked unavailable (see below).
  • Queue list — jobs waiting to be picked up, shown as a small tree: a job that spawned a follow-up job nests underneath it, so you can see at a glance what triggered what.
  • Command panel — pick an agent, a target computer, a repo (by name, not a typed path — see below), a ticket, and a task, and launch a run.
  • Artifact run panel — the same idea for document/presentation generation instead of code changes (see below).
  • Run stream — a live scrolling feed of runs finishing, with a cancel button on anything still queued.
  • Gate panel — runs flagged as needing a human to look at them (shown first on small screens, because it's the thing that "needs you").
  • Governor panel — the traffic-light usage tier (green/yellow/red).
  • Quota panel — the underlying usage numbers behind that traffic light.
  • Toast — small pop-up notices.

Trust and safety — what this cockpit deliberately will not do

These aren't accidents; they're the load-bearing design decisions, and each one is a direct answer to "what's the worst thing that goes wrong here":

  • The keys to the fleet never touch your browser. Launching a job or toggling a computer's availability needs real authority — the same authority that can make a fleet computer run arbitrary code. Rather than hand that master key to whatever's running in your browser tab (where a bug or a malicious extension could steal it), the cockpit asks for a short-lived, weaker pass instead — and only your own machine, sitting right at the keyboard, is allowed to ask for one. A screen you're viewing remotely over the tailnet can watch everything but can't touch anything.
  • "Unavailable" always has a floor, never a hole. When you mark a computer unavailable because you're gaming or streaming on it, the fleet still lets small jobs through by default and only blocks the heavy ones — and if you never touch the toggle at all, nothing changes: a computer with no opinion about its own availability is treated as fully available. The one exception, marking a machine unavailable for everything, is something you have to choose on purpose.
  • Generated documents stay local until you decide otherwise. When an agent produces a slide deck or a spreadsheet for you, it's saved to this cockpit's own machine and shown as a download link — it is deliberately not automatically attached to a Jira ticket or sent anywhere else. Getting a file off this machine and onto another system is a decision a person makes, not something that happens silently as a side effect of generating it.
  • A job can't be forged, only refused. Every job the cockpit hands to a fleet computer is stamped with a cryptographic signature only the fleet itself can produce — a computer that receives an unsigned or tampered job simply won't run it. That signature is also how the "unavailable" flag actually gets enforced: it's checked at the moment a job is about to be handed off, not baked in earlier, so "I just started gaming ten seconds ago" genuinely stops the next job before it lands.

Picking a repo by name, not typing a path

Launching a job used to mean typing an exact folder path by hand — easy to get wrong, and different on every machine (drive letters, OneDrive sync paths, and so on). Now you pick a repo by its plain name from a dropdown, and the backend looks up the real path on whichever machine you're targeting — you never see or type the actual path. If a repo hasn't been "scanned" on a machine yet, it simply won't show up in that machine's list rather than letting you guess a path that doesn't exist. See learn-more.md if you want the security reasoning behind why this replaced a free-text box instead of adding a "browse for folder" button.

Why the layout changes shape depending on your screen

This is the part that had a real bug, so it's worth explaining carefully.

The cockpit doesn't use one fixed layout. It rearranges itself depending on how much horizontal room it has, so it works whether you're looking at it on a wide desktop monitor, a folded laptop-sized window, or a phone:

  • Wide (desktop, 1100 pixels or more across): three columns side by side — fleet info on the left, the command/artifact/run-feed column in the middle, and the gate/gauges on the right.
  • Medium (600–1099 pixels across): two columns — command/run feed and gate/gauges on top, fleet info dropped below.
  • Narrow (under 600 pixels, i.e. a phone): one column, stacked, with the "needs you" gate panel shown first since that's the thing worth checking first on a small screen.

There's a separate, rarer case: some foldable phones can be physically bent open into a book or tent shape, with the screen split by the hinge. When a device is actually bent that way, the cockpit detects it and splits its layout across the two physical halves so nothing straddles the hinge.

The bug, honestly told: an earlier version of this code assumed that "bent-open detection" would also catch the case of a foldable phone opened flat, like a small tablet. It doesn't — a phone lying flat and open reports itself as "one continuous screen," not "two segments," even though it's a foldable device. So the flat-open case was falling through to the narrow phone layout instead of the medium two-column layout — a phone-sized layout stretched across a tablet-sized screen. It was fixed by relying on plain screen width for the flat-open case (which is what should have been used from the start) and reserving the hinge-split detection strictly for devices that are actually bent open. See making-of.md for the full story of how that got caught.

flowchart TD
    A(["How wide is the screen,<br/>and is it bent open?"]) --> B{"Physically bent open<br/>(book/tent shape)?"}
    B -- "Yes" --> C["Split across the two halves"]
    B -- "No (flat and open, or not foldable)" --> D{"How wide?"}
    D -- "1100px or more" --> E["Three columns (desktop)"]
    D -- "600 to 1099px" --> F["Two columns (medium — includes<br/>a foldable phone opened flat)"]
    D -- "under 600px" --> G["One column, stacked (phone)"]

Where the data actually comes from

Almost nothing here is invented. The backend reads:

  • Status files the fleet's own dispatch system already writes (which workers exist, what's queued, what finished).
  • A usage-budget file that a separate governor tool maintains.
  • A quota ledger for a separate agent-usage system.
  • The team's Jira ticket board, refreshed once a minute.
  • A signed list of known repos and where they live on each machine (populated by a separate scanning tool — until that tool runs on a given machine, the repo list for it is simply empty, never guessed).
  • Each machine's self-reported availability (gaming/streaming detection), if that machine's own software publishes one.

The narrow set of things it writes: a signed job file when you launch a run, a small "this machine is unavailable" note when you use the toggle, and generated documents saved to its own local disk. Nothing about the fleet's status is ever fabricated to fill a gap — a missing signal is always shown as "unknown" or "none," never guessed at.

Pointing the screen at a different backend

Normally the screen and the backend live at the same web address, so the screen just talks to "itself." But some tools that embed this screen (an IDE extension, for example) load the screen's files from one place while wanting it to talk to a backend running somewhere else. For that case, the hosting tool can hand the screen a small note before it starts up saying "talk to this other address instead" — and if no such note is given, the screen behaves exactly as before.