Interfaces & ops

Dashboard & admin screen

A static SPA-style front end over the .NET 10 minimal-API backend. Five pages share one nav and stylesheet — Dashboard, Setup Guide, Graph, Quality, and Admin. The dashboard is a public, always-live readout; the admin screen is a full operational control room whose every action is gated to loopback or the tailnet. This page documents every dashboard tile and every admin card.

The trust boundary

The admin page is public HTML, but every action it takes hits /api/admin/*, which is gated to loopback OR the Tailscale tailnet only — IsLoopback(ip) || IsTailnetAddress(ip) (Program.cs:3049-3064), matching IPv4 CGNAT 100.64.0.0/10 and IPv6 ULA fd7a:115c:a1e0::/48. Off-tailnet requests get HTTP 403 (:3083-3085), and admin.html swaps its whole panel for a gate card carrying an SSH-tunnel command. The public nginx vhost also blocks /api/admin/ outright. Dashboard, search, and /mcp stay public. Dashboard tiles are driven by script.js fetching /api/stats, /api/distribution, and /api/recent every 15s (script.js:11-19) plus /api/caller-recall best-effort.

Why it matters

Anyone can watch the brain's health, but only someone on the private network (or an SSH tunnel) can change a setting or trigger a run — a leaked admin URL is inert from the public internet.

Prometheus /metrics — the same gate, a different consumer

PR #196 (1e64463) exposes Prometheus-format metrics at GET /metrics via OpenTelemetry.Exporter.Prometheus.AspNetCore 1.16.0-beta.1, mapped after the same gate middleware as /api/* and /hook/* — loopback or tailnet, or a bearer token — and never added to Security:PublicPaths, so it is never public. Metrics:Enabled (default true) is the single kill switch: off skips both the exporter registration and the route mapping. Verified live after deploy: /metrics returns 401 from the public hostname and serves on loopback.

Instruments: process working set and GC heap size, an MCP tool-call counter plus a latency histogram, gate allow/deny counters, ingest running / last-duration gauges, Groq lifetime cost, overlay roots registered, and precise (Roslyn) workspaces loaded. Sample live values captured after deploy: ledger_overlay_roots_registered 55, ledger_groq_lifetime_cost_usd 23.11679605000003, process_working_set_bytes 4375715840, and — after one real call — ledger_tool_calls_total{tool="symbol_lookup"} 1.

A local Prometheus + Grafana stack is committed under observability/ (docker compose up -d) — both bound to 127.0.0.1 via network_mode: host, data persisted under /opt/arch-ledger-observability/, ports 9090 (Prometheus) and 3000 (Grafana), no nginx change. It is not deployed or proxied anywhere; it is a local operator convenience for graphing what /metrics already exposes, documented with a one-command start in observability/README.md.
Why it matters

The dashboard tiles are a snapshot fetched every 15s; a scrape endpoint on the standard Prometheus contract lets an operator graph trends — RSS sawtooth, gate denials/hour, query latency percentiles — over time, with a real-time-series store, using the exact same trust boundary the rest of the admin surface already relies on.

Dashboard tiles (index.html) — "Architectural Memory Hub"

Fifteen live tiles, each refreshing every 15s. Each carries its own concrete benefit:

TileWhat it showsWhy it matters
Total Memory ChunksHeadline = live chunks (current is_latest), subtext = distinct repos + total points incl. superseded — /api/stats liveChunks/totalChunks/distinctRepos (script.js:69-76; Program.cs:371).The live-vs-total split makes version bloat visible at a glance.
Distinct PatternsDistinct patterns with a "layers covered" subtext (script.js:77-78).Signals the brain indexes real architecture, not just files.
Last SyncRelative time of last ingest + files processed/scanned · chunks · duration, with error and budget-skip annotations (script.js:83-90,110-113). Freshness and health; surfaces budget-throttled runs.
MCP ActivityLifetime query_ledger count, sync count, last-activity relative time. Proves agents are actually using the brain.
Groq SpendLifetime $ + calls + last-run cost; a coloured budget pill (% of cap, green/amber/red) when a cap is set (script.js:92-109). LLM cost control at the front door.
Query Hit Rate24h hit-rate % (green ≥60/amber 30-60/red <30) + 24h & 7d counts + median latency (renderHitRate script.js:149-160). The core "is retrieval working" signal.
Enrichment CoverageAverage of seven coverage fractions (quality/git/concept/log/endpoint/ sproc/ledgerId) as a headline %, plus a per-dimension line (renderCoverage :162-172). Shows how enriched each chunk is; low git/log explains thin why_query/churn.
Ledger Token Saving−N% tokens (ledger vs direct file tools), green when saving, plus "cheaper on X/Y" (renderBenchmark :124-141) from bench_* A/B. Quantifies ROI in tokens — the headline justification.
Graph DensityDistinct concepts, routes, sprocs (renderGraphDensity :174-179). How much cross-stack structure graphify materialised.
Caller Recall% of source callers the graph resolves (green ≥70/amber ≥30/red <30) or n/a; detail names the worst repo (renderCallerRecall :42-60). Dedicated GET /api/caller-recall (Program.cs:582), off-thread. Honest self-audit — the graph is forward-only.
Learning CorpusAgent-query count + sessions ingested + log templates + refactor plans (renderCorpus :181-187). Tracks the compounding "has anyone tried this before" memory.
Hosted Session IngestState (off/pending/healthy/error) + last-run relative time, session count, next run, interval; error text on failure (renderHostedIngest :189-212). Confirms the periodic transcript-ingest service is alive.
Repository Coverage chartDoughnut of chunk count per repo (Chart.js) or empty-state (renderChart :221-254); GET /api/distribution dist.byRepo (Program.cs:569).Which repos dominate or are under-covered.
Recent Architectural IngestsList of the latest ingested files (repo tag, file, intent, relative time) (renderActivity :256-275); GET /api/recent?limit=8 (Program.cs:637).A live feed proving ingestion flows.
Query box + Trigger IngestionFree-text box posting to /api/ask (Program.cs:992) for a synthesised cited answer (routes to cluster_cohort/refactor_plan/find_similar/query_ledger), falling back to raw /api/search (:650); "Trigger Ingestion" → POST /api/ingest (409 = already running); "View code" → GET /api/graph/node-detail (:2886). A human uses the same brain the agents use, with verifiable citations.
Why it matters

The dashboard is a single always-on health-and-ROI panel: freshness, spend, retrieval quality, token savings, and the honest caveats (caller recall, enrichment coverage) all sit in one view that anyone can watch without credentials.

Quality & Setup pages

The Quality page (quality.html) is a per-file risk table ranked by Composite = Cyclomatic + Cognitive − Coverage×5, with a repo filter and sort (risk/churn/coverage/complexity); rows link to /graph2?focus=<ledgerId> and pull GET /api/quality?sort=&limit=100&repo= (Program.cs:2516). It is documented in full on the Analysis & quality page. The Setup Guide (setup.html) is a static onboarding/how-to-connect page whose only live behaviour is the shared /api/stats status ping.

The weekly digest

Eight things the ledger already knows about the week — corpus size, which repos ingested, the route inventory, which load-bearing files moved, where churn concentrates, how far the caller graph reaches, what is still outstanding, and where the team is weak — live behind eight separate JSON endpoints that nobody reads on a Monday. tools/weekly-digest.sh plus tools/weekly-digest.py assemble them into one dated page under wwwroot/docs/digests/, with latest.html as the pointer at the most recent one.

The design constraint is that a digest nobody can check is worse than no digest. Every figure on the page is read out of a response body captured at generation time, the endpoint is named under each heading, and a provenance table closes the page. There is no default, no estimate and no carry-forward from last week: when a fetch fails or a window is empty the section renders "No data from <endpoint> this week — <reason>", and the reason distinguishes a missing capture from an HTML error page from malformed JSON from a genuinely empty result. Two of the figures are deltas, which need a previous observation, so each run leaves a JSON sidecar beside the page and the next run diffs against it; the first run says it has nothing to compare with rather than presenting today's level as if it were a change.

The split between shell and Python is deliberate: the shell only GETs and writes bytes, so everything that could be wrong about which week it is, which files are load-bearing, or whether a section has data at all lives in weekly-digest.py under its own test suite — most of which assert that an absence renders visibly.

The digest is not yet on a schedule. No cron entry or timer runs it; the page currently published was generated by hand. The one committed so far covers the week of 2026-08-03 (window 2026-08-03T00:00:00Z → 2026-08-10T00:00:00Z), generated 2026-08-09T12:46:23Z, and all 8 of its sections returned data. Treat its figures as a snapshot of that moment, not as live numbers — it records 8 repositories indexed, because infra was added after it was generated.

The admin screen (admin.html)

On load, tryLoad() (:940-951) calls GET /api/admin/config (Program.cs:1357); a 403 renders the gate card with a personalised ssh -L 5006:localhost:<port> root@<host>, while success renders the panel and applyConfig (:1490-1568) hydrates the fields. Save posts a diff-patch to POST /api/admin/config (Program.cs:2332); Save & Restart also hits POST /api/admin/restart (:2426). Every card below carries its own benefit.

Observability & cost cards

CardWhat it doesWhy it matters
Gate card + SSH commandExplains loopback/tailnet-only access; prints the tailnet URL and an SSH-tunnel command auto-filled with host/port. An off-tailnet operator still gets a one-copy path in.
Live Status (log tail + slowest files)Idle/Running pill, live per-run detail (files, chunks, reused, Groq calls/$, elapsed, "now: <file>"), a level-coloured log tail, and a top-10 slowest-files table. GET /api/admin/ingest-status (Program.cs:2132) + /api/admin/logs?since= (:1363), polled 1s running / 4s idle. Real-time ingestion observability including per-file hotspots.
Groq (config/spend/budget/reset)API key (rotate, masked), model datalist, input/output pricing per 1M tokens, lifetime Spend Cap (0=unlimited), lifetime readout + Reset spend totals. GET /api/admin/spend (:2317), POST /api/admin/reset-spend (:2374).Full LLM cost governance.
Anthropic / Benchmark runnerAnthropic API key + model (default claude-opus-4-8), "key set/no key" badge, Run benchmark (enabled only with a key). POST /api/admin/bench/run?fresh=20 (:1825); seed /bench/seed (:1794); list /bench/list (:1742). Feeds the Ledger Token Saving tile.Measures real ledger-vs-direct token cost on live discovery questions.
Top hit filesEvery file path returned by query_ledger/find_similar, counted with hits, best score, last-seen, last query; Refresh + Clear stats. GET /api/top-hits (:516), clear /api/admin/top-hits/clear (:2366). "What is Claude actually pulling out."
Query analytics (Qdrant)24h hit-rate, 24h/7d counts, median latency, top failed-query clusters, top hit paths; auto-refresh 30s. GET /api/query-stats (:562) from motlogs_agent_queries.Retrieval-quality analytics + raw material for synonym tuning.
Team metrics tileActive devs, calls in window, reverted-rate, global LEDGER÷DIRECT efficiency, a per-team table (devs, calls, calls/dev, cache-served, median top score) + durability line; 7/30/90d selector, 30s refresh. GET /api/admin/metrics/team?days= (:1777). Per-team adoption + reverted-session durability proxy (falls to shared until dev tokens issued).
Query missesLast 50 failed queries — zero (no rows) vs low-relevance (below threshold) — with when/reason/hits/top-score/query. GET /api/query-misses (:540). The exact queries worth tuning for.

Connection & build cards

CardWhat it doesWhy it matters
Qdrant (config + clear)URL, API key (masked), collection name; Clear Qdrant collection (drops only the configured collection + local caches). POST /api/admin/qdrant/clear-collection (:2181).Vector-DB connection management + clean-slate reset.
MCP HTTP TransportShared Bearer token for POST /mcp (masked; blank=public); low-relevance threshold (default 0.05); a Developer-tokens sub-tile issuing per-{developer,team} tokens (shown once), listing, revoking. POST /api/admin/tokens (:1169), GET (:1209), /revoke (:1226). Authenticates AND attributes every MCP call to a developer/team for the Team tile.
Fix dispatcherMax-per-run, interval (minutes; restart-required), Daily auto-fix master switch, Auto-merge fixed PRs (danger), and a read-only dry-run mirror of Git:DryRun. Manual: POST /api/admin/agents/fix/dispatch (:1992). Governs the autonomous approve→PR pipeline and its safety gates.
GraphifyEnabled, run-on-ingest, binary path, output dir, snippet-footer toggle, LLM backend, Reuse Groq (--backend openai with Groq creds). Configures the graph build powering /graph2, snippet footers, and cross-stack walks.

Ingestion control room

The Ingestion glass is the operational cockpit: Sync All (iterate every repo, syncAll); per-repo Sync + Clear (POST /api/admin/repos/clear :2205) with last-ingest timestamps; Max Parallelism; Ignored Directories (globs) + Purge archived chunks (/api/admin/qdrant/purge-archived :2163); a Repositories id→path list with an auto-ingest webhook badge (/api/admin/auto-ingest :689); Clear caches (/api/admin/clear-cache :2358); post-ingest enrichment triggers (Run cross-stack / concepts / all post-passes — /api/admin/cross-stack/run :2021, /concepts/run :1471, /post-passes :2043); a Source Snapshots list with download/delete (/api/admin/source-snapshots :2245); and Graphs download-all zip + per-file list (/api/admin/graph/files :1300, download :1321, download-all :1332).

Why it matters

Everything an operator needs to drive, throttle, enrich, back up, and reset the index lives in one card — the single control surface for the whole load pipeline.

Intelligence & governance cards

CardWhat it doesWhy it matters
SynonymsRun suggester (Groq over failed clusters), list with pending/approved/all filter, approve/delete per row. /api/admin/synonyms/suggest (:1054), /list (:1071), /approve (:1094), DELETE (:1124). Teaches query expansion (VRN = vehicle registration).
Per-repo agents.mdGenerate all + per-repo Preview. /api/admin/agents-md/generate (:1412), /preview (:1424), /export (:1438). Auto-authored per-repo agent onboarding docs, at no LLM cost.
Concept clustersRe-cluster (k-means over design_vector → concept_id) + List centroids. /api/admin/concepts/run (:1471), /list (:1487). Powers cross-language concept_neighbours and refactor planning.
Cross-stack edgesResolve all (routes↔fetch, sproc calls↔CREATE PROCEDURE) + Peek one path. /api/admin/cross-stack/run (:2021), /peek?path= (:2116). Materialises the controller↔SQL↔UI links.
Refactor plan (+ drift check)Goal + anchor + depth → structured blast-radius plan JSON; Drift check compares a changed-file list to the plan. POST /api/plan (:1013). PR-attachable, risk-annotated cross-repo refactor plans.
Session transcriptsDays-back + Ingest sessions into motlogs_session_outcomes. POST /api/admin/session-ingest?days=N (:1392), status /status (:1402).Manual trigger for the "has anyone tried this before" corpus.
Skill candidatesClusters recurring sessions into proposed MCP tools; repo scope, sort, Run discovery, Refresh, per-row Promote. /api/admin/skill-discovery (:1506), /list (:1523), /preview (:1538); promote /api/admin/skills/promote (:1573).Surfaces repetitive agent tasks worth promoting.
Skills & Agents — promotion registryGoverned discover→propose→review→approve→publish→ deprecate lifecycle; propose-from-scratch form; filters; per-row transitions (approve runs a validation gate). GET /api/admin/skills (:1555), POST propose (:1596), /transition (:1628), /export (:1688). Governance that turns candidates into published .claude/skills.
Agent console — findingsThe shared findings queue (inconsistency/bug/fix-proposal/test-gap/ dead-code/log-bug/improvement) with repo/type/status filters; Run agent buttons; per-row approve/dismiss; markdown Preview. Runners /api/admin/agents/{inconsistency,bug-hunt,test-gap,conversation-review, log-scan,dead-code}/run (:1892-1961), fix/run (:1976), fix/dispatch (:1992); queue /api/admin/findings/list (:1728), /status (:1870), /preview (:2005). The human-gated triage surface for the multi-agent quality platform.
Global action rowSave Settings, Reload Current, Save & Restart Service (/api/admin/restart :2426).Applies or reverts config, with an explicit restart path.
Why it matters

Every intelligence layer the ledger builds — synonyms, concepts, cross-stack edges, refactor plans, session memory, skills, and the agent quality queue — has a hands-on control here, so the whole platform is operable by a human, not just by background loops.

API endpoints present but not surfaced as tiles

For completeness, several endpoints exist without a dedicated UI card: GET /api/whats-new (Program.cs:2623); GET /api/learning-needs (:602) and /api/admin/learning-actions (:1148); POST /api/admin/qdrant/rebuild (:774), /qdrant/gc (:2868), /cache/snapshot (:2800), /deprecate (:2386); GET /api/admin/query-cache-stats (:2419); GET /api/admin/eval/relevance (:2818), smoke-run status (:2791)/run-now (:2839), /api/admin/journalctl-tail/status (:2781); POST /api/admin/rubric/backfill (:2067), /decisions/extract (:624), /conversation/ingest (:631); GET /api/graph/{quality,churn,concepts} (:2448/2472/2493), /graph/data/* (:932), /graph/data/similar (:1259), /graph2 + /graph2/app.js; POST /mcp + GET /mcp (:1586/1631); POST /api/admin/overlay/register and /api/admin/overlay/refresh (checkout-overlay registration and the PostToolUse-hook-driven re-diff, loopback-only — see MCP tools & deploy); and the public GET /api/gate-health (the mot-ledger consult-gate health tile — see Governance layer).

Many of these are driven programmatically — by hosted services, the deploy gate, or MCP tools — rather than by an operator clicking a button, which is why they have no tile.

Open Brain — the MOTMaster Architectural Ledger · generated from source 2026-07-07