02 · see it work

Observability: daemon + viewer

You already started the daemon and opened the viewer at the end of getting started. This page is the deeper tour: what the records mean, how to read the live timeline, how to manage the daemon as a service, and the HTTP endpoints it exposes.

The big picture

Three pieces work together:

The daemon and viewer are decoupled from the records: records keep landing on disk whether or not the daemon is running. The daemon being up is only required for live viewing.

Open the viewer

Point any browser at the daemon's own address:

http://127.0.0.1:8765/

Because the page is served by your local daemon, it is same-origin with the data it reads: it loads your records directly, with no "connect" step, no account, and nothing uploaded. (The darkmux.com/flow URL from older docs now redirects to the playback demo; for your live data, use the daemon's own address above. Loading a remote page like darkmux.com against your local daemon would be blocked by the daemon's default CORS policy anyway, which is the safe default.)

This guide intentionally ships without static screenshots: the viewer is iterating fast in this phase. Open it next to a real dispatch to see the live shape. Each card shows:

A source pill in the toolbar tells you the truth about the data path: live (reading your daemon, Redis-aggregated if configured), no daemon (can't reach the daemon), or replay (a static fixture, as on the demo).

Reading a live dispatch

Run something that emits records and watch them land:

darkmux mission launch <your-config>         # cyan: mission launch record (mints + starts the mission)
darkmux dispatch coder "..."                # green: dispatch start + (later) dispatch complete
darkmux mission finalize <your-mission-id>   # cyan: mission finalize + duration arc

With the viewer open as you run those commands, the records appear in order: a cyan operator-tier launch record, then a green local-tier dispatch pair (with the model pill visible on each green card), followed by the cyan mission-finalize record with its duration arc.

The records pair via session_id: dispatch start and dispatch complete share the same session id, which is how the wall-clock graphic computes duration. The viewer joins them automatically.

Managing the daemon

You started the daemon in getting started. How you operate it depends on how you installed darkmux.

Homebrew install (managed service)

The daemon runs under brew services: it starts at login and respawns on crash. You rarely touch it.

brew services start darkmux       # start now + at every login
brew services restart darkmux     # after `brew upgrade darkmux`
brew services stop darkmux        # stop + unregister from login
brew services info darkmux        # is it running? where are the logs?

The brew service binds 127.0.0.1:8765 and resolves its Redis connection (if any) from config.redis plus the Keychain. For the production-grade always-on setup (Redis hardening, audit substrate, log rotation, daily integrity checks), see the secure hub guide.

From source (manual)

A cargo install build has no brew service, so you run the daemon yourself. It is a foreground process that blocks until you Ctrl-C, so give it its own terminal tab:

darkmux serve                     # foreground; Ctrl-C to stop
darkmux serve --port 9000 --bind 127.0.0.1   # override the defaults

To restart after upgrading: Ctrl-C, cargo install --path . --force, then re-run darkmux serve. The viewer auto-reconnects on the next interaction; no need to refresh.

Keep the bind on loopback.

The daemon's data surface (flow records + the mission/phase state of whatever you're working on) is fleet-wide. Don't bind it to 0.0.0.0. To reach the viewer from another device on your tailnet, keep the daemon on 127.0.0.1 and put Tailscale Serve in front of it (HTTPS terminated at the tailnet node, daemon untouched): see the secure hub guide. A non-loopback bind also trips darkmux's remote-auth gate, which a browser page-load can't satisfy.

Verify it's up

curl -s http://127.0.0.1:8765/health
# {"darkmux_version":"1.17.0","flow_schema_version":"1.17.0"}

Or run darkmux doctor: the daemon: reachable check reports Pass, and shows the viewer URL.

What the doctor's daemon check does

darkmux doctor includes a daemon: reachable check that probes 127.0.0.1:8765/health with a short timeout. Three outcomes:

The check is Warn, not Fail: the daemon being off doesn't break anything end-to-end. It only disables live viewing.

The pre-dispatch nudge

When you run darkmux dispatch or darkmux mission launch review while the daemon is off, darkmux prints a one-line stderr nudge: the daemon isn't reachable, your flow records still write to disk, and you won't see them live until you start it (brew services start darkmux on Homebrew, or darkmux serve from source).

The dispatch proceeds either way: this is situational awareness, not consent-gating. You won't commit to a multi-minute dispatch only to realize the viewer would have been empty.

If you started the daemon mid-dispatch, the records that landed before you started it are still visible. The viewer fetches the whole day's file first, then opens an SSE stream for new records. No data lost.

Other daemon endpoints

EndpointWhat it returns
GET /health darkmux version + flow schema version. Used by doctor.
GET /flow/<YYYY-MM-DD>.jsonl That day's full flow file as JSONL.
GET /flow/<YYYY-MM-DD>/stream Server-Sent Events stream: new records appended to that day's file, in real time.
GET /machine/status Snapshot of lms ps --json, loaded models grouped by ownership. Backs the darkmux machine status <id> peer read. (#1426: renamed from the retired /model/status alongside the machine CLI family.)
GET /machine/resources Live machine resources: per-model RAM commitment vs observed footprint plus pressure rows. Backs darkmux machine resources and the viewer's machine lens. Read-only, zero model dispatches.
GET /missions All missions from ~/.darkmux/missions/ (status, phase_ids, timestamps).
GET /phases All phases from ~/.darkmux/phases/ (status, mission_id, depends_on, timestamps).
GET /flow-status Diagnostic snapshot of the flow substrate (active sinks, Redis health, disk health, schema drift). Same JSON shape as darkmux flow status --json; consumed by the store-status pill in the shared shell.