Runtime Dashboard Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.
Goal: Build a read-only localhost dashboard for runtime memory/debugging with scoped status, events, and graph-inspection API endpoints.
Architecture: Add src/zaxy/dashboard.py with a dependency-light HTTP server built on Python's standard library. The first slice serves bundled HTML/CSS/JS plus read-only JSON endpoints backed by existing Eventloom memory status/log helpers and bounded graph adapter methods. Add zaxy dashboard CLI wiring without coupling the dashboard to MCP.
Tech Stack: Python 3.11 standard library HTTP server, Typer CLI, Eventloom helpers, Neo4j graph adapter interfaces, plain HTML/CSS/JS, Cytoscape.js loaded by the browser UI.
---
File Structure
- Create
src/zaxy/dashboard.py: scope resolution, API model serialization, read-only HTTP handler, static assets, and server runner. - Modify
src/zaxy/__main__.py: add thezaxy dashboardTyper command. - Create
tests/test_dashboard.py: unit tests for scope, API routing, read-only enforcement, events/status serialization, static UI references, and graph adapter limits. - Optionally modify
src/zaxy/graph.pyin a later task only if the dashboard needs concrete read-only graph query methods beyond the test adapter interface.
Task 1: Dashboard Scope And API Models
Files:
- Create:
src/zaxy/dashboard.py - Test:
tests/test_dashboard.py
- [ ] Step 1: Write failing tests
Add tests that prove dashboard scope defaults to the active workspace and does not discover unrelated projects:
from pathlib import Path
from zaxy.dashboard import DashboardConfig, resolve_dashboard_scope
def test_dashboard_scope_defaults_to_current_workspace(tmp_path: Path) -> None:
workspace = tmp_path / "project"
workspace.mkdir()
scope = resolve_dashboard_scope(DashboardConfig(workspace=workspace))
assert scope.workspace == workspace.resolve()
assert scope.eventloom_path == workspace.resolve() / ".eventloom"
assert scope.host == "127.0.0.1"
assert scope.port == 8765
assert scope.read_only is True
def test_dashboard_scope_accepts_explicit_eventloom_and_session(tmp_path: Path) -> None:
workspace = tmp_path / "project"
custom_eventloom = tmp_path / "memory"
workspace.mkdir()
scope = resolve_dashboard_scope(
DashboardConfig(
workspace=workspace,
eventloom_path=custom_eventloom,
session_id="agent-1",
domain="demo",
host="localhost",
port=9000,
)
)
assert scope.workspace == workspace.resolve()
assert scope.eventloom_path == custom_eventloom.resolve()
assert scope.session_id == "agent-1"
assert scope.domain == "demo"
assert scope.host == "localhost"
assert scope.port == 9000
- [ ] Step 2: Run tests to verify RED
Run: pytest tests/test_dashboard.py::test_dashboard_scope_defaults_to_current_workspace tests/test_dashboard.py::test_dashboard_scope_accepts_explicit_eventloom_and_session -q
Expected: import failure for zaxy.dashboard.
- [ ] Step 3: Implement minimal scope models
Create DashboardConfig, DashboardScope, and resolve_dashboard_scope(). The implementation resolves paths, defaults to .eventloom, defaults host to 127.0.0.1, port to 8765, and marks the scope read-only.
- [ ] Step 4: Run tests to verify GREEN
Run: pytest tests/test_dashboard.py::test_dashboard_scope_defaults_to_current_workspace tests/test_dashboard.py::test_dashboard_scope_accepts_explicit_eventloom_and_session -q
Expected: both tests pass.
Task 2: Read-Only Eventloom API
Files:
- Modify:
src/zaxy/dashboard.py - Modify:
tests/test_dashboard.py
- [ ] Step 1: Write failing tests
Add tests for DashboardApp.handle_api():
from zaxy.dashboard import DashboardApp, DashboardConfig, resolve_dashboard_scope
from zaxy.event import EventLog
def test_dashboard_status_and_events_use_resolved_eventloom(tmp_path: Path) -> None:
workspace = tmp_path / "project"
workspace.mkdir()
eventloom = workspace / ".eventloom"
log = EventLog(eventloom / "default.jsonl")
event = log.append("decision.recorded", actor="tester", payload={"decision": "Build dashboard."})
app = DashboardApp(resolve_dashboard_scope(DashboardConfig(workspace=workspace)))
status_code, headers, body = app.handle_api("GET", "/api/status", "")
assert status_code == 200
assert headers["content-type"] == "application/json; charset=utf-8"
assert body["scope"]["workspace"] == str(workspace.resolve())
assert body["memory"]["total_events"] == 1
assert body["memory"]["sessions"][0]["latest_hash"] == event.hash
status_code, _headers, body = app.handle_api("GET", "/api/events", "session_id=default&limit=5")
assert status_code == 200
assert body["events"][0]["type"] == "decision.recorded"
assert body["events"][0]["summary"] == "Build dashboard."
def test_dashboard_rejects_non_get_api_methods(tmp_path: Path) -> None:
workspace = tmp_path / "project"
workspace.mkdir()
app = DashboardApp(resolve_dashboard_scope(DashboardConfig(workspace=workspace)))
status_code, _headers, body = app.handle_api("POST", "/api/events", "")
assert status_code == 405
assert body["error"] == "read_only"
- [ ] Step 2: Run tests to verify RED
Run: pytest tests/test_dashboard.py::test_dashboard_status_and_events_use_resolved_eventloom tests/test_dashboard.py::test_dashboard_rejects_non_get_api_methods -q
Expected: DashboardApp or handle_api missing.
- [ ] Step 3: Implement status/events endpoints
Add DashboardApp.handle_api() with:
GET /api/status: scope metadata andinspect_memory_status()GET /api/sessions: session list from statusGET /api/events: recent entries frominspect_memory_log(), supportingsession_idand boundedlimit- non-GET rejection with
405 - unknown route
404
- [ ] Step 4: Run tests to verify GREEN
Run: pytest tests/test_dashboard.py::test_dashboard_status_and_events_use_resolved_eventloom tests/test_dashboard.py::test_dashboard_rejects_non_get_api_methods -q
Expected: tests pass.
Task 3: Graph API Contract
Files:
- Modify:
src/zaxy/dashboard.py - Modify:
tests/test_dashboard.py
- [ ] Step 1: Write failing tests
Add a fake read-only graph provider and tests for scoped, bounded graph endpoints:
class FakeGraphProvider:
def __init__(self) -> None:
self.calls: list[tuple[str, dict[str, object]]] = []
def summary(self, *, session_id: str | None) -> dict[str, object]:
self.calls.append(("summary", {"session_id": session_id}))
return {"nodes": 2, "edges": 1}
def neighborhood(
self,
*,
session_id: str | None,
node_id: str,
view: str,
hops: int,
limit: int,
) -> dict[str, object]:
self.calls.append(
(
"neighborhood",
{
"session_id": session_id,
"node_id": node_id,
"view": view,
"hops": hops,
"limit": limit,
},
)
)
return {"nodes": [{"id": node_id}], "edges": [], "omitted_nodes": 0, "omitted_edges": 0}
def test_dashboard_graph_summary_uses_session_scope(tmp_path: Path) -> None:
workspace = tmp_path / "project"
workspace.mkdir()
graph = FakeGraphProvider()
app = DashboardApp(
resolve_dashboard_scope(DashboardConfig(workspace=workspace, session_id="agent-1")),
graph_provider=graph,
)
status_code, _headers, body = app.handle_api("GET", "/api/graph/summary", "")
assert status_code == 200
assert body["graph"] == {"nodes": 2, "edges": 1}
assert graph.calls == [("summary", {"session_id": "agent-1"})]
def test_dashboard_graph_neighborhood_enforces_bounds(tmp_path: Path) -> None:
workspace = tmp_path / "project"
workspace.mkdir()
graph = FakeGraphProvider()
app = DashboardApp(resolve_dashboard_scope(DashboardConfig(workspace=workspace)), graph_provider=graph)
status_code, _headers, body = app.handle_api(
"GET",
"/api/graph/neighborhood",
"node_id=n1&view=temporal&hops=99&limit=5000",
)
assert status_code == 200
assert body["graph"]["nodes"] == [{"id": "n1"}]
assert graph.calls[0][1]["hops"] == 2
assert graph.calls[0][1]["limit"] == 250
- [ ] Step 2: Run tests to verify RED
Run: pytest tests/test_dashboard.py::test_dashboard_graph_summary_uses_session_scope tests/test_dashboard.py::test_dashboard_graph_neighborhood_enforces_bounds -q
Expected: graph endpoints return 404.
- [ ] Step 3: Implement graph provider protocol and routes
Add:
DashboardGraphProviderprotocol withsummary()andneighborhood()UnavailableGraphProviderreturning degraded graph stateGET /api/graph/summaryGET /api/graph/neighborhood- bounds:
hopsclamped to1..2,limitclamped to1..250
- [ ] Step 4: Run tests to verify GREEN
Run: pytest tests/test_dashboard.py::test_dashboard_graph_summary_uses_session_scope tests/test_dashboard.py::test_dashboard_graph_neighborhood_enforces_bounds -q
Expected: tests pass.
Task 4: Static Dashboard UI
Files:
- Modify:
src/zaxy/dashboard.py - Modify:
tests/test_dashboard.py
- [ ] Step 1: Write failing tests
Add tests for the HTML shell:
def test_dashboard_index_html_references_core_tabs_and_api() -> None:
html = render_dashboard_html()
assert "Runtime Dashboard" in html
assert "Overview" in html
assert "Sessions" in html
assert "Graph" in html
assert "Checkout" in html
assert "Events" in html
assert "/api/status" in html
assert "/api/graph/summary" in html
assert "cytoscape" in html.lower()
- [ ] Step 2: Run test to verify RED
Run: pytest tests/test_dashboard.py::test_dashboard_index_html_references_core_tabs_and_api -q
Expected: render_dashboard_html missing.
- [ ] Step 3: Implement static HTML renderer
Add render_dashboard_html() returning a self-contained operational UI shell. Include a graph canvas placeholder, read-only badge, scope header, tab buttons, and JavaScript that fetches /api/status, /api/events, and /api/graph/summary.
- [ ] Step 4: Run test to verify GREEN
Run: pytest tests/test_dashboard.py::test_dashboard_index_html_references_core_tabs_and_api -q
Expected: test passes.
Task 5: HTTP Server And CLI Command
Files:
- Modify:
src/zaxy/dashboard.py - Modify:
src/zaxy/__main__.py - Modify:
tests/test_dashboard.py - Modify:
tests/test_cli.py
- [ ] Step 1: Write failing tests
Add tests that create_dashboard_handler() serves HTML and JSON without starting a real long-running server, and that the CLI exposes the command:
from typer.testing import CliRunner
from zaxy.__main__ import app as cli_app
from zaxy.dashboard import create_dashboard_handler
def test_dashboard_handler_serves_index_and_api(tmp_path: Path) -> None:
workspace = tmp_path / "project"
workspace.mkdir()
handler_cls = create_dashboard_handler(
DashboardApp(resolve_dashboard_scope(DashboardConfig(workspace=workspace)))
)
assert handler_cls.dashboard_app.scope.workspace == workspace.resolve()
def test_dashboard_cli_help_exposes_localhost_default() -> None:
result = CliRunner().invoke(cli_app, ["dashboard", "--help"])
assert result.exit_code == 0
assert "127.0.0.1" in result.output
assert "8765" in result.output
- [ ] Step 2: Run tests to verify RED
Run: pytest tests/test_dashboard.py::test_dashboard_handler_serves_index_and_api tests/test_cli.py::test_dashboard_cli_help_exposes_localhost_default -q
Expected: handler or CLI command missing.
- [ ] Step 3: Implement handler and CLI wiring
Add:
create_dashboard_handler(app: DashboardApp) -> type[BaseHTTPRequestHandler]run_dashboard(scope: DashboardScope) -> None@app.command("dashboard")insrc/zaxy/__main__.py
The command accepts --workspace, --eventloom-path, --session-id, --domain, --host, and --port, then prints Zaxy dashboard listening on http://host:port before serving.
- [ ] Step 4: Run focused tests to verify GREEN
Run: pytest tests/test_dashboard.py tests/test_cli.py::test_dashboard_cli_help_exposes_localhost_default -q
Expected: tests pass.
Task 6: Quality Gate
Files:
- Review all changed files
- [ ] Step 1: Run focused dashboard tests
Run: pytest tests/test_dashboard.py tests/test_cli.py::test_dashboard_cli_help_exposes_localhost_default -q
Expected: all selected tests pass.
- [ ] Step 2: Run lint
Run: ruff check src/zaxy/dashboard.py tests/test_dashboard.py src/zaxy/__main__.py tests/test_cli.py
Expected: no lint failures.
- [ ] Step 3: Run formatting check
Run: ruff format --check src/zaxy/dashboard.py tests/test_dashboard.py src/zaxy/__main__.py tests/test_cli.py
Expected: no formatting changes needed.
- [ ] Step 4: Run existing CLI and viewer regression tests
Run: pytest tests/test_viewer.py tests/test_memory_status.py tests/test_cli.py::test_viewer_command_writes_static_html -q
Expected: tests pass.
- [ ] Step 5: Commit implementation
git add src/zaxy/dashboard.py src/zaxy/__main__.py tests/test_dashboard.py tests/test_cli.py docs/superpowers/plans/2026-05-16-runtime-dashboard.md
git commit -m "feat: add read-only runtime dashboard"