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 / Reviewers / * Review) 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 run a panel of sandboxed * "reviewer" containers over confidential documents you drop in. 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 reviewer's read-file // tool loop relies on it). Must match the served model family — Qwen3 → // 'hermes'. Empty disables native tool-calling (reviewers 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 // reviewer (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: 'reviewer-a', hfModel: 'Qwen/Qwen3-32B-FP8', spark: 'primary', port: 8001 }, ]), // --- The review panel: one entry per reviewer ("number of reviews") --- // Each reviewer is a model + a persona (the lens it reads through) + an // optional temperature. Mirrors bm_config.py CONFIG_DEFAULTS["reviewers"]. reviewers: 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([ { name: 'reviewer-1', model: 'reviewer-a', persona: '', temperature: null }, ]), // --- Review job settings --- // The rubric: what every reviewer should look for / produce. Layered above // each reviewer's persona. reviewInstructions: z.string().default( 'Review the attached document(s). Produce a structured report: a 3-5 sentence ' + 'summary, the key findings and insights, risks or red flags, open questions, ' + 'and concrete recommendations. Cite the document and section for each point. ' + 'Be honest about uncertainty; never invent facts not present in the documents.', ), // Confidentiality posture for the reviewer containers: // 'airgapped' — reviewers 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' — reviewers 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 reviewers can reach the network. networkMode: z.enum(['airgapped', 'local_services']).default('airgapped'), // SearXNG JSON endpoint, used ONLY in local_services mode to give reviewers a // web_search tool. Empty = no web search. searxngUrl: z.string().default(''), // --- Synthesis (a local lead reviewer; no frontier model) --- synthesisEnabled: z.boolean().default(true), // Alias of the model that writes the consolidated report. Empty = first model. synthesisModel: z.string().default(''), // Optional persona/instructions for the lead reviewer. Empty = built-in default. synthesisPersona: z.string().default(''), // --- Document handling --- // After a job, wipe the extracted document text from the Sparks. Reports are // kept on the StartOS box regardless. Default true for confidentiality. wipeRemoteDocs: z.boolean().default(true), // Watch /data/inbox and auto-start a review when files land (debounced). // Default false: you trigger reviews explicitly with "Run Review". autoRunOnDrop: z.boolean().default(false), // Name of the per-job Docker network created on the head Spark. networkName: z.string().default('boardroom-net'), // --- Auth flags (the secret itself lives in secrets.ts) --- hfTokenSet: z.boolean().default(false), }) export type Config = z.infer export const configFile = FileHelper.json('./config.json', configShape)