Skip to content

feat(dashboard): a live, read-only dashboard: scio dashboard (#68) - #70

Merged
NewMe505 merged 11 commits into
mainfrom
feat/68-dashboard
Oct 28, 2026
Merged

NewMe505 merged 11 commits into
mainfrom
feat/68-dashboard

Conversation

@NewMe505

@NewMe505 NewMe505 commented Oct 28, 2026 •

Copy link
Copy Markdown
Owner

Summary

scio dashboard [--root DIR ...] [--port 8765] is a live web view of SCIO's own state machine across workspaces, on 127.0.0.1 only. It shows:

  • every thread's current step, lit on its graph;
  • the stop waiting for you, and crashed threads;
  • feature runs (tier, status, iterations, escalations, cost, links), including the attempt running now;
  • the timeline, and cost by role and by feature.

It never writes. It reads the checkpoint DB through mode=ro connections, plus the cost ledgers and progress.json, and reuses scio report's builders, so its costs equal scio report's. The page is plain HTML/JS with no build step, refreshed over a WebSocket. It ships as the optional dashboard extra (FastAPI, uvicorn). docs/OBSERVABILITY.md puts it beside the other two views: GitHub (--gh) and an OpenTelemetry trace viewer.

Closes #68

The build view of the live graphcalc run

The real build-graphcalc-m1 while it runs: feature is lit, P1 to P3 passed, P4's first attempt failed at advisor_cap, and the second attempt is running at $1.93 (+ $1.9269 running), with the human's retry in the timeline.

Plan vs. done

Plan step Status Notes
Wave 0: the contract, fixtures, stub, extra done scio/dashboard/contract.py. Two probes came first (a mode=ro connection under SqliteSaver on a WAL DB; a Fake build's channels and pending writes). Reviewing the contract against them caught I-120 before fan-out.
Wave 1, A: data layer done CheckpointDataSource: mode=ro, setup() skipped, a connection per call; channel classification; stops, crashes, Send packets; feature links; tolerant ledger reads. 19 tests on Fake-build checkpoints, including a byte-identical DB after every read.
Wave 1, B: API and WebSocket done create_app: GET routes only (a test asserts it), /ws polling versions, Host check, same-origin WebSocket, no framing. 54 tests.
Wave 1, C: page done Threads list plus build, feature and bootstrap views; Mermaid 11.15.0 with SRI and a text fallback; CSP; DOM built with textContent only. 13 Playwright tests, offline.
Wave 1, D: ADR 0017, OBSERVABILITY.md done The OTel recipe for Phoenix (6006) and Jaeger (4318); the docs' gaps are I-122 and I-123.
Wave 2: scio dashboard, live check, shared docs done The CLI (loopback only, bounded shutdown); the real app serving the real page under uvicorn as a test; README, CHANGELOG, PRODUCT_MODE, ARCHITECTURE's tree.
Wave 3: review agent, full check done A fresh-context review found six issues (I-133 to I-138), each confirmed by a probe, and all fixed or documented. It found no breach of the read-only guarantee.

Live check (graphcalc-run, while its build runs)

  • Costs equal scio report on a backup-API snapshot of the live DB: $9.0983 total, features $8.9244, orchestrator $0.1739, and every feature row and role identical.
  • Live update from the real run: P4's second attempt wrote a checkpoint during the watch. /ws pushed changed: [build-graphcalc-m1-p4-a2], and the page fetched the list and the open build again, with no reload and no page errors.
  • The run is unaffected: its checkpoint count kept rising while the dashboard read it.
  • Read-only: test_the_data_layer_never_writes hashes the main DB before and after every public method, and a write through the dashboard's connection raises. The ADR notes that a mode=ro reader on a DB with no open writer leaves empty -wal/-shm sidecars, which SQLite's next writer cleans up.
  • The dashboard caught a real stop: while graphcalc waited at "Feature P4 (advisor_cap, attempt 1) needs a decision", the build and P4 both read waiting, though the build's last checkpoint was 26 minutes older than the stop (I-120 in practice).

Test evidence

set -o pipefail; SCIO_PROVIDER_OVERRIDE=fake uv run ruff check . && uv run ruff format --check . && uv run mypy && uv run python -m pytest -n 8 && uv run python scripts/check_docs.py && uv run python -m evals.runner --suite regression --provider fake --trials 5 --min-pass-pow-k 1.0
ruff: all checks passed · format: 266 files formatted · mypy: no issues in 130 source files
482 passed, 18 skipped in 789 s · docs-check: 0 problem(s) · regression pass@1 1.00, pass^5 1.00 (10 tasks)   # on 79dacc2

CI on 79dacc2: checks 495 passed, 5 skipped (12m53s; the dashboard and Playwright tests ran); docker-live pass (1m1s). 273246d adds only the CHANGELOG ### Fixed line for I-127.
The new tests:

  • tests/unit/test_dashboard_data.py: 19 tests on Fake-build checkpoints plus tiny graphs for a stop, its answer, a crash, Send and unreadable rows.
  • tests/unit/test_dashboard_app.py: 54 tests.
  • tests/unit/test_dashboard_contract.py: the fixtures, and the one-interrupt-per-node scan.
  • tests/unit/test_dashboard_cli.py: loopback only, bounded stop, the install hint.
  • tests/live/test_dashboard_page.py: 13 Playwright tests.
  • tests/live/test_dashboard_server.py: the real app and page under uvicorn, with a clean stop.
  • tests/unit/test_lf_writes.py.
  • The torn-line test in test_m5_observability.py.

Docs updated

  • README (status line, usage with the screenshot, docs list, ADR list, roadmap)
  • docs/ARCHITECTURE.md (the package in the tree)
  • ADR 0017 (read-only and local only; extra; plain JS; reuse; polling; statuses; the attempt in progress; live files; out of scope: answering stops, Postgres)
  • CHANGELOG [Unreleased]
  • Docstrings for changed public APIs
  • docs/OBSERVABILITY.md (new), docs/PRODUCT_MODE.md

Continuous improvement

  • Issue ledger updated for every problem hit (ids: I-120 to I-139; I-121 wontfix, a refuted prediction; I-122 and I-123 open, pre-existing telemetry gaps; I-133 wontfix, documented)
  • Each bug fix was swept across the codebase. Patterns searched:
    • checkpoint_id\|get_state( (I-120)
    • uv sync --extra (I-124)
    • write_text(\|open( (I-126)
    • load_entries(\|read_text(encoding="utf-8") (I-127)
    • websocket_connect( (I-128)
    • wait_for_function (I-129)
    • uvicorn\.\|serve_forever\|HTTPServer( (I-132)
    • __interrupt__ (I-134)
    • branch:to: (I-136)
    • the toolchain boundary scan (I-139)
  • Retrospective and bottleneck audit: docs/LESSONS_LEARNED.md, "The dashboard". Guardrails 20 to 34 each name a guard; new scans: test_lf_writes.py, test_every_node_interrupts_at_most_once.

Shared code fixed along the way:

  • CostLedger.load_entries keeps only complete lines, so scio report no longer dies on a live run's half-written ledger line (I-127; the test fails on the old code).
  • _read_artifact replaces a broken character instead of raising.

Risk notes

  • New optional dependencies: FastAPI 0.162 and uvicorn 0.54, in the dashboard extra only; uv.lock updated. The core install is unchanged, and scio dashboard without the extra says how to install it (--all-extras, never a bare --extra, I-124).
  • The diagram needs cdnjs (pinned, SRI); without it the diagram falls back to text.
  • Statuses read from the DB have two documented limits:
    • a resumed crashed thread reads crashed until the resumed node returns (I-133);
    • a process killed without an exception reads in_progress, and only "last update" shows it.
  • Rollback: the feature is additive. Revert the PR, or simply don't run scio dashboard.

Bottleneck audit

  • Slowest steps:
    • the page agent: 35 minutes, mostly Playwright waits;
    • diagnosing I-132: about 15 minutes, a shutdown hang that appeared only in the full parallel run;
    • the review: about 20 minutes, and worth it.
  • Could have run in parallel: the snapshot live check could have started as soon as the API and page were committed.
  • Improvement: the wave gate is now the full parallel suite, because I-132 and I-139 each showed only there. Reusable: tests/live/test_dashboard_server.py::_Server runs uvicorn as the CLI does.

Agent run log

Not a SCIO agent PR: built by Claude Code sessions (an orchestrator, four wave agents and a review agent).

🤖 Generated with Claude Code

NewMe505 and others added 11 commits October 27, 2026 20:26
scio/dashboard/contract.py fixes what every layer speaks before any layer is
built: the response models (Workspace, ThreadSummary, StopInfo, ThreadDetail,
GraphDiagram, ThreadVersion, ChangeEvent) and the DataSource protocol. The
fixtures in tests/fixtures/dashboard/ come from a real Fake build's checkpoints
and validate against the models; tests/dashboard_support.py serves them as a
DataSource for the API and page tests. The `dashboard` extra adds FastAPI and
uvicorn; the core install is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lds (wave 0, #68)

A stop arrives as a pending write on a checkpoint that already exists, so a
version built from the checkpoint id alone never showed a thread starting to
wait: ThreadVersion counts the thread's writes too. A crash leaves a pending
__error__ write (seen read-only on the live graphcalc DB), now status
`crashed` with the error text. A build is recognised by `product_dir` as well
as `feature_runs`, so one that stopped before its first feature still counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the contract (wave 0, #68)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…68)

- scio/dashboard/data.py: CheckpointDataSource reads each root's checkpoint
  DB read-only (mode=ro, SqliteSaver.setup skipped, a connection per call),
  classifies threads by their channels, reuses the scio report builders, and
  reads stops, crashes, progress.json and feature-thread links.
- scio/dashboard/app.py: create_app serves the contract's GET routes and a
  /ws WebSocket that polls versions and pushes changes; Host and Origin are
  checked so only the loopback page can read it; a test asserts every route
  is a read.
- scio dashboard [--root DIR ...] [--port 8765] binds 127.0.0.1 only.
- CostLedger.load_entries keeps only complete lines, so scio report no longer
  dies on a live run's half-written last line (I-127); transcripts are read
  with errors="replace".
- An AST scan guards the LF rule for every text write (I-126).
- ADR 0017, docs/OBSERVABILITY.md, README, CHANGELOG, PRODUCT_MODE; ledger
  I-120 to I-128, guardrails 20 to 25.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… views (wave 1, #68)

Plain HTML/CSS/JS with no build step. The threads list is grouped by root and
product, with waiting, crashed and error rows. The build view lights the
current step on the milestone graph and shows feature runs, integration lines,
the PR, the timeline and SVG cost bars; the feature and bootstrap views follow
the same shape. Live refresh over /ws with reconnect and backoff. Mermaid
11.15.0 from cdnjs with SRI, falling back to text; a CSP allows only self and
cdnjs scripts. DOM is built with textContent only. Playwright tests run
against a fixture server with a stub Mermaid, offline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, cost, running_usd (wave 2, #68)

SCIO appends a FeatureRun only when an attempt ends, so on the live graphcalc build the feature running now read "not run" and its spend was missing from the build's costs. The contract now names the build's feature threads without a run as running, counts their spend in cost_by_role and cost_by_feature, and carries it separately as running_usd; summary.usd stays the figure scio report prints.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… stop (wave 2, #68)

- A build's feature threads with no FeatureRun yet are linked (running, or
  their own waiting/crashed/halted status) and their spend counts in the cost
  by role and by feature and as running_usd; the headline stays scio report's
  figure. Found on the live graphcalc build, whose P4 waits at advisor_cap.
- scio dashboard bounds uvicorn's graceful shutdown: on Windows a reset
  connection kept asyncio.Server.wait_closed open forever, through a second
  Ctrl+C too (I-132). The real app serving the real page under uvicorn is now
  a test, which also stops it with a tab open and one closed (I-130).
- The fixture source is thread-safe; ledger I-129 to I-132, guardrails 26 to
  28, the dashboard retrospective.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t in the README (#68)

The screenshot is the real graphcalc build seen through scio dashboard: P4's second attempt running, its spend in the cost bars, the human's retry in the timeline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ve spend, origins (#68)

A fresh-context review of the whole diff, each finding confirmed by a probe:
- an answered stop read "waiting" until its node returned; a __resume__
  write beside the __interrupt__ now means answered (I-134), exact because no
  SCIO node interrupts twice, which a scan test keeps true
- a resumed crash reads "crashed" until the resumed node returns; the DB has
  no signal, so the contract, ADR and docs say so (I-133)
- one unreadable root made /api/threads a 500 for every root; it is skipped
  and logged, and answers 503 on its own (I-135)
- next_nodes include pending Send packets, so T2's explorers light (I-136)
- a running attempt's spend comes from its ledger, not its last checkpoint
  (I-137)
- /ws accepts only the server's own origin; every response denies framing
  (I-138)
Ledger I-133 to I-138, guardrails 29 to 33, the retrospective.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ral (I-139, #68)

The M6 toolchain boundary scan keeps stack command words inside scio/toolchain/; the full suite caught the literal the wave gate's dashboard-only run did not. Ledger I-139, guardrail 34.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…68)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@NewMe505
NewMe505 marked this pull request as ready for review October 28, 2026 06:04
@NewMe505
NewMe505 merged commit 7e55023 into main Oct 28, 2026
2 checks passed
NewMe505 added a commit that referenced this pull request Oct 30, 2026
Conflicts in the shared files, resolved by keeping both sides: the
CHANGELOG's Fixed entries, the README status line (the dashboard is
merged now), the ledger (I-114 to I-118, then I-120 to I-139), the
guardrails (rule 17 merges I-117 and I-131; 20 to 34 are the
dashboard's, 35 to 38 this branch's), and two appended build tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@NewMe505 NewMe505 mentioned this pull request Oct 30, 2026
2 tasks done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: a live read-only dashboard (FastAPI, WebSocket, plain HTML/JS)

1 participant