kb://architecture/cockpit-option2-2026-06-01active2026-06-17

openalice-cockpit — MC-decouple Option 2 (2026-06-01)

openalicedesignalice-core

openalice-cockpit — MC-decouple Option 2 (2026-06-01)

Read-only dashboard relocated out of the agent. Designed by a read-only Explore agent; execute AFTER the M3 route-deletion lands (it edits api-handlers mod.rs/v2.rs — don't collide).

Goal

Agent = headless file-first runtime. The rank/graph/memory dashboard moves to a standalone read-only Rust binary openalice-cockpit that reads the agent's store/files directly. Preserve working code via git mv — NO rewrites. NAO especially values /graph (memory graph) → move 1:1.

New crate crates/openalice-cockpit/ (binary)

  • Deps (minimal): axum/tower/tower-http/tokio/hyper, serde(_json), chrono, uuid, thiserror, anyhow, tracing, async-trait, parking_lot, linkme + internal read crates: openalice-schemas, -core, -store, -agents, -agents-memory, -storage-v2. NO api-router/api-handlers/middleware/kernel.
  • `src/state.rs` — CockpitState (replaces AppState): { store: Arc<dyn Store>, started_at: Instant, version: String } + get_store(). Handlers take State<Arc<CockpitState>> (same extraction syntax as State<Arc<AppState>> → minimal signature edits).
  • `src/main.rs`: openalice_store::register_backends()new_store("file://{alice_data_dir()}")Arc::from → CockpitState → axum serve on 0.0.0.0:8081 (agent is 8080). SAME store-open as agent.
  • Add crates/openalice-cockpit to root Cargo.toml workspace members.

Per-view (git mv crates/api-handlers/src/http/routes/<X>.rs → cockpit/src/routes/<X>.rs)

  • knowledge_graph_view.rs (/graph + /api/v1/memory/graph) → move 1:1; only swap state.kernel.store()state.store.clone() + AppState→CockpitState import (3 handlers). Reads memory store keys + v2 identities on disk. Move static/force-graph.min.js too (git mv).
  • rank_chart.rs (/public/rank/chart + /v1/rank/history) → move as-is (file I/O via spawn_blocking, no store use); signature swap only.
  • chat_editor_view.rs (/memory/chat/{scope}) → move GET handlers; DROP the PUT handler (cockpit is read-only — mutation stays on the agent's same endpoint). Reads identity.md.
  • status_widget.rs (/public/status[.json]) → move + adapt: state.kernel.get_driver() → env-var checks; openalice_schemas::llm_usage::snapshot() (in-process) → read ~/.openalice/data/{agent}/llm_usage.json (agent writes it in a tiny periodic task — FUTURE/separate; empty-vec fallback meanwhile).
  • dashboard.rsDO NOT MOVE. 100% in-process scheduler/budget/driver state, no file equiv. Keep in agent (its /v1/dashboard|stats|budget are the canonical read interface). If cockpit needs it later: agent writes dashboard_snapshot.json.
  • Copy (not move) the needed helpers from routes/helpers.rs (write_error/json_response/ct_eq/validate).

Agent-side cleanup (after moves) — in api-handlers/src/http/routes/mod.rs

Remove the 4 moved modules' pub mod + .merge(X::routes()) (keep dashboard). Then REGENERATE the spec: python3 scripts/internal/extract_routes.py && python3 scripts/internal/generate_openapi.py. Gate: cargo build + cargo test -p openalice-api-handlers --no-run + contract tests + cockpit builds + golden Cat-A.

Deploy

cockpit.blal.pro via Traefik (mirror openalice-mc); mount the agent data volume read-only (:ro); share STATUS_TOKEN via env. Concurrent reads safe (FsStore). Rollback = git revert the moves.

Order: (1) finish M3 route-deletion → (2) create crate + git mv + adapt + agent cleanup + regen spec

→ (3) gate green → (4) deploy. Then Option 3 (per-section audits).