#!/usr/bin/env bun // machine: run, use interactively, and grade the machines in a workspace directory. // Machines live at /machines//machine.ts; runs and sessions // are stored under /.machine/. import { readFile, writeFile } from "node:fs/promises"; import path from "node:path"; import { parseArgs } from "node:util"; import { pathToFileURL } from "node:url"; import { agreement, contamination } from "./agree.ts"; import { compareTrials, summarize, trialStats, type Comparison, type TrialStats } from "./compare.ts"; import { judge, type Grader } from "./evaluator.ts"; import { jev } from "./graders/jev.ts"; import type { Harness } from "./harness.ts"; import { DEFAULT_TINKER_JUDGE, defaultHarness, lazyHarness } from "./harnesses/select.ts"; import { INSTRUCTIONS_FILE, withEvaluation, withInstructions, withModel, type Machine } from "./machine.ts"; import { decide, failureReport, optimizer, parseProposal } from "./optimizer.ts"; import { Budget, FREE, isFreeModel, tinkerPrice } from "./pricing.ts"; import { checkoutRevision, descendants, git, loadMachineFrom, machineChangedSince } from "./revision.ts"; import { exportRuns } from "./export.ts"; import { converse } from "./converse.ts"; import { hasCaseFile, loadCases, type Case } from "./cases.ts"; import { comparePaired, mapLimit, runMatrix } from "./trials.ts"; import { CommitRefused, formatReport, machine as sdkMachine } from "./sdk.ts"; import { formatSpecialization } from "./specialize.ts"; import { findVersion, versionFile } from "./versions.ts"; import { interactive } from "./interactive.ts"; import { fileEvents } from "./events.ts"; import { serveDashboard } from "./dashboard.ts"; import { nameSegments } from "./store.ts"; import type { Run } from "./types.ts"; import { createWorkspace, type Workspace } from "./workspace.ts"; const USAGE = `Usage: machine dashboard Live view of runs, sessions, and training in this workspace (--port, default 4242; --judge-model and --budget for grading from the page, default $1 per request; --host 0.0.0.0 to reach it from other machines, with a printed token) machine Use the machine interactively in Pi (--continue resumes, --eval grades each exchange) machine run [input...] Run once and print the reply; without input, open it in Pi machine runs List recent runs, newest first machine eval Grade a stored run against the machine's rubric machine suite Run and grade every input in machines//cases.json machine optimize Rewrite the machine's instructions.md from its failures; keep a winning rewrite machine agree Measure another grader against the machine's own on stored runs machine compare Compare the working tree's machine with a git revision of it machine tree Show a machine, its children, and what each inherits machine export Write graded runs as chat JSONL for fine-tuning machine train Fine-tune the live model on good runs; report the candidate against the live version on holdout.json and leave it pending (never changes the machine) machine specialize --focus An author model writes a child of the machine for the focus (new criteria with graders, instructions) and rehearsal cases (cases.json, holdout.json), validated machine propose --model Measure another model against the live version on holdout.json, as a candidate machine commit [candidate] Make a pending candidate (default: the newest) the live version, if it passed the promotion gate (--force to override) machine revert Make the previous live version live again machine versions List the machine's versions; the first call records the current one as v1 machine converse [case] Run one conversation with the machine's counterpart and print it as it happens (case: a number in --cases, default 1, or the text of a new case) Options: --harness tinker|pi|openrouter|opencode|fx|openai|echo Agent loop (default: $MACHINE_HARNESS, else pi for openrouter/ models and tinker otherwise) --model Override the machine's model --judge-model Model for judge graders (default: $MACHINE_JUDGE_MODEL; on tinker, thinkingmachines/Inkling-Small; otherwise the run's model) --budget |none Stop spending at this many dollars (default: 1 on tinker and pi, none elsewhere) --effort Reasoning effort for runs: none, low, medium, high, xhigh, or 0-0.99 (default: the model's) --judge-effort Reasoning effort for judges (default: medium on tinker, else the model's) --price-as Price a fine-tuned tinker:// checkpoint as this base model --concurrency Runs at once in suite, compare, and optimize (default: 4) --base-url Endpoint for --harness openai ($OPENAI_BASE_URL) --eval Grade each run after it finishes --workspace Workspace directory (default: current directory) --name Workspace name (default: $MACHINE_WORKSPACE or local) --limit Number of runs to list (default: 20; agree uses all stored runs) --json Print runs as JSON --rounds In optimize, rewrite rounds (default: 1) --optimizer-model In optimize, the optimizer's model (default: the machine's) --with jev|judge In agree, the grader to measure (default: jev) --question In agree, ask Jev this instead of the criterion's description --ref In compare, the revision to compare against (default: HEAD) --holdout |none In optimize, held-out cases (default: holdout.json if present) --cases In suite, compare, and converse, a case file in the machine's directory (default: cases.json); in export, only runs made for these cases --trials In suite and compare, run each case n times (default: 1) --tree In suite and export, include the machine's descendants --min-z In optimize, standard errors a rewrite must win by (default: 2) --min-score In export, the lowest scorecard total to include (default: 1) --best In export, keep only each case's best k runs (rejection sampling) --since