- v0.1.1: config persistence — FileHelper paths made absolute (/media/startos/volumes/main/...); relative paths resolved into the JS runtime's ephemeral cwd so action saves never reached /data - v0.1.2: preJobStopContainers (Configure Grading) — docker-stop resident vLLM containers on the head Spark at job start, no auto-restart - v0.1.3: preflight auth (LiteLLM master_key gates /models), poll-until-loaded, crash fast-fail (restarting counts as dead) - v0.1.4: HF_HUB_OFFLINE/TRANSFORMERS_OFFLINE in airgapped serving (--internal network has no DNS); grader _post timeout 600→1800s for ~3.6 tok/s GB10 generation - v0.1.5: dashboard viewer survives the periodic background refresh; download buttons for deck reports, deck JSON, and SCORECARD.md - .gitignore: .startos/ build workspace, start-technologies/ Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
227 lines
10 KiB
TypeScript
227 lines
10 KiB
TypeScript
import { FileHelper, z } from '@start9labs/start-sdk'
|
|
|
|
/**
|
|
* Boardroom Map configuration, persisted to the `main` volume as config.json.
|
|
*
|
|
* Written by the StartOS actions (Configure Sparks / Models / Graders /
|
|
* Grading / Companies) and read by the Python orchestrator inside the
|
|
* container, which mounts the same volume at /data and reads /data/config.json.
|
|
* Keep field names in sync with orchestrator/bm_config.py (CONFIG_DEFAULTS).
|
|
*
|
|
* Boardroom Map is a CONTROL PLANE: a GPU-free orchestrator on StartOS that
|
|
* SSHes into one or two DGX Sparks to serve local models and grade the board
|
|
* decks you drop into /data/inbox/<company-slug>/. A panel of sandboxed
|
|
* "grader" containers scores each deck against the BDEF v1.1 framework
|
|
* (Girdley + Munger/Buffett); Python then computes a deterministic composite
|
|
* (quant KPI attainment 60 incl. profitability 30, qualitative categories 40,
|
|
* red-flag penalties up to -15) and appends it to the company's running
|
|
* ledger. There is NO frontier model and NO cloud key — everything stays on
|
|
* your hardware. The only secrets are the Spark SSH key and an optional
|
|
* Hugging Face token (secrets.ts).
|
|
*/
|
|
export const configShape = z.object({
|
|
// --- Spark connection (mirrors LLaMA-Factory / Nightshift) ---
|
|
primarySparkHost: z.string().default(''),
|
|
primarySparkUser: z.string().default('nvidia'),
|
|
sshPort: z.number().int().positive().default(22),
|
|
// Second Spark for extra model capacity. null = single node.
|
|
secondarySparkHost: z.string().nullable().default(null),
|
|
useBothSparks: z.boolean().default(false),
|
|
// Address the head Spark's own preflight checks use to reach the local proxy.
|
|
headInternalHost: z.string().default('127.0.0.1'),
|
|
|
|
// --- Remote execution ---
|
|
remoteWorkDir: z.string().default('/home/nvidia/boardroom-map'),
|
|
|
|
// --- Images (built ON the Sparks; not packed into the s9pk) ---
|
|
servingImage: z.string().default('boardroom-vllm:latest'),
|
|
graderImage: z.string().default('boardroom-grader:latest'),
|
|
|
|
// --- Serving (vLLM on the Sparks) ---
|
|
gpuMemoryUtilization: z.string().default('0.85'),
|
|
maxModelLen: z.number().int().positive().default(32768),
|
|
// vLLM tool-call parser for native function-calling (the grader's read-file
|
|
// tool loop relies on it). Must match the served model family — Qwen3 →
|
|
// 'hermes'. Empty disables native tool-calling (graders fall back to a
|
|
// JSON-action text protocol).
|
|
toolCallParser: z.string().default('hermes'),
|
|
// LiteLLM router exposing every model alias on one OpenAI-compatible endpoint.
|
|
proxyPort: z.number().int().positive().default(4000),
|
|
// How many distinct models may be co-resident on the HEAD Spark at once. The
|
|
// job runner loads models in WAVES so it never exceeds this — letting you run
|
|
// a panel across more models than fit in GPU memory simultaneously. 1 is
|
|
// safest.
|
|
maxConcurrentModels: z.number().int().positive().default(1),
|
|
|
|
// The MODEL CATALOG: the set of local models the service can serve. Each
|
|
// grader (below) references one of these by `alias`. Mirrors
|
|
// bm_config.py CONFIG_DEFAULTS["models"].
|
|
models: z
|
|
.array(
|
|
z.object({
|
|
alias: z.string(),
|
|
hfModel: z.string(),
|
|
// Which Spark serves this model. In `airgapped` network mode all models
|
|
// must be on the head Spark (see networkMode).
|
|
spark: z.enum(['primary', 'secondary']).default('primary'),
|
|
port: z.number().int().positive().default(8001),
|
|
}),
|
|
)
|
|
.default([
|
|
{ alias: 'grader-a', hfModel: 'Qwen/Qwen3-32B-FP8', spark: 'primary', port: 8001 },
|
|
]),
|
|
|
|
// --- The grading panel: one entry per grader ---
|
|
// Each grader is a model + a persona (the lens it grades through — e.g. a
|
|
// Munger-style inversion skeptic or a Girdley-style operator) + an optional
|
|
// temperature. Mirrors bm_config.py CONFIG_DEFAULTS["graders"].
|
|
graders: z
|
|
.array(
|
|
z.object({
|
|
name: z.string(),
|
|
// Must match one of the model catalog aliases above.
|
|
model: z.string(),
|
|
persona: z.string().nullable().default(''),
|
|
temperature: z.number().nullable().default(null),
|
|
}),
|
|
)
|
|
.default([
|
|
// >= 2 graders required: every deck needs >= 2 valid grade reports.
|
|
{ name: 'munger-lens', model: 'grader-a', persona: '', temperature: null },
|
|
{ name: 'girdley-operator', model: 'grader-a', persona: '', temperature: null },
|
|
]),
|
|
|
|
// Which catalog model runs the stage-1 structured KPI extractor over each
|
|
// deck. Empty = first model in the catalog.
|
|
extractorModel: z.string().default(''),
|
|
|
|
// --- Grading job settings ---
|
|
// The rubric override. Empty = the baked-in BDEF v1.1 framework
|
|
// (orchestrator/bdef.md — Girdley + Munger/Buffett). Non-empty text replaces
|
|
// it wholesale, so include scoring categories A-H if you customize.
|
|
bdefOverride: z.string().default(''),
|
|
|
|
// Deterministic-scorer knobs. The composite is 0-100 = quant 60 (profitability
|
|
// 30 + other KPIs 20 + forecast integrity 10) + qualitative 40 (8 BDEF
|
|
// categories x 5) - red-flag penalties (capped at 15). Mirrors
|
|
// bm_config.py WEIGHTS_DEFAULTS; keep both in sync.
|
|
weights: z
|
|
.object({
|
|
// Points for the profitability KPI attainment bucket (heaviest weight).
|
|
profitabilityKpi: z.number().default(30),
|
|
// Points for the non-profitability measurable-KPI bucket.
|
|
otherKpi: z.number().default(20),
|
|
// Points for forecast integrity: deck N actuals vs deck N-1 stated targets.
|
|
forecastIntegrity: z.number().default(10),
|
|
// Max points per qualitative BDEF category A-H (8 x 5 = 40).
|
|
qualCategoryMax: z.number().default(5),
|
|
// Cap on total red-flag penalty.
|
|
redFlagCap: z.number().default(15),
|
|
// actual/target ratio below which a KPI earns zero credit.
|
|
kpiCreditFloor: z.number().default(0.5),
|
|
// Penalty per KPI that silently disappeared from the deck.
|
|
droppedKpiPenalty: z.number().default(2),
|
|
// Count at most this many dropped-KPI flags.
|
|
droppedKpiMax: z.number().default(3),
|
|
// Quote characters required for full qualitative-evidence weight.
|
|
evidenceFullCredit: z.number().default(400),
|
|
// Damping factor for red flags raised by a single grader only.
|
|
singleSourceFlagFactor: z.number().default(0.5),
|
|
})
|
|
.default({
|
|
profitabilityKpi: 30,
|
|
otherKpi: 20,
|
|
forecastIntegrity: 10,
|
|
qualCategoryMax: 5,
|
|
redFlagCap: 15,
|
|
kpiCreditFloor: 0.5,
|
|
droppedKpiPenalty: 2,
|
|
droppedKpiMax: 3,
|
|
evidenceFullCredit: 400,
|
|
singleSourceFlagFactor: 0.5,
|
|
}),
|
|
|
|
// Confidentiality posture for the grader containers:
|
|
// 'airgapped' — graders join an --internal Docker network: they can
|
|
// reach ONLY the on-Spark model proxy, with zero internet
|
|
// egress. Models must be pre-pulled into the Spark's HF
|
|
// cache (no live download). All models must be on the head
|
|
// Spark. Strongest confidentiality.
|
|
// 'local_services' — graders may also reach configured LAN services
|
|
// (e.g. SearXNG) and the second Spark. NOTE: this network
|
|
// has egress unless you firewall it — use only when you
|
|
// accept that graders can reach the network.
|
|
networkMode: z.enum(['airgapped', 'local_services']).default('airgapped'),
|
|
// SearXNG JSON endpoint, used ONLY in local_services mode to give graders a
|
|
// web_search tool. Empty = no web search.
|
|
searxngUrl: z.string().default(''),
|
|
|
|
// --- Adjudication (a local lead grader; no frontier model) ---
|
|
adjudicatorEnabled: z.boolean().default(true),
|
|
// Alias of the model that reconciles the panel's grades. Empty = first model.
|
|
adjudicatorModel: z.string().default(''),
|
|
// Optional persona/instructions for the adjudicator. Empty = built-in default.
|
|
adjudicatorPersona: z.string().default(''),
|
|
|
|
// --- Document handling ---
|
|
// After a job, wipe the extracted deck text from the Sparks. Scorecards are
|
|
// kept on the StartOS box regardless. Default true for confidentiality.
|
|
wipeRemoteDocs: z.boolean().default(true),
|
|
// Watch /data/inbox and auto-start grading when decks land (debounced).
|
|
// Default false: you trigger grading explicitly with "Grade Decks".
|
|
autoRunOnDrop: z.boolean().default(false),
|
|
// Name of the per-job Docker network created on the head Spark.
|
|
networkName: z.string().default('boardroom-net'),
|
|
// Space/comma-separated docker container names stopped on the head Spark at
|
|
// the start of every job to free GPU memory (e.g. an always-on vLLM another
|
|
// service runs). NOT restarted afterwards — their owner reloads them.
|
|
preJobStopContainers: z.string().default(''),
|
|
|
|
// --- Portfolio companies ---
|
|
// The authoritative source of pinned KPI targets and KPI-name aliases. Decks
|
|
// are dropped into /data/inbox/<slug>/ and each company keeps its own running
|
|
// scorecard ledger. Mirrors bm_config.py CONFIG_DEFAULTS["companies"].
|
|
companies: z
|
|
.array(
|
|
z.object({
|
|
// Directory name under /data/inbox and the ledger key. Stable — do not
|
|
// rename once decks have been graded.
|
|
slug: z.string(),
|
|
// Display name for the dashboard. Empty = the slug.
|
|
name: z.string().default(''),
|
|
// Newline-separated "canonical=alias1;alias2" lines mapping the names a
|
|
// deck uses for a KPI onto its canonical name.
|
|
kpiAliases: z.string().default(''),
|
|
// Targets the scorer holds the company to even when a deck goes quiet
|
|
// about them. `profitability: true` marks the KPI as part of the
|
|
// heavier profitability bucket.
|
|
pinnedTargets: z
|
|
.array(
|
|
z.object({
|
|
kpi: z.string(),
|
|
target: z.number(),
|
|
unit: z.string().default(''),
|
|
direction: z.enum(['gte', 'lte']).default('gte'),
|
|
profitability: z.boolean().default(false),
|
|
}),
|
|
)
|
|
.default([]),
|
|
}),
|
|
)
|
|
.default([]),
|
|
|
|
// --- Auth flags (the secret itself lives in secrets.ts) ---
|
|
hfTokenSet: z.boolean().default(false),
|
|
})
|
|
|
|
export type Config = z.infer<typeof configShape>
|
|
|
|
// Absolute path into the `main` volume (mounted at /media/startos/volumes/main
|
|
// in the JS runtime, /data in the orchestrator container). A relative path here
|
|
// resolves against the JS runtime's EPHEMERAL working directory and silently
|
|
// loses the config on restart.
|
|
export const configFile = FileHelper.json(
|
|
'/media/startos/volumes/main/config.json',
|
|
configShape,
|
|
)
|