Files
the-custodian/state-hub
tegwick efd13b13dd Implement Workstream Health Index (WHI) KPI card
Add a live WHI card to the Workstreams page TOC sidebar. All six base
metrics from the spec (workstream-kpi.md) computed client-side from
existing data — no API or schema changes required.

Computation (workstreams.md):
- DD: dependency edges / open workstreams (normalised at DD_crit=1.0)
- BR: blocked workstreams / open workstreams
- SPR: max inbound deps on one incomplete workstream / open count
- PEP: active workstreams with all deps completed / open count
- CDDR: cross-domain edges / total edges
- CPI: DFS cycle detection (back-edge = 1, halves WHI as hard penalty)
- WHI = 0.30(1-DDnorm) + 0.25(1-BR) + 0.15(1-SPR) + 0.20·PEP + 0.10(1-CDDR)
- Per-domain breakdown using intra-domain edges only

Card UI: global WHI % with green/orange/red health label, sub-metric
rows with per-spec warning thresholds, cycle alert panel, per-domain
breakdown rows with coloured dots.

Also add src/docs/workstream-health-index.md reference page (formula,
thresholds, improvement guidance) and wire ? button on the card.
Add "Workstream Health" to Reference nav.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 00:03:27 +01:00
..

State Hub v0.1

The operational brain of the Custodian: a local PostgreSQL database, FastAPI REST service, FastMCP stdio server for Claude Code, Observable Framework dashboard, and a custodian CLI.


Stack

Layer Technology Port
Database PostgreSQL 16-alpine (Docker) 127.0.0.1:5432
API FastAPI + SQLAlchemy 2.0 async + asyncpg 127.0.0.1:8000
MCP server FastMCP stdio (Claude Code native) stdio
Dashboard Observable Framework 127.0.0.1:3000
CLI custodian (Python, uv entry point)

All services bind to 127.0.0.1 only — nothing exposed to the network.


Setup

Prerequisites

  • Docker Engine (WSL2: see CLAUDE.md in repo root → Docker Setup)
  • Python 3.12+ with uv (pip install uv)
  • Node.js 18+ (dashboard only)

First-time

cd state-hub

cp .env.example .env          # edit POSTGRES_PASSWORD
make install                  # uv sync
make db                       # docker compose up postgres
make migrate                  # alembic upgrade head (creates 5 tables)
make seed                     # insert 6 canonical topics
make api                      # uvicorn :8000 --reload

Shortcut

make start    # db + sleep + migrate + api

Dashboard

make dashboard    # Observable dev server on :3000

CLI

make install-cli              # symlink .venv/bin/custodian → ~/.local/bin
custodian status              # API health + summary totals
custodian register-project    # register cwd as a Custodian project

Makefile Targets

Target What it does
make install uv sync — install Python deps + entry points
make install-cli Symlink custodian to ~/.local/bin
make db Start postgres container
make db-tools Start postgres + pgadmin (http://127.0.0.1:5050)
make migrate alembic upgrade head
make seed Insert 6 canonical topics
make api uvicorn api.main:app --reload
make dashboard Observable dev server
make check curl /state/health
make start db + wait + migrate + api
make register-project DOMAIN=x PROJECT_PATH=y Register a project
make clean docker compose down -v (destroys DB volume)

Database Schema

Five tables in dependency order:

topics
└── workstreams
    └── tasks (self-FK: parent_task_id)
        └── progress_events
decisions (FK: topic_id, workstream_id — at least one required)
    └── progress_events

Enums

Enum Values
topic_status active · paused · archived
workstream_status active · blocked · completed · archived
task_status todo · in_progress · blocked · done · cancelled
task_priority low · medium · high · critical
decision_type made · pending
decision_status open · resolved · escalated · superseded
domain custodian · railiance · markitect · coulomb_social · personhood · foerster_capabilities

Governance constraints encoded in schema

  • No hard DELETE endpoints — only soft: archived, cancelled, superseded
  • progress_events has no updated_at and no DELETE endpoint (append-only per constitution §5)
  • decisions with financial/legal keywords + pending type → auto-set escalation_note (§4)

API

Interactive docs at http://127.0.0.1:8000/docs once the API is running.

Key endpoint: /state/summary

Returns a full snapshot in one call — used by both the MCP server and dashboard:

{
  "generated_at": "...",
  "totals": {
    "topics":      { "active": 6, "paused": 0, "archived": 0, "total": 6 },
    "workstreams": { "active": 1, "blocked": 0, "completed": 1, "total": 2 },
    "tasks":       { "todo": 9, "in_progress": 0, "blocked": 0, "done": 11, "total": 20 },
    "decisions":   { "open": 1, "resolved": 0, "escalated": 0, "total": 1 }
  },
  "topics": [...],             // topics with nested workstream stubs
  "blocking_decisions": [...], // pending decisions only
  "blocked_tasks": [...],
  "recent_progress": [...],    // last 20 events
  "open_workstreams": [...]
}

Router summary

Prefix Operations
/topics CRUD (soft-delete: archived)
/workstreams CRUD (soft-delete: archived)
/tasks CRUD (soft-delete: cancelled); PATCH updates status
/decisions CRUD (soft-delete: superseded); auto-escalation
/progress GET list + POST append — no DELETE
/state/summary Full snapshot
/state/health DB connectivity check

MCP Server

Registered in ~/.claude.json at user scope. Config in .mcp.json (repo root).

Uses absolute path + PYTHONPATH so cwd is not required:

{
  "command": "/home/worsch/the-custodian/state-hub/.venv/bin/python",
  "args": ["/home/worsch/the-custodian/state-hub/mcp_server/server.py"],
  "env": { "PYTHONPATH": "/home/worsch/the-custodian/state-hub", "API_BASE": "http://127.0.0.1:8000" }
}

See mcp_server/TOOLS.md for the full tool reference card (30 lines, faster than reading server.py).

Tools at a glance

Query (read-only): get_state_summary · get_topic · list_blocked_tasks · list_pending_decisions · get_recent_progress

Mutate (each auto-emits a progress event): create_task · update_task_status · record_decision · resolve_decision · add_progress_event · update_workstream_status

Resources: state://summary · state://topics · state://workstreams/{topic_slug} · state://decisions/blocking · state://tasks/blocked


custodian CLI

Installed into .venv/bin/custodian by uv sync; symlinked to ~/.local/bin by make install-cli.

custodian register-project [--domain DOMAIN] [--path PATH]
  • --path defaults to current working directory
  • --domain is auto-detected from project_charter_v*.md frontmatter if omitted
custodian status

Prints API health, totals, and any blocking decisions.

What register-project does

  1. Verifies the API is reachable (fails fast with make api hint)
  2. Looks up the topic ID for the domain via /topics/?status=active
  3. Checks that state-hub is in ~/.claude.json
  4. Writes $PROJECT_PATH/CLAUDE.md from scripts/project_claude_md.template
  5. Posts a milestone progress event recording the registration

Project Registration Scripts

Script Purpose
scripts/register_project.sh Shell version of custodian register-project
scripts/patch_mcp_cwd.py Patches cwd into ~/.claude.json after claude mcp add-json drops it
scripts/project_claude_md.template CLAUDE.md template with {PROJECT_NAME}, {DOMAIN}, {TOPIC_ID}
scripts/seed.py Insert the 6 canonical topics into a fresh database
scripts/pull_image.py WSL2 workaround: pull Docker images via Python urllib with Range-request chunking

Dashboard

Four pages at http://127.0.0.1:3000 (dev) or built with npm run build:

Page Content
Overview Status cards, task-by-status chart, recent activity feed, decisions due within 7 days
Workstreams Filterable table by domain/status/owner; selected workstream task list; progress timeline
Decisions Pending tab (with escalation highlights) and Made tab; resolution velocity chart
Progress Append-only event feed with author badges; 30-day event volume chart

Data loaders (src/data/*.json.py) are Python scripts that call the local API. They run at dev-server start and on npm run build. Clear the cache if data appears stale:

rm -rf dashboard/src/.observablehq/cache/

Known Issues / WSL2 Notes

  • TLS bad record MAC on large downloads: WSL2 corrupts packets on big TCP transfers. Use scripts/pull_image.py instead of docker pull for future image pulls.
  • claude mcp add-json drops cwd: Known Claude Code bug. Run python3 scripts/patch_mcp_cwd.py after any re-registration. The current .mcp.json uses absolute path + PYTHONPATH so cwd is not strictly needed.
  • AsyncSession concurrency: SQLAlchemy 2.0 async sessions don't support concurrent operations. All queries in /state/summary run sequentially on a single session.