Dashboard¶
The dashboard is the body of a single pinned GitHub issue, labelled nexus:overview, in your asset+issue repo. The orchestrator narrates its sections and the bot pushes them (ng dashboard put); an auto-generated identity block sits above them; you read the whole thing.
(Not to be confused with the local service cockpit — the tmux-side monitor/svc.sh view of the running stack. Below.)
The pinned overview issue¶
On first launch the monitor creates an issue titled Nexus in github.repo if one doesn't already exist, labels it nexus:overview, and pins it. The orchestrator's prompt looks up github.repo from config/nexus.yml at startup; the same value steers every subsequent dashboard write.
Comments on this issue are the "ask the nexus anything" channel — spawn agents, start projects, post general questions. Comments on other open issues in the same repo flow through the same eligibility filter, so per-thread discussions can live on per-thread issues without crossing wires with the overview.
Body schema¶
The body has two auto-managed regions plus operator prose. The first is the Nexus identity block (bounded by <!-- nexus-identity:start --> / <!-- nexus-identity:end -->), generated by ng nexus-identity. The second is the dashboard proper — the content between <!-- NEXUS_DASHBOARD_START --> / <!-- NEXUS_DASHBOARD_END -->. The orchestrator narrates the dashboard sections and splices them in via ng dashboard put, which always re-reads the live body first, so any prose you write outside the markers survives every write.
<!-- text the operator wrote stays here -->
<!-- nexus-identity:start -->
... auto-generated identity block (working dir, host, repos, watcher paths) ...
<!-- nexus-identity:end -->
<!-- NEXUS_DASHBOARD_START -->
## Identity
...
## Infra
...
## Services
...
## In-flight
...
## Awaiting operator
...
## Recent landings
...
<!-- NEXUS_DASHBOARD_END -->
<!-- text below the END marker also stays -->
Six sections, in this fixed order. DASH_REQUIRED_SECTIONS (monitor/ng) is what validate checks against; scaffold emits its own literal skeleton from _dashboard_template, and the two are held in agreement by monitor/watcher/test-ng-dashboard.sh (Test 15 asserts every required section appears in the scaffold; Test 18 asserts the scaffold passes validate). So a rename must touch BOTH, and the suite goes red if it touches only one. Once both are updated it propagates to every operator nexus on the next nexus-code pull.
| Section | What's in it |
|---|---|
| Identity | A pointer to the ng nexus-identity block above — never a copy. Single source of truth for "where this nexus lives." |
| Infra | Operator-narrated health of the watcher, the orchestrator, and the pinned Claude Code version. |
| Services | Auto-populatable from monitor/services.registry (name + healthcheck); the operator narrates exceptions. |
| In-flight | Active worker windows — mirrors tmux list-windows plus per-window heartbeats. |
| Awaiting operator | Threads and decisions blocked on you. |
| Recent landings | The last few merged PRs / completed tasks, each linkable to its tracking issue and (via ng wrap-up's link comment) its uploaded report. |
The deeper convention — why the schema is standard across every operator nexus, and how identity and dashboard divide the #1 body — lives in skills/nexus.dashboard/SKILL.md.
When the bot updates the dashboard¶
The orchestrator decides on every wake whether the dashboard needs a refresh. Triggers it acts on:
- A new
reports/*.mdlands → update In-flight and Recent landings. - A tmux window appears or disappears → update In-flight.
- A registered service flips health or a PR merges → update Services / Recent landings.
- A thread or decision starts blocking on you → update Awaiting operator.
For trivial deltas (single dirty/clean flip, no other change) the orchestrator skips the refresh — the dashboard isn't a log, and noise edits inflate the <!-- NEXUS_DASHBOARD_END --> revision history.
The --- dashboard --- last-updated timestamp at the end of each watcher report (absent under status_surface.kind: external; see below) is a gentle reminder, not a hard rule; if state hasn't moved since the timestamp the orchestrator declines to rewrite.
Running your own status page instead¶
The dashboard is the default status surface. If you run your own status page, set status_surface.kind: external in config/nexus.yml (optionally url_file, a file whose first line is the page URL, and feed, the landings file the page reads). Then the orchestrator stops maintaining the dashboard, the watcher report shows --- status surface --- (your page and feed) in place of the put timestamp, and ng dashboard put still works but notes that the dashboard is not the configured surface. The overview issue stays the routing channel. With the key absent, nothing changes. Any other kind is refused, never read as the default. Details and the full table: skills/nexus.dashboard/SKILL.md ("Two modes").
The identity block (ng nexus-identity)¶
Above the dashboard markers sits the auto-generated Nexus identity block — the stable "where things live" record every operator sees. It is generated, never hand-typed, and upserted in place (replace, never duplicate) between <!-- nexus-identity:start --> / <!-- nexus-identity:end -->:
monitor/ng nexus-identity --dry-run # render to stdout, no GitHub write
monitor/ng nexus-identity --upsert-overview # idempotently PATCH the block into #1
Every field is derived from the nexus's own environment ($NEXUS_ROOT, github.repo, hostname, the watcher pidfile/log paths) — nothing is hardcoded to one operator: working directory (the headline), host, asset+issue repo + overview issue number, implementation clone (git origin + branch), and watcher pidfile / log. It's identity, not status — live "what's running" is the dashboard's job. Re-running is cheap and idempotent, so it's the repair path for a drifted identity record. The orchestrator runs it from the primary clone after a fresh setup or whenever root/host/repo provenance changes (rare).
Reading and writing the dashboard¶
Four ng verbs cover the surface — get/put are the read/write pair; scaffold/validate are the anti-drift workflow that keeps the section set stable:
# Fetch the middle-of-markers content (cached to .state/dashboard.md).
monitor/ng dashboard get
# Print the canonical six-section skeleton (one-line hint per section).
monitor/ng dashboard scaffold
# Strict gate: exit 1 if any required section is missing. For CI / pre-commit.
monitor/ng dashboard validate [--body-file new-dashboard-middle.md]
# Re-fetch the body, splice the new middle in, PATCH. WARNS on missing sections, never blocks.
monitor/ng dashboard put --body-file new-dashboard-middle.md
validate and put share their section check — _dashboard_missing_sections in monitor/ng, reading DASH_REQUIRED_SECTIONS. (scaffold runs no check at all; it just prints _dashboard_template.) The split is deliberate: validate is the hard gate (exit 1 on a missing required section, and also on an exactly-duplicated one), while put runs the same missing-section check in warn-only mode — it prints which sections are missing to stderr but still pushes, so the operator can update the dashboard in a hurry without a footgun-y hard block. Seed a fresh dashboard by piping scaffold into put.
ng dashboard put always re-reads the live body before splicing, so static prose around the markers (operator notes above the START marker, your own running scratchpad below the END marker) — and the identity block — is preserved across writes. The cache at monitor/.state/dashboard.md is for the orchestrator's diff-before-overwrite check, not the canonical state.
The verbs come from monitor/ng; the schema and the two-region body convention are documented in skills/nexus.dashboard/SKILL.md.
Anatomy of a dashboard refresh¶
1. Watcher emits a state change ────► 2. Orchestrator wakes
(paste-buffer turn)
3. Orchestrator runs ng dashboard get ────► reads current middle
4. Computes the new middle in-context
5. Writes new-dashboard-middle.md
6. monitor/ng dashboard put --body-file new-dashboard-middle.md
────► PATCH the issue
The whole loop takes one wake cycle — usually well under a minute from your phone-tap to the dashboard rendering on github.com.
What you do with it¶
The dashboard is the canonical answer to "what is the nexus doing?". Workflows:
- Start of session. Open the overview issue. Skim Awaiting operator — anything queued for you? Then In-flight — anything still running that needs a follow-up? Then Infra / Services — anything unhealthy?
- Before spawning new work. Glance at In-flight to avoid a window-name collision. The same names show up in
tmux list-windowson the host; new windows must not duplicate existing ones. - Quick "did it work?" check. Push notifications tell you the bot acted; the dashboard's Recent landings tells you whether the work shipped.
The dashboard does not replace per-issue conversations. Per-thread discussion belongs on the per-thread issue; the dashboard is the index.
Action log¶
Every meaningful action — a processed comment, a dashboard write, a worker spawn, a window close — appends one JSONL line to monitor/.state/action-log.jsonl via monitor/ng log-action. The log is append-only, intentionally cheap, and the raw trace behind the dashboard's Recent landings and In-flight sections.
Use the log when the dashboard summary feels off ("when did that worker actually start?") or when reconstructing what the orchestrator did over a session. The format is documented per-verb in the ng log-action reference.
The local service cockpit (svc.sh)¶
The GitHub dashboard answers "what is the nexus doing?"; the local cockpit answers "is the nexus running?". ./watcher leaves it open in the services tmux window after boot, and you can open it anywhere:
monitor/svc.sh --show-secrets # live auto-refreshing cockpit (read-only), URLs unredacted
monitor/svc.sh status # one status table, then exit (scriptable); secrets redacted
--show-secrets (or SVC_SHOW_SECRETS=1) shows a URL's secret query parameters, such as the JupyterLab ?token=, so the link can be copied straight from the cockpit. Every cockpit launcher passes it. Without it, a bare svc.sh and svc.sh status redact the value and name the mode-0600 monitor/.state/services/<name>.url file that holds the full URL (
One row per piece of the stack — the watcher (headless service; tri-state liveness from its heartbeat) and the orchestrator (watcher-managed; window presence + turn-end heartbeat age) pinned on top, then every service registered in monitor/services.registry. Single-key controls:
0— tail the watcher's log (monitor/.state/watcher.log) in a tmux split; the cockpit keeps focus.1–9— tail that registry service's log.n/p— next / previous page when the table overflows the pane.xclose the log split,rrefresh now,qquit.
The dashboard never launches, restarts, or kills anything; the acting verbs are explicit: svc.sh up (idempotent whole-stack bring-up — --dry-run to preview the plan, --no-services / --no-workers to scope what comes up), svc.sh start|stop|restart <name>, svc.sh logs <name>. The orchestrator is deliberately not start/stoppable here — it's watcher-managed.