The cockpit

August 12, 2026 · View on GitHub

The cockpit is DevPilot's operating surface: a work horizon, a planning agent, wave-based dispatch, and the instruments that tell a conductor whether their fleet is about to run dry.

It ships inside the open-source CLI.

npm i -g @devpilot.sh/cli
devpilot serve            # → http://127.0.0.1:3847

That was not true until recently. devpilot serve used to start a second, Fastify implementation of the API and serve no UI at all — its source read // Note: In a full implementation, this would open the UI. The cockpit existed and ran only from a repo checkout. See Packaging below.


The mental model

Everything you intend to build sits in one queue and gets more structured as it moves toward dispatch.

DIRECTIONAL  →  SHAPING  →  REFINING  →  READY  →  dispatched to the fleet
 a one-liner    feature      plan under   specced,
 you captured   intent       review       staged
ZoneMeansVisual weight
READYSpecced, dependency-resolved, staged. One click dispatches.Largest, highest contrast
REFININGA plan exists and you are reviewing it.Medium
SHAPINGFeature-level intent; the planning agent can pick it up.Smaller
DIRECTIONALA rough thought. Capture-first, zero structure.Smallest, quietest

The weights are not decoration — they are spec/DESIGN.md §2.1, and they exist so a conductor can tell dispatchable work from a scratchpad thought without reading a word.

Saying it on the screen

The model above used to live only in this file. On the screen it was four columns of internal vocabulary, side by side, with nothing to suggest they were one pipeline rather than four unrelated lists — and the queue runs right to left, which is the opposite of what most people assume. Three additions state it:

  • A flow rail above the zones: ← dispatched to the fleet · work moves left as it gets more specced · new ideas enter here. It is a sentence, not an animation, so the direction survives a screenshot.
  • One line of plain English per zone header — "Specced and staged — one click dispatches" under READY, and so on. The jargon stays, because it is the vocabulary of the CLI and the API. It just no longer arrives unexplained.
  • The Fleet Status panel says what it is: "Running what READY dispatched". It sits at the left end of the horizon — the end work flows toward — and nothing had connected the two.

The core claim: the bottleneck is planning throughput, not agent capacity. Agents consume specs faster than a human writes them, so the cockpit's job is to make the conductor faster than the fleet.


Instruments

SignalWhat it measuresThresholds
RunwayHow long the READY queue lasts at current fleet velocityAmber < 4h, red < 2h
Idle warningA session >70% done with nothing queued behind itAmber pulse
Idle imminent>90% done with no READY itemRed pulse + badge
Conductor scorePlanning throughput over time

An idle agent is the one cost that is entirely avoidable, which is why two of the four instruments are about seeing it coming.

Both headline numbers used to be bare values. Runway: 3h 12m named no unit and implied no direction; Score: 742 was out of nothing, and its "click to view breakdown" tooltip pointed at a button with no handler. Now:

  • Runway reads over two lines — the value, then before the fleet idles. Hovering or tab-focusing it gives the thresholds. The caption is the fix; the tooltip is the footnote. A tooltip nobody hovers explains nothing.
  • Score opens the per-dimension breakdown spec/DESIGN.md §8.2 always called for, each dimension captioned with what it measures.

Rendering that breakdown immediately turned up a spec divergence that a bare total had hidden: the five dimensions are capped at 200 each in the implementation, not the 250/250/200/200/100 in §8.1. The seeded velocityTrend: 138 would be an impossible 138 / 100. The flat caps are the behaviour everywhere — schema defaults, /api/score, and the clamps in the dispatch and orchestrator-complete routes — so that is what the UI draws. §8.1 is annotated; which weighting is correct is still an open product question.

The REFINING cards lost a number for the same reason. Each carried a 32px progress ring fed by plan ? 100 : 0 — a permanent, prominent 100% that measured nothing and read as a confidence score. §6.1.1 asks for the words "Plan Ready" on the summary line, so that is what is there.


Motion language

Defined in src/styles/motion.css. One rule governs it:

Motion is diegetic. Every animation encodes fleet state. If a motion does not represent state, it does not belong.

That rule is what makes an animated operational screen defensible rather than exhausting — in a cockpit, a blinking light means something.

ClassEncodes
dp-sweepRunway. The rate is the signal — 4.5s healthy, 2.2s amber, 1.1s critical
dp-pulse-warn / dp-pulse-urgentIdle risk, at two urgencies
dp-breatheA live agent. Slow and low-contrast: "working" must not compete with a warning
dp-flowThe critical path through a dependency graph
dp-enter / dp-staggerArrivals — a zone reads as assembled, not repainted
dp-launchAn item leaving READY for the fleet
dp-ambient / dp-radarThe only exception. Atmosphere, confined to backdrops carrying no data

Accessibility

Every animation is switched off by a single prefers-reduced-motion block at the bottom of motion.css. It is a wildcard list rather than per-rule opt-ins, so a new animation cannot forget to participate.

Motion is always a second channel — colour and text carry the same signal — so reduced motion loses emphasis and never information. Add animations to that block when you add them.


Wave planning

A plan is not a checklist. The planning agent assembles live fleet context — which sessions are active, which files are locked in flight, what capacity each repo has — and returns a dependency graph. Independent tasks are grouped into waves that dispatch concurrently; when every task in wave N finishes, wave N+1 stages itself.

Because it is a DAG, the critical path is computed rather than guessed: you find out which task gates the release before you start it.

View an executing plan at /waves. Critical edges render emerald and flow; everything else stays grey.

An edge is on the critical path only when both endpoints are on it and are adjacent in it. Testing endpoints alone lights up every edge that merely touches the path, which turns the longest chain into a bush.


Running it locally

pnpm dev:app                      # cockpit on :3000 (PORT= to change)
pnpm --filter @devpilot.sh/core db:seed     # horizon items, sessions, plans
node scripts/seed-wave-plan.mjs             # an executing wave plan

Two traps worth knowing:

Seed paths are relative to cwd. Running the core seed through pnpm --filter puts you in packages/core, so it silently creates a second database there. Pass an absolute path:

DEVPILOT_SQLITE_PATH="$PWD/.devpilot/data.db" pnpm --filter @devpilot.sh/core db:seed

Never run pnpm build:app while pnpm dev:app is running. The production build overwrites .next underneath the dev server and every route starts 500ing with Cannot read properties of undefined (reading '/_app'). Stop the dev server, rm -rf .next, then build.


Packaging

devpilot serve runs the cockpit's own Next server. There is one API implementation and one UI.

next.config.mjs         output: 'standalone' — traces the server and its deps
scripts/bundle-cockpit  assembles that into packages/cli/ui (~24 MB)
package.json            "ui" in files; prepublishOnly runs the bundler

The bundler copies .next/static and public explicitly. Next does not do this for you, and a cockpit missing .next/static boots fine and renders with no CSS — a failure that looks like a styling bug rather than a packaging one. The script hard-fails instead.

tests/e2e/cockpit-serve.test.ts guards the contract: if ui falls out of files, npm publishes a CLI with no cockpit and no other test would notice.


Known gaps

  • Wave hand-off is not animated. When wave N completes and N+1 stages, the product's most satisfying moment happens invisibly.
  • three-panel and timeline layouts are declared in the switcher and fall back to the default.
  • The DAG is visually flat compared to the marketing hero image it echoes — no layered depth, haze, or bloom.
  • Narrow viewports are unverified. The top bar hides its button labels and the flow rail drops its middle caption below lg / xl, but none of that has been seen. Chrome's resize_window reports success and window.innerWidth does not move — the same tooling failure that left the marketing site's mobile layout unverified. Needs a real narrow window.
  • Score sparklines. §8.2 asks for a sparkline per dimension in the expanded card. No score history is plumbed into the fleet store, and a fabricated trend line is worse than none, so the breakdown ships without them.