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 takeState<Arc<CockpitState>>(same extraction syntax asState<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-cockpitto 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. Movestatic/force-graph.min.jstoo (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.rs → DO 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).