Something went wrong. Try again.
Parametric learning in your code harness.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122// Machine versions. A version is what decides how a machine behaves and is// graded: its own instructions, its model, and its rubric. Versions are// numbered (v1, v2, …) in a small history file, machines/<name>/versions.json,// checked in beside the machine; every run records the version that made it.//// The machine's files stay the truth: instructions.md, model.json (read by// modelFrom), and rubric.ts define what runs. The history records each// definition that became live and the order they did, so `revert` can put the// previous one back. A definition that matches no recorded version (an edit// by hand) is recorded as a new version the first time it is needed.
import { createHash } from "node:crypto";import { mkdir, readFile, rename, writeFile } from "node:fs/promises";import path from "node:path";import type { Machine } from "./machine.ts";import { rubricFingerprint } from "./rubric.ts";
export type VersionSource = | { kind: "initial" } | { kind: "recorded"; note?: string } | { kind: "update"; note?: string } | { kind: "train"; candidate: string; checkpoint: string } | { kind: "optimize"; candidate: string } | { kind: "propose"; candidate: string };
export type VersionEntry = { /** "v1", "v2", … */ id: string; /** The machine's own instructions (a child's parent instructions are not part of its version). */ instructions: string; model: string; /** For a tinker:// checkpoint, the base model it was trained from. */ baseModel?: string; /** rubricFingerprint of the rubric at the time. */ rubric: string; /** Fingerprint of (instructions, model, rubric). */ fingerprint: string; createdAt: string; source: VersionSource; /** One line for people: why this version exists, e.g. a candidate report's summary. */ note?: string;};
export type VersionHistory = { machine: string; /** Every version, in creation order. */ versions: VersionEntry[]; /** Ids in the order they became live; the last is live. Revert drops the last. */ live: string[];};
export function definitionFingerprint(def: { ownInstructions: string; model: string; rubric: Machine["rubric"] }): string { return createHash("sha256") .update(JSON.stringify([def.ownInstructions, def.model, rubricFingerprint(def.rubric)])) .digest("hex") .slice(0, 12);}
/** Where a machine's history is kept, and how it is read and written. */export interface VersionFile { readonly path: string; read(machine: string): Promise<VersionHistory>; write(history: VersionHistory): Promise<void>;}
/** machines/<name>/versions.json in a workspace, or any JSON file. */export function versionFile(file: string): VersionFile { return { path: file, async read(machine) { try { const parsed = JSON.parse(await readFile(file, "utf8")) as VersionHistory; if (parsed.machine !== machine) throw new Error(`${file} holds versions of "${parsed.machine}", not "${machine}".`); return parsed; } catch (error) { if ((error as NodeJS.ErrnoException).code === "ENOENT") return { machine, versions: [], live: [] }; throw error; } }, async write(history) { await mkdir(path.dirname(file), { recursive: true }); const tmp = `${file}.${process.pid}.tmp`; await writeFile(tmp, `${JSON.stringify(history, null, 2)}\n`); await rename(tmp, file); }, };}
export function liveVersion(history: VersionHistory): VersionEntry | undefined { const id = history.live.at(-1); return id ? history.versions.find((v) => v.id === id) : undefined;}
/** The recorded version matching this definition, preferring the live one. */export function findVersion(history: VersionHistory, def: Machine): VersionEntry | undefined { const fp = definitionFingerprint(def); const live = liveVersion(history); if (live?.fingerprint === fp) return live; return history.versions.findLast((v) => v.fingerprint === fp);}
/** Add a version for `def` and make it live. Returns the new history and entry. */export function addVersion( history: VersionHistory, def: Machine, source: VersionSource, options: { baseModel?: string; note?: string; now?: Date } = {},): { history: VersionHistory; entry: VersionEntry } { const entry: VersionEntry = { id: `v${history.versions.length + 1}`, instructions: def.ownInstructions, model: def.model, ...(options.baseModel && def.model.startsWith("tinker://") ? { baseModel: options.baseModel } : {}), rubric: rubricFingerprint(def.rubric), fingerprint: definitionFingerprint(def), createdAt: (options.now ?? new Date()).toISOString(), source, ...(options.note ? { note: options.note } : {}), }; return { history: { ...history, versions: [...history.versions, entry], live: [...history.live, entry.id] }, entry };}