progress.py reads the whole graded ledger and answers two questions the
per-deck scorecard can't: is the company actually progressing (composite/
BDEF-category/KPI trajectories, recurring vs resolved flags), and is the
material good enough to judge them by — a deterministic gap engine spots
what the decks are NOT showing (no profitability visibility, untargeted
KPIs, no forward guidance, broken forecast chain, silently dropped KPIs,
thin-evidence BDEF categories, no board asks) and renders each gap as a
concrete, paste-ready request for the next deck.
Served live from the ledger (no GPU) at /api/companies/{slug}/progress(.md),
written to /data/ledger/<slug>/PROGRESS.md + /data/reports/latest-progress.md
after each graded deck, and viewable/downloadable from the dashboard company
card ("View progress review"). 18 new tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Boardroom Map — private board-deck grading on your DGX Sparks
Boardroom Map is a StartOS service (.s9pk) that grades portfolio-company
board decks with a panel of local LLMs on your NVIDIA DGX Sparks. Drop each
company's deck into inbox/<company-slug>/; the panel grades it against the
BDEF v1.1 framework (Girdley + Munger/Buffett), an optional local
adjudicator reconciles the panel, and a deterministic Python scorer computes
a 0–100 composite that lands on the company's running scorecard ledger. A
web dashboard shows per-company trends. There is no frontier model and no
cloud API key — in the default air-gapped mode the decks and their grades
never leave your hardware.
It is a sibling of Chambers and reuses the same control-plane pattern (a GPU-free orchestrator on StartOS driving the Sparks over SSH), but swaps the free-form document-review panel for a deterministic deck-grading pipeline with pinned KPI targets and per-company ledgers.
The scoring model
composite (0–100) = quant 60 + qual 40 − red flags (capped at 15)
- Quantitative 60: profitability KPI attainment 30 (heaviest slice), other measurable KPIs 20, forecast integrity 10 — deck N's actuals are chained against deck N−1's stated targets, so moved goalposts cost points. KPI credit is linear above a floor ratio (default 0.5 → zero credit below).
- Qualitative 40: eight BDEF categories (A–H) × 5 points, scored by the panel with evidence quotes; thin evidence scales down.
- Red flags: up to −15; silently dropped KPIs are auto-flagged (capped), and flags raised by a single grader are damped by 0.5.
Every knob lives in config (weights, per-company pinnedTargets and
kpiAliases) so the model can be retuned without a rebuild.
Architecture
StartOS box (control plane, no GPU) DGX Spark(s)
┌────────────────────────────────────┐ ┌───────────────────────────────┐
│ FastAPI dashboard + job runner │ SSH │ per-job Docker network │
│ • inbox/<company-slug>/ (decks) │ ───────▶│ (──internal in airgapped) │
│ • extract text (PDF/PPTX/DOCX/…) │ rsync │ ┌─────────┐ ┌────────────┐ │
│ • plan model "waves" │ ───────▶│ │ vLLM(s) │◀─│ LiteLLM │ │
│ • extractor → graders → adjudicator│ │ └─────────┘ │ router │ │
│ • deterministic composite scorer │◀─────── │ ┌──────────────┐ ▲ │ │
│ • per-company ledgers + scorecards │ rsync │ │ grader ×N │──┘ │ │
└────────────────────────────────────┘ │ │ (read-only, │ │ │
│ │ sandboxed) │ │ │
│ └──────────────┘ │ │
└───────────────────────────────┘
- Graders are one-shot, read-only, hardened containers (non-root,
--cap-drop ALL, read-only rootfs, no docker socket). In air-gapped mode they sit on an--internalnetwork and can reach only the model proxy. - Waves: the job runner serves models in waves bounded by
maxConcurrentModels, so a panel can span more models than fit in GPU memory at once. - Air-gap modes:
airgapped(default — graders reach only the on-Spark model proxy, zero egress, models pre-pulled) orlocal_services(graders may reach LAN services like SearXNG and the second Spark — has egress unless firewalled). - Confidentiality: decks are extracted to text on the StartOS box; only text crosses to the Sparks, and it is wiped from the Sparks after the job. Scorecards and ledgers live only on the StartOS box.
Setup order
Configure Sparks → Test Spark Connection → Configure Models → Configure Graders
→ Configure Grading (rubric, air-gap, weights) → Configure Companies (slugs,
KPI aliases, pinned targets — especially profitability thresholds) → drop decks
into inbox/<company-slug>/ → Grade Decks → watch the dashboard.
Two operational notes:
- Deck filenames must contain the period with the year — e.g.
Board Deck - Q4 2025.pdfparses; a bareQ4does not. Periods are canonicalized (2025-Q4,2026-H1,2026-05,FY2026) and drive the forecast-target chaining between consecutive decks. - If a Spark already runs resident vLLM containers, list their names in
Configure Grading →
preJobStopContainers— they are docker-stopped on the head Spark at job start to free GPU memory (and deliberately not restarted afterward; re-warm them from whatever job owns them).
Repo layout
startos/ StartOS package definition (TypeScript / start-sdk)
manifest/ main.ts interfaces.ts versions/ file-models/ actions/
orchestrator/ The control-plane app (Python)
app.py FastAPI dashboard + JSON API
jobs.py the job runner (extract → serve waves → grade → adjudicate → score)
serving.py vLLM + LiteLLM router on the Sparks, in waves
graders.py launch the grading panel
adjudicator.py the local lead grader
extraction.py PDF/PPTX/DOCX/TXT/MD → text (on the StartOS box)
preflight.py probe models before launching graders
spark_client.py SSH/rsync helpers
bdef.md the baked-in BDEF v1.1 rubric
bm_config.py config defaults (mirrors startos/file-models/config.ts)
sandbox/ grader image (built ON the Spark, not packed in the s9pk)
grader_agent.py grader.Dockerfile build.sh
Build
Local build with start-cli (on a Mac: colima + start-cli in a VM — see the
s9pk-build-on-mac recipe). The vLLM and grader images are built on the
Sparks, not packed into the .s9pk:
boardroom-vllm:latest— the vLLM serving image withENTRYPOINT [](serving.py passes the fullvllm serve …command as the container CMD).boardroom-grader:latest— fromsandbox/build.sh.
npm ci && npm run check && npm run build # type-check + bundle
make # pack the .s9pk (needs start-cli)
Canonical repo: https://gitea.ten31.ai/Ten31AI/boardroom-map.
Status
v0.1.9. Deployed on a StartOS box driving a DGX Spark in single-spark air-gapped mode (gemma-4-31B panel: munger-lens / girdley-operator / buffett-owner). First full grading run completed 2026-07-29: a three-deck company history graded end-to-end into a running ledger with quarter-over-quarter forecast chaining.
Version history: v0.1.1 fixed config persistence (absolute /media/startos/…
paths — relative paths resolved into an ephemeral cwd); v0.1.2 added
preJobStopContainers; v0.1.3 hardened preflight (authed model probes,
poll-until-loaded, crash fast-fail); v0.1.4 fixed air-gapped serving
(HF_HUB_OFFLINE — the --internal network has no DNS) and raised grader
timeouts for ~3.6 tok/s local generation; v0.1.5 fixed the dashboard viewer
(stays open across background refresh) and added report/JSON/scorecard
downloads; v0.1.6 made reports human-friendly — rendered markdown in the
dashboard, an at-a-glance strip per deck report, and a concise "At a glance"
summary table atop every generated DECK_REPORT.md; v0.1.7 added company
deletion from the dashboard (wipes the graded history, optionally restores
the graded deck files to the inbox) so an evaluation can be re-run from
scratch with a different model panel; v0.1.8 added the two-Spark pipeline —
secondary-Spark models work in air-gapped mode (dual-homed proxy; graders
keep zero egress), extraction and grading run in parallel when their models
sit on different Sparks, and single-wave jobs keep the vLLMs warm across
decks instead of reloading the 31B (~6 min) per deck; v0.1.9 added per-company
progress reviews — PROGRESS.md / "View progress review" reads the whole
graded ledger for the trajectory (composite deltas, improving/declining BDEF
categories and KPIs, recurring vs resolved flags) plus a deterministic
deck-quality gap engine that lists what the materials aren't showing (no
profitability visibility, untargeted KPIs, no forward guidance, dropped KPIs,
thin-evidence categories, no board asks) and renders them as a paste-ready
request list for the next deck (/api/companies/{slug}/progress(.md), no GPU
needed).
Known optimization not yet done: the wave is torn down per deck, so the 31B reloads from disk (~6 min) between decks even when the model set is unchanged.
HF model pre-pull on the head Spark is required for air-gapped runs.