# Command post — operator-driven dashboard
The Command Post is v4.3's reframe — the dashboard becomes the
**primary editing surface** rather than a viewer. Operators don't
touch JSON files for routine work; they use forms, tables, and
curated views. Edits emit on the bus so agents observe.
This topic covers v4.3's foundation stories. As of v4.3 session 7,
the registry editor (C-4 v1 weights + v2 autonomy/hats/federation),
the Logs page (C-3 v1 + v2 with all 6 sources wired —
publish-gates, approvals, invocations, notifications, score-history,
services.jsonl), custom pages (C-6 v1 CRUD + v2 widget gallery
integration), edit-via-bus integration (C-7 v1 envelope + v2
keypath diffs), and inline help (C-8 v3) are shipped. Mobile-
responsive breakpoints (C-9) and the schemars-driven generic form
generator (C-4 v3) are the remaining deferred polish.
## What's in v4.3 v1 (this stage)
Three new built-in pages joined the existing six (Overview,
Domains, Federation, Skills, Publish gates, Approvals):
- **Services** (`/brains/:id/services`) — per-peer process list:
peer_name, pid, port, uptime, log path. Refreshes every 5
seconds. Start/stop actions stay on the Federation page. Each
row has a **View log** button that opens an in-dashboard log
tail modal — surfaces the trailing ~256 KB of the peer's
spawned-process log file with a manual refresh button. Works
for any peer declared in the registry, including ones started
in a previous dashboard session (the log path is computed from
the conventional `<peer_brain>/.claude/brain/logs/<peer>.log`
rather than tied to the in-memory service registry).
- **Logs** (`/brains/:id/logs`) — filterable timeline aggregating
six ledger sources into a single view: publish-gate runs,
autonomy approvals, skill invocations, `_neurogrim/notifications`,
score-history snapshots (each annotated with the delta against
the chronologically-prior score), and service lifecycle events
(started / failed / stopped). Filter chips narrow per source.
Click any row to open a drill-down modal that surfaces the full
per-source record — publish-gate exit codes + error_detail,
approval decision metadata, invocation session id, notification
full payload, score-history detail, services lifecycle reason —
plus a pretty-printed raw payload below the curated fields so
operators can copy any field for external investigation. Refreshes
every 30 seconds + SSE-driven live updates. Service events are
written to `<project>/.claude/brain/services.jsonl` by the start
/ stop / readiness-watcher handlers, durable across dashboard
restarts.
**Cross-page toast notifications** — when a peer fails (the
canonical "operator on the wrong page misses something
important" event), a top-right toast surfaces the peer name +
failure reason regardless of which page the operator is on.
Auto-dismisses after 8 seconds, or click to dismiss. Toast
triggers are conservative by policy: only `service_failed` for
v1, with the framework reusable for future events identified as
"operator-would-miss-this" without being noisy.
- **Settings** (`/brains/:id/settings`) — five tabs:
- **Registry** (C-4 v1+v2) — sub-tabbed curated editors for
domain weights, autonomy action_types, hats with multipliers,
and federation children. Single ETag-protected Save round-trips
the whole registry; `BrainRegistry::validate()` runs server-side.
- **Custom pages** (C-6 v1+v2) — add/delete custom named pages.
The page itself opens at `/brains/:id/p/:pageName` with full
edit-mode support: Customize → add widgets via the v3.4 catalog
→ resize / reorder / per-widget config → Save. PUTs to
`/api/brains/:id/dashboard-pages/:name/layout` (gated by
`--allow-mutations`).
- **Culture** — read-only viewer (culture is a contract, not
a setting).
- **Queue config** — read-only viewer (editor lands with the
queue backend selector).
- **Publish gates** — pointer to the dedicated `/publish-gates`
page.
## What's deferred
| **C-4 v3** schemars-driven generic form generator | Useful for adopters declaring custom registry sections we don't ship curated forms for. Curated forms (v2) cover the in-tree sections operators actually edit; the generator is the long tail. |
| **C-4 v3** 3-way merge UI on ETag conflict | v1 ships ETag detection + reload-on-conflict banner. The merge UI is value-add when concurrent operator edits become common — today's single-operator usage rarely hits it. |
| **C-4 v3** domain definitions / `_todo_<name>` editor | Less frequently-edited surface; benefits from text-editor review more than form fields. |
| **C-6 v3** custom-page polish | v2 ships the widget gallery integration (operators compose pages through the UI). v3 follow-ons: page rename, icon picker, per-page title (vs. the kebab-case id), folder grouping at the 8-page limit. |
| **C-9** mobile-responsive breakpoints | Audit each page at 375px viewport; collapse sidebar at <768px. Best paired with operator visual review. |
## Multi-page schema (v2)
The new `dashboard-pages.json` shape supersedes v3.4's single
`dashboard-layout.json`:
```json
{
"schema_version": "2",
"brain_id": "alpha",
"pages": {
"overview": [...widgets...],
"custom-pc-state": [...widgets...]
},
"page_order": ["overview", "services", "logs", "settings", "custom-pc-state"]
}
```
**Backward compat**: when only the v3.4 file exists,
`read_dashboard_pages` synthesizes a v2 view with the old layout
under `pages.overview`. Operators don't lose their work when the
adopter Brain upgrades binary versions.
**Built-in pages**: Overview, Services, Logs, Settings, Approvals,
Publish gates, plus the existing v3.4 routes (Domains, Federation,
Skills). The frontend renders these regardless of what's in the
`pages` map. The map governs widget content (Overview's layout,
custom pages); built-ins have hardcoded React components.
In v1 of S15, the v2 schema is **defined + read-compatible** but
not yet wired into the existing `dashboard-layout` endpoints. Full
migration lands with C-6 (custom pages) when the dynamic-route
runtime derivation is actually exercised.
## CLI parity invariant
Per S15 epic refinement, **every UI mutation maps to a documented
CLI surface**. Each form on the Registry editor (C-4 v1+v2) has a
CLI equivalent — adopters who prefer text-editor edits can skip the
dashboard entirely; the file system stays the source of truth.
| Domain weight slider | Edit `config.domain_weights.<name>` in `brain-registry.json` |
| Autonomy action_type level dropdown | Edit `config.autonomy.action_types.<name>.default_level` |
| Hat domain multiplier slider | Edit `config.hats.<hat>.domain_multipliers.<domain>` |
| Federation child CRUD | `neurogrim federation register --name <name> --path <path>` (add); edit `config.children.<name>` (modify); remove the entry (delete) |
| Federation rewire | `neurogrim federation rewire --child <name>` (CLI today; button-driven flow is C-4 v3) |
| Add domain | `neurogrim domain new <name>` (existing) |
## Inspectability discipline
The on-disk JSON/YAML remains the canonical authority. The
dashboard pulls from disk on each refresh; an operator's `vim`
edit shows up in the dashboard within 30 seconds via SSE-driven
refetch. Dashboard edits flow back to disk through the C-4 v1+v2
PUT path — atomic temp-file-rename with ETag-protected concurrency.
Adopters can `tail -f .claude/brain/dashboard-pages.json` (or
`.claude/brain-registry.json`) to watch their changes propagate,
the same way they can `tail -f` any other Brain artifact.
## Edit-via-bus design (C-7)
Every UI mutation emits on `_neurogrim/config-changes`. v1 (the
foundation) shipped a minimal envelope; v2 adds a keypath-level
`diff` so agents subscribed to the topic react surgically without
re-fetching the whole document.
### v2 payload shape (registry edits + layout changes)
```json
{
"timestamp": "<RFC3339>",
"brain_id": "<id>",
"summary": "<one-line human-readable summary>",
"diff": [
{
"path": "config.autonomy.action_types.edit-code.default_level",
"op": "replace",
"before": "approve",
"after": "auto"
},
{
"path": "config.children.python-starter.weight",
"op": "replace",
"before": 1.0,
"after": 0.8
}
]
}
```
### Diff semantics
- **Path format** — object keys joined with `.`; array indices
bracketed. Examples: `config.domain_weights.test-health`,
`widgets[2].size`. Top-level scalar replacements (rare — our
domain's roots are always objects) use the `"$"` marker.
- **Ops** — `add` (new path), `remove` (path gone), `replace`
(existing path's value changed). Mirrors RFC 6902 (JSON Patch).
- **Before/after** — `before` is omitted for `add`; `after` is
omitted for `remove`; both populated for `replace`.
- **Truncation** — capped at 100 entries to keep queue payloads
bounded. Realistic operator edits touch 1-3 paths; widget
re-orderings emit replaces per index. Pathological all-fields
edits truncate silently (consumer notices via the count).
- **Diff scope** — registry edits diff the whole registry JSON;
layout changes diff just the page's widget array (not the whole
multi-page config), matching the URL-scope of the PUT.
### Which actions carry diffs
| `registry_edit` | yes | natural before/after via the GET that loaded + the PUT that saved |
| `layout_change` | yes | both the legacy `dashboard-layout` and the v4.3 per-page `dashboard-pages/:name/layout` PUTs compute it |
| `layout_reset` | no | DELETE has no after-state; the summary is enough |
| `page_added` | no | trivial (single page added); summary names the page |
| `page_removed` | no | trivial (single page removed); summary names the page |
| `approval_resolved` | no | different shape entirely (approval action_id + decision) |
Agents subscribed to the topic observe operator activity in
real-time — the substrate for "agent reacts when operator changes
the autonomy block" workflows. SSE-pushed via the v4.1 bus.
### Subscriber example (agent reacting to autonomy changes)
```python
# pseudocode — actual queue subscription via the MCP queue tools
for event in subscribe("_neurogrim/config-changes"):
if event["action_type"] != "registry_edit":
continue
for change in event.get("diff", []):
if change["path"].startswith("config.autonomy.action_types"):
# The operator just dropped or raised an autonomy floor.
log_autonomy_shift(change["path"], change["before"], change["after"])
```
The keypath path lets the agent route to the right handler without
parsing the registry; the before/after lets it react to the
direction of the change (loosened vs tightened) rather than just
"something autonomy-related".
## Conflict detection (C-4 v1)
Two operators edit the same registry section concurrently — UI A
loads version N, UI B loads version N, both Save. C-4 v1 ships
ETag-style versioning: the GET response carries a SHA-256 fingerprint
of the file bytes; PUT echoes it back. The server rejects with 409
Conflict + `code: "etag-conflict"` when the on-disk fingerprint
differs.
The v1 mitigation is a "Reload" button on the conflict banner —
the operator loses unsaved changes. The 3-way merge UI is a v3
follow-on; today's single-operator workflow rarely hits it
because `vim` users typically don't have concurrent UI sessions
to the same Brain.
## See also
- `neurogrim explain methodology` — the conceptual model
- `neurogrim explain queues` — v4.1 bus that edit-via-bus uses
- `neurogrim explain publish-gates` — v4.0 sibling pipeline
- `neurogrim explain secrets` — v4.2 secrets infrastructure (S14-S-6
is the future Settings-tab editor for `secret-refs.yaml`)
- `roadmap/epics/S15-command-post-ui.md` — story-level plan
- `crates/neurogrim-dashboard/src/pages.rs` — multi-page schema +
backward-compat read
- `crates/neurogrim-dashboard/frontend/src/components/services/` —
Services page implementation
- `crates/neurogrim-dashboard/frontend/src/components/logs/` — Logs
page
- `crates/neurogrim-dashboard/frontend/src/components/settings/` —
Settings page