`;
}
function Benchmark({ summary }) {
const s = summary || {};
const cells = [
[s.n_projects ?? 0, "", "real projects scored", "held-out" + (s.n_sourced ? `, of ${s.n_sourced} with outcomes` : "")],
[s.n_methods ?? 0, "", "methods compared", "classical · ML · time-series FMs · agents"],
[s.stages ? s.stages.length : 0, "", "snapshots", "forecast at 25 / 40 / 60 / 75% done"],
[s.families ? s.families.length : 0, "", "method families", "from simple formulas to AI agents"],
];
return Slide("benchmark",
html`01 - The benchmark`,
html`
Forecast a project before it finishes
`,
html`
Reveal only the first part of a project's history, then predict its
total final cost and the period it will finish. Every method is scored the same way,
on the same real projects it never saw in training.
`,
html`
${cells.map(([n, sfx, lab, sub]) => html`
<${CountUp} to=${n} suffix=${sfx} />
${lab}
${sub}
`)}
`);
}
function Gap({ bench }) {
const opts = useMemo(() => bench ? blendOpts(bench) : null, [bench]);
return Slide("gap",
html`02 - The gap an agent fills`,
html`
Earned Schedule (the industry standard) is excellent early but its finish forecast
collapses on long projects - from 0.35 to 5.4 periods. ML reference-class models stay robust
late but are weaker early. The agentic layer writes code to call every tool, trusts
the ML reference-class for long-horizon timing and Earned Schedule's BAC/CPI for cost, and explains its
choice - the only method strong across all horizons.
`);
}
function Distill({ bench, stage }) {
const opts = useMemo(() => bench ? baseSftOpts(bench, stage) : null, [bench, stage]);
return Slide("distill",
html`03 - Distilling to the edge`,
html`
Shrink the agent for air-gapped, on-device forecasting
`,
html`
Much project data - defence, infrastructure, government - can't leave its
environment. So we distil the teacher's reasoning into 1-4B open models that run offline.
Off the shelf, a 1B model returns a usable forecast under 2% of the time.
After distillation the best reach parity with the teacher and Earned Schedule
(~2.3-2.4% cost error) - small enough to forecast at the edge.
Cost is largely solved by Earned Schedule - the agent matches it; its real win is on schedule, plus auditability and edge-deployability.
The "realcv" ML figures are an in-domain ceiling (trained on the real labels), not a deployable result.
Small still has a floor: the 2B Qwen never reliably learned the tool-calling format.
107 real projects, one simulator, one teacher - results are bounded accordingly.
`);
}
function Modal_() {
const steps = [
["Simulate", "Real libraries have structure but not full cost/progress outcomes - we simulate them to completion."],
["Generate", "The teacher agent runs the code-action loop over the simulated projects; its reasoning traces are captured."],
["Fine-tune", "Five small models LoRA-fine-tuned on the traces, each on the stack it needs."],
["Evaluate", "Every model + baseline scored through one shared harness on the held-out real projects."],
];
return Slide("modal",
html`05 - Built on Modal`,
html`
The whole pipeline runs on Modal
`,
html`
Simulation, trace generation, fine-tuning and evaluation - all on Modal's GPUs,
with a strict firewall: distillation seeds are simulation-only, the real projects stay a clean test set.
`,
html`
${steps.map(([t, d], i) => html`
${i + 1}
${t}
${d}
`)}
`);
}
function Releases() {
const HF = "https://huggingface.co/";
const items = [
["Distillation dataset", "Multi-turn code-action forecasting traces - the data the students learn from (CC-BY-4.0).", "datasets/build-small-hackathon/slipstream-evm-sft", "slipstream-evm-sft"],
["MiniCPM5-1B agent", "From an unusable 1B base to Earned-Schedule parity.", "build-small-hackathon/slipstream-minicpm5-1b-evm", "slipstream-minicpm5-1b-evm"],
["Nemotron-3-Nano 4B agent", "The Mamba-hybrid agent, distilled.", "build-small-hackathon/slipstream-nemotron3-nano-4b-evm", "slipstream-nemotron3-nano-4b-evm"],
["Gemma-E2B agent", "The compact Gemma agent.", "build-small-hackathon/slipstream-gemma4-e2b-evm", "slipstream-gemma4-e2b-evm"],
];
return Slide("releases",
html`07 - Open releases`,
html`
Released on Hugging Face
`,
html`
On the Build Small Hackathon
organisation, with a full write-up.
The dataset is CC-BY-4.0; each model inherits its base model's licence. Trained, evaluated and benchmarked entirely on Modal.
`,
html`Read the technical write-up →`);
}
function Demo() {
const [meta, setMeta] = useState(null);
const [pid, setPid] = useState("");
const [stage, setStage] = useState(0.4);
const [model, setModel] = useState("MiniCPM5-1B");
const [res, setRes] = useState(null);
const [status, setStatus] = useState("idle"); // idle | running | done | error
const [msg, setMsg] = useState("");
const poll = useRef(null);
useEffect(() => {
fetch("/demo/projects").then(r => r.json()).then(m => {
if (m.error) { setMsg(m.error); return; }
setMeta(m); setModel(m.default_model || "MiniCPM5-1B");
const pick = m.projects.find(p => p.strata === "long") || m.projects[0];
if (pick) setPid(pick.id);
}).catch(e => setMsg(String(e)));
return () => clearInterval(poll.current);
}, []);
const run = async () => {
if (!pid) return;
clearInterval(poll.current);
setRes(null); setStatus("running");
setMsg("Every run cold-starts a fresh GPU on Modal, so expect roughly 5-7 minutes. Results stream in as each method lands - Earned Schedule and the real outcome are instant; TabPFN and the live agent follow.");
let base;
try {
const r = await fetch("/demo/forecast", { method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ project_id: pid, stage, model }) });
base = await r.json();
} catch (e) { setStatus("error"); setMsg("Request failed: " + e); return; }
if (base.error) { setStatus("error"); setMsg(base.error); return; }
setRes(base);
const j = base.jobs || {}; const t0 = Date.now();
poll.current = setInterval(async () => {
let pr;
try { pr = await (await fetch(`/demo/result?agent=${j.agent || ""}&tabpfn=${j.tabpfn || ""}`)).json(); }
catch (e) { return; }
setRes(prev => {
if (!prev) return prev; const n = { ...prev }; let ch = false;
if (pr.timesfm && !prev.timesfm) { n.timesfm = pr.timesfm; ch = true; }
if (pr.tabpfn && !prev.tabpfn) { n.tabpfn = pr.tabpfn; ch = true; }
if (pr.agent && !prev.agent) { n.agent = pr.agent; ch = true; }
if (pr.agent_error && !prev.agent_error) { n.agent_error = pr.agent_error; ch = true; }
return ch ? n : prev;
});
const secs = Math.round((Date.now() - t0) / 1000), mm = Math.floor(secs / 60), ss = secs % 60;
const tick = (ok) => ok ? "✓" : "…";
setMsg(`Running live on Modal · ${mm}:${String(ss).padStart(2, "0")} - `
+ `TabPFN ${tick(pr.tabpfn)} · agentic layer ${tick(pr.agent)} (cold start, ~5-7 min)`);
if (pr.done) { clearInterval(poll.current); setStatus("done"); setMsg(""); }
}, 4000);
};
const finishOpts = useMemo(() => res ? demoFinishOpts(res) : null, [res]);
const eacOpts = useMemo(() => res ? demoEacOpts(res) : null, [res]);
const grouped = useMemo(() => {
const g = {}; (meta?.projects || []).forEach(p => (g[p.strata] = g[p.strata] || []).push(p)); return g;
}, [meta]);
// headline readout once the agent has landed
let readout = null;
if (res && res.agent && res.agent.finish != null) {
const a = res.agent, T = res.truth;
const fErr = Math.abs(a.finish - T.finish), cErr = Math.abs(a.eac - T.eac) / T.eac * 100;
const es = res.classical?.earned_schedule;
const esF = es && es.finish != null ? Math.abs(es.finish - T.finish) : null;
readout = html`
The distilled ${res.model} agent forecast a finish within ${per(fErr)}${" "}
and a final cost within ${cErr.toFixed(1)}% of the real outcome${
esF != null && esF > fErr + 0.5 ? html` - versus Earned Schedule's ${per(esF)} off on the finish` : ""}.
Pick a real held-out project and reveal only part of its history. The
agentic layer runs live on Modal - calling every tool and reconciling them -
next to Earned Schedule, TimesFM and TabPFN, against the true outcome. Each run cold-starts a GPU,
so it takes ~5-7 minutes and the methods stream in as they finish.