* Quality overhaul: code tooling, CI/CD, architecture diagrams, UI redesign, and legacy cleanup - Add ruff (lint+format) and mypy (strict) with zero errors across 37 source files - Add GitHub Actions CI (lint, typecheck, test matrix 3.11/3.12/3.13) and PyPI publish workflow - Create 12 PlantUML architecture diagrams with PNG renders covering all subsystems - Refresh README and docs with badges, diagram links, and current descriptions - Refactor test_server_live.py with mock streaming helpers for deterministic CI testing - Update dependencies to current versions (openai>=2.24, httpx>=0.28, redis>=7.2) Console dashboard: - Move state indicators from top cards to fixed bottom status bar with cluster metrics - Replace flat 50-node list with hostname-prefix grouped nodes (expand/collapse, up to 1000) - Apply "Instrument Panel" visual redesign: IBM Plex Mono + Outfit fonts, warm amber accent, LED glow state indicators, deep charcoal surfaces, WCAG AA contrast compliance - Add render cache, stale indicator, active filter highlight, loading states Server web UI: - Apply matching Instrument Panel aesthetic for visual consistency with console - Fix branding (pcode → turnstone), extract inline styles to CSS classes - Rename pcode localStorage keys and history state to turnstone Legacy cleanup: - Remove persona-model-specific --persona flag and /persona slash command - Remove model_identity from chat_template_kwargs (vLLM-specific mechanism) - Refactor plan agent to use standard developer message instead of model_identity - Remove dead code (unused date/has_tools variables, noqa suppressions) * Fix CI typecheck: add mypy overrides for optional sympy/numpy imports The math sandbox optionally imports sympy and numpy at runtime (try/except ImportError). In CI these packages are not installed, so mypy raises import-not-found rather than import-untyped. Add mypy overrides to ignore missing imports for these optional dependencies. * Fix Copilot review findings: ARIA role, status bar cache, and pulse opacity - Change #node-table from role="tree" to role="list" and group elements from role="treeitem" to role="listitem" (proper ARIA semantics) - Include currentView and currentFilter.state in renderStatusBar cache key so active pill highlight updates when switching views - Align pulse animation to 0.35 opacity (already applied in CSS)
8.5 KiB
Cluster Dashboard (turnstone-console)
turnstone-console is a standalone monitoring service that provides cluster-wide visibility across all turnstone nodes. It connects to the shared Redis broker, discovers nodes via heartbeat keys, polls each node's HTTP API for workstream data, and subscribes to a cluster event channel for real-time state changes.
The console is read-only — it observes but does not own workstreams or drive LLM sessions.
Architecture
See also: Console Data Flow diagram
turnstone-server ──→ turnstone-bridge ──→ Redis ──→ turnstone-console ──→ Browser
(per node) (per node) (shared) (one instance)
Each bridge publishes state changes to {prefix}:events:cluster on Redis pub/sub. The console subscribes once to that channel for real-time updates and periodically polls each node's GET /api/dashboard for full workstream snapshots.
Data Sources
| Source | Method | Frequency | Data |
|---|---|---|---|
| Redis heartbeats | SCAN turnstone:node:* |
Every 15s | Node discovery (node_id, server_url, started) |
| Redis pub/sub | SUBSCRIBE turnstone:events:cluster |
Real-time | State changes, creates, closes, renames |
| Node HTTP API | GET {server_url}/api/dashboard |
Every 10s | Full workstream list with tokens, context, activity |
| Node HTTP API | GET {server_url}/health |
Every 10s | Node health status |
Redis Key: Cluster Event Channel
Bridges publish to {prefix}:events:cluster whenever a workstream state change, creation, closure, or rename occurs. Events include node_id so the console can attribute them to the correct node.
Event types on the cluster channel:
| Event | Fields | Trigger |
|---|---|---|
cluster_state |
ws_id, state, node_id, tokens, context_ratio, activity | Workstream state transition |
ws_created |
ws_id, name, node_id | New workstream created |
ws_closed |
ws_id | Workstream closed |
ws_rename |
ws_id, name | Workstream renamed |
ClusterCollector
The collector (turnstone/console/collector.py) maintains an in-memory snapshot of all nodes and workstreams. Three daemon threads handle data acquisition:
-
Event subscriber — subscribes to
{prefix}:events:clusterviaRedisBroker.subscribe_cluster(). Applies state changes, creates, closes, and renames to the in-memory model immediately. -
Node discovery — scans heartbeat keys every 15 seconds via
broker.list_nodes(). Adds newly discovered nodes, removes expired ones, emitsnode_joined/node_lostevents to SSE listeners. -
Poll loop — fetches
GET /api/dashboardandGET /healthfrom each known node every 10 seconds. UsesThreadPoolExecutor(max_workers=50)for parallelism. Each poll replaces the node's workstream list with the authoritative server data.
Thread Safety
All reads and writes to the node/workstream map are protected by a single threading.Lock. Query methods acquire the lock, copy data, and release before returning.
Scale Considerations
- 10,000 workstreams at ~500 bytes each = ~5 MB in memory
- 1,000 nodes polled in parallel with 50 threads at ~100ms each = ~2 second poll cycle
- Filtering and pagination run in-memory on the full workstream list — sub-millisecond at this scale
- SSE fan-out uses the same per-client queue pattern as the per-node server — backed-up clients get events dropped, not blocking
HTTP API
GET /api/cluster/overview
Cluster-wide state counts and aggregate metrics.
{
"nodes": 847,
"workstreams": 4219,
"states": {"running": 1847, "thinking": 312, "attention": 89, "idle": 1940, "error": 31},
"aggregate": {"total_tokens": 12400000, "total_tool_calls": 34200}
}
GET /api/cluster/nodes?sort=activity&limit=100&offset=0
Paginated node list. Sort options: activity (default, by running+attention count), tokens, name.
{
"nodes": [
{
"node_id": "db-west-04",
"server_url": "http://10.0.3.4:8080",
"ws_total": 6, "ws_running": 4, "ws_thinking": 0, "ws_attention": 1, "ws_idle": 1, "ws_error": 0,
"total_tokens": 48200,
"started": 1709294400.0,
"reachable": true,
"health": {}
}
],
"total": 847
}
GET /api/cluster/workstreams?state=running&node=db-west-04&search=perf&page=1&per_page=50
Filtered, paginated workstream list. All query parameters are optional. per_page is capped at 200.
{
"workstreams": [
{
"id": "a1b2c3d4", "name": "perf-db-west", "state": "running", "node": "db-west-04",
"title": "Query latency analysis", "tokens": 24100, "context_ratio": 0.18,
"activity": "bash: EXPLAIN ANALYZE...", "activity_state": "tool", "tool_calls": 42
}
],
"total": 1847, "page": 1, "per_page": 50, "pages": 37
}
GET /api/cluster/node/{node_id}
Single node detail with all its workstreams.
{
"node_id": "db-west-04",
"server_url": "http://10.0.3.4:8080",
"health": {"status": "ok", "version": "0.2.0", "model": "kappa_20b_131k"},
"workstreams": [...],
"aggregate": {"total_tokens": 48200, "total_tool_calls": 156}
}
GET /api/cluster/events
Server-Sent Events stream for real-time cluster updates.
data: {"type":"cluster_state","ws_id":"a1b2","node_id":"db-west-04","state":"running"}
data: {"type":"ws_created","ws_id":"e5f6","node_id":"api-east-01","name":"new-task"}
data: {"type":"ws_closed","ws_id":"a1b2"}
data: {"type":"node_joined","node_id":"db-west-05"}
data: {"type":"node_lost","node_id":"db-west-03"}
Keepalive comments (: keepalive\n\n) are sent every 5 seconds. Clients should reconnect on error with exponential backoff.
GET /health
{"status": "ok", "service": "turnstone-console", "nodes": 847, "workstreams": 4219}
Browser Dashboard
The web UI has three views, toggled client-side:
1. Cluster Overview (landing)
- State cards — 5 clickable cards (running, thinking, attention, idle, error) with count and colored top border. Clicking filters to that state.
- Aggregate bar — total tokens and tool calls across the cluster.
- Node table — columns: NODE, WS, RUN, ATTN, TOKENS, HEALTH. Sorted by activity. Clickable rows drill down to node detail.
2. Node Drill-down
Breadcrumb: Cluster > db-west-04. Shows the node's workstreams in a table matching the per-node dashboard layout (STATE, NAME, NODE, TASK, TOKENS, CTX) with activity sub-lines. Includes a link to the node's own dashboard (http://{server_url}/).
3. Filtered Workstreams
Breadcrumb: Cluster > Running or Cluster > db-west-04. Server-side paginated workstream table. NODE column values are clickable to filter further. Pagination controls at bottom.
All three views receive live updates via SSE — state cards update counts, node rows update metrics, workstream rows update state indicators.
CLI Commands
The /cluster command in the turnstone CLI queries the console's HTTP API. Requires --console-url or [console] url in config.toml.
| Command | Description |
|---|---|
/cluster status |
Cluster overview — node/workstream counts, state breakdown, aggregate stats |
/cluster nodes |
Node table — WS, RUN, ATTN, TOKENS per node |
/cluster workstreams [state] [node=X] |
Filtered workstream list with state, name, node, tokens, context |
/cluster node <id> |
Single node's workstreams with activity details |
Configuration
CLI flags for turnstone-console:
| Flag | Default | Description |
|---|---|---|
--host |
0.0.0.0 |
Bind host |
--port |
8090 |
HTTP port |
--redis-host |
localhost |
Redis host |
--redis-port |
6379 |
Redis port |
--redis-password |
$REDIS_PASSWORD |
Redis password |
--redis-db |
0 |
Redis DB |
--poll-interval |
10 |
Node polling interval (seconds) |
--log-level |
INFO |
Log level |
Config file (~/.config/turnstone/config.toml):
[console]
host = "0.0.0.0"
port = 8090
url = "http://localhost:8090" # used by CLI /cluster commands
poll_interval = 10
[redis]
host = "localhost"
port = 6379
password = "my-redis-password"
Deployment
# Start Redis
redis-server
# Start turnstone servers (one per node)
turnstone-server --port 8080
# Start bridges (one per server)
turnstone-bridge --server-url http://localhost:8080 --node-id node-a
# Start cluster console (one instance)
turnstone-console --redis-host localhost --port 8090
Open http://localhost:8090 for the cluster dashboard.