Skip to content

Changelog

Each release of Thermite, newest first. Version numbers count up by the middle number, 0.2.0, 0.3.0 and so on, with fixes and quick follow-ups as 0.1.1, 0.1.2. Versions 0.x make no upgrade promise: read Known limits before you upgrade. What comes next is in the roadmap, with a goal for each item.

[0.1.3] 2026-11-06: a rig over several machines, and what a first campaign on it needs

Section titled “[0.1.3] 2026-11-06: a rig over several machines, and what a first campaign on it needs”

v0.1.3 makes a rig a core and its runners on other machines, and holds the fixes its first campaign on three DGX Sparks needed. A v0.1.2 stack updates to it in place, from its release bundle, as Stop, start and update says: control first, then each rig. Control adds its new tables as it starts; no setting or secret changes.

  • A rig over several machines. A rig is a core and its runners: the core keeps the run, and each runner, a GPU or a machine’s CPUs anywhere control’s message bus reaches, builds and measures for the judge or runs a lane’s run_experiment. Add a rig and Edit rig set them up on clusters and machines, with each runner’s volume, CPUs and memory, and every agent program works on them.
  • What each metric is for. A kit’s metric is a priority metric, a guard or an informational metric; a guard may ask the owner instead of refusing, and a metric may be zero. A hill climb’s Overview opens with its metrics.
  • A run’s model files on its runners, however large. Each runner fetches them itself, as the judge or a lane first needs them there, and shows how far it is; a core that judges on its runners alone keeps no copy.
  • What a first campaign on runners found: a build may move its files between folders under the GPU fence, a measured run on a runner may take longer than five minutes, a stale bundle is fetched again, and a failed build is tried again after new images.
  • Priority, guard and informational metrics, in the kit. A kit’s metric may say what it is for: a priority metric (1 is the primary; each next one breaks a tie, by its own min_effect), a guard metric, or an informational one, kept and never deciding. A guard says where its metric must stay, above, below or within some standard deviations, against the baseline, the champion in force or a fixed value. The judge refuses a change that breaks one (tradeoff, or infeasible against a value), and a change equal to the champion on the primary and better on the next priority by its min_effect is a win. A contract’s new metrics adds to its kit’s and tightens a kit’s guards, never loosens one, and a contract’s own guards, hard_constraints and objective.secondary are judged as before. See What each metric is for. Each verdict carries the lower priorities’ and the informational metrics’ values, the judge records the run’s metric plan with its baseline, and the agents’ brief says what breaks a tie, each guard a change must keep and the metrics only recorded.
  • A guard that asks. A guard against a value may say "ask": true: a change past it is not refused, and once the judge confirms it better it waits for the owner under Needs you, a guard asks you before this change is kept: <metric>/<case>: mean <value> is not at most <value>, whatever the run’s promotion rule. Beside a guard that refuses on the same metric, a change that keeps the bound is kept, one between the two waits, and one past both is refused. The verdict event carries asks, the agents’ brief says which changes wait, and the run’s page says , or it asks the owner. See Decide what waits for you.
  • A metric may be zero. A check’s metric that is 0 when nothing is wrong, such as a divergence from a reference or a count of failures, says "may_be_zero": true and may be 0 where every metric had to be above zero. It is compared by its value alone: informational, or held by guards against a value, and a kit or a contract that makes it a priority metric or a guard against the baseline or the champion is refused in words. See the kit manifest.
  • A hill climb’s Overview shows its metrics. At the top, Metrics: the primary in a card with the champions’ gain over time, as the gain or in its unit; each lower priority in a card with a sparkline; a row for each guard with its bound in words, a sparkline against the start, its limit and an allowed band, the champion’s value, the newest measured attempt’s and the room it leaves; and a tile with a sparkline for each informational metric. Below, the Judge queue (what the judge measures now and what waits, each with its runner) and the attempts on the left, Now and Recent on the right. The run, the run’s facts, moves to a tab of its own, Info, with Where the run started, and the list of Champions to the Attempts tab.
  • An experiment tried more than once. The owner may give an experiment up to 10 tries: Tries when adding or editing it on the Experiments tab, tries in queue_add and queue_edit. A try that ends without a win, with no change submitted or with a change the judge settles without a win, sends the same experiment back to the top, until a try wins or none is left. Each try goes to a lane that has not tried it yet while one has not, the planner’s answer is held to that, and the next worker’s brief says This is try 2 of 3 of this experiment. with how each earlier try ended. The Experiments tab shows try 2 of 3 and the earlier tries, and History says why it came back (experiment_retried). See An experiment tried more than once.
  • Add hours, beside ⋯ on a run its stop time paused: the run gets more hours of work, and it resumes, where it paused again at once on Resume and a new run was the only way on. add_run_hours {run, hours} over MCP and POST /v1/runs/<run>/hours do the same, for a running run too (issue 50).
  • Start a teachers’ round, under ⋯ in a running run’s header and in its Live: the run’s teachers review it now, as at a timed round, and the next timed round counts from this one, where they waited for their timer. A round under way finishes first. The owner’s and the operator’s start_teachers_round over MCP and POST /v1/runs/<run>/teachers-round do the same. Control refuses it, in words, for a run that is not running, a rig with no teachers and a rig with its teachers off (issue 53).
  • Runners on other machines. A rig is a core and its runners: the core keeps the run, and each runner, one GPU or a machine’s CPUs, builds and measures for the judge or runs a lane’s run_experiment, one run at a time, in its pool: the judge’s, shared (in turn, first come, first served) or one lane’s. A runner process is the harness and supervisor images started with THERMITE_ROLE=runner. Add a rig is a wizard, a step at a time with Back and Next, that opens on What kind of rig?: One machine, whose steps are where it runs, its GPUs and its name; Several machines, a core and its runners; or, under Other ways to add a rig, one you set up yourself or one on RunPod. Each field has an info icon and each step a link to its page on the docs site. A rig of several machines is set up as a core and its runners: 1 · The core, any machine or free slot control knows, with no GPU needed; 2 · Runners, none at first, each added with + Add a runner: its machine, one of its free GPUs and its pool, with Workers at a time; and What this rig will be, which says where the judge measures and where each lane tries its changes, and keeps Create the rig off with no runner for the judge or runners of two kinds of GPU. Add one later on Edit rig under 4 · Where it runs, on a cluster or a machine control knows (control sets it up, with a token of its own) or one you started yourself, change its pools or remove it while a run goes on (Remove marks it to leave on Save changes, and its row says what happens to the run there, with Keep it to undo it; Add a runner puts a free GPU in the list for Save changes to set up), or send runners over REST and MCP (add_cluster_runner and remove_runner for one on a cluster); the rig’s page has The core (where it runs, its facts and what it keeps) and Runners: each runner with its GPU and place, its pool, whether it answers, what it runs now and its store, who waits for one by pool and since when, and the runners’ lines of the rig’s history. A runner that leaves or stops answering stops its run under way: the judge queues its job again, with no verdict, and a lane is told. A core on a machine without a GPU judges on its runners (THERMITE_LOCAL_JUDGE=0). A runner control sets up, and the new rig’s Workers at a time, are given to the rig at once, or within a minute of the rig’s first report, even for a core set up later or a report while control was away. The judge times on a GPU that shares the machine’s memory, such as a DGX Spark’s GB10, which reports no memory of its own: its use, processes, heat and clocks decide whether it is idle. A core that judges on its runners alone reports their target as its own, so a campaign of their kind starts on it from Start a run. A lane’s run_experiment on a runner no longer stages anything in the lane’s own git index, so a submission carries only its editable paths, and its answer says each run starts on a fresh copy, where what an earlier run built is not kept. See Give a rig runners on other machines.
  • Settings › Rigs’ models is gone: each rig’s agents’ models are set on its Edit rig, and the rig’s own page shows what its agents run, under The agents’ model, with the model check as it runs. Its old address opens Rigs. Run your first campaign gives the rig its free model on Edit rig.
  • Settings › Machines: where rigs run has its own page, apart from Providers’ logins: Kubernetes clusters with their slots, computers over SSH and GPU clouds, each with Add a rig here, and Add a machine with its three kinds. Providers keeps model providers, subscriptions and Git access. Add a rig’s + A new machine opens it, and Add a rig here opens Add a rig with that machine or cluster chosen.
  • The harness image takes HARNESS_KEEP_BASE=1 to keep its BASE_IMAGE’s system packages as their publisher built them, with no upgrade, for a project measured in that publisher’s environment, such as NVIDIA’s PyTorch container. See the harness image.
  • A deploy key’s Git access for github.com or gitlab.com also reaches the host’s SSH on port 443 (ssh.github.com, altssh.gitlab.com), for a network that lets out 443 alone, where control refused the address as another host. See Use a private repository.
  • When control can’t read a project, a kit or a results repository, its message gives the line of git’s error that says why, such as ssh: connect to host github.com port 22: Connection timed out, where it gave git’s last line, the advice “and the repository exists.” that git adds after any failure.
  • A slot that holds a rig’s runner is that rig’s: control’s cluster check named it for the runner (such as rig-a-gpu-2), as another rig, so Edit rig offered none of the slot’s other GPUs to the rig and said there was no free GPU.
  • Edit rig’s Add a runner takes Volume, in GB, CPUs and Memory, in GiB for the runners it adds on a cluster, where every one got 100 GB and the namespace’s default size: a model of about 95 GB, its engine’s builds and its load need more, and a volume can’t grow later.
  • A run’s model files on runners, however large. A lane’s runner fetches them too, at the lane’s first run_experiment there, which is told they are on their way (files and GiB so far) instead of waiting; the judge’s runner fetches them before its first build of the run. The core asks how a runner’s fetch stands in turns and waits as long as bytes come, where it gave up after three hours; it fails in words when a file fails or nothing came for 15 minutes. A runner removes each file from its inbox once its harness has taken it in, so it keeps a model once, and a core that never judges itself (THERMITE_LOCAL_JUDGE=0) keeps none: its supervisor downloads no model file, and its harness takes the assignment with them left to its runners. A runner’s fetch shows as it goes, in its row on the rig’s page and in the line at the top of the run’s Overview: files and GiB so far, and for how long. See Model files on a rig.
  • A rig on a cluster can run the CUDA 13 harness, for Blackwell GPUs and the DGX Spark on arm64: control sets a rig’s or a runner’s CUDA to 13 for an arm64 node or a Blackwell GPU where it can tell; MCP’s add_cluster_rig and add_cluster_runner take cuda and default the same way. Control deploys THERMITE_RIG_HARNESS_CUDA13_IMAGE (a release’s compose.yaml sets the release’s -cuda13 image), and each update keeps the rig on it. The cluster check reads each node’s architecture and, where it may read resource slices, the GPU each DRA device class names, and its node.
  • A lane’s two tools are run_experiment, which runs a command with its change on the target and never counts, and submit_experiment, which sends the change to the judge: under OpenCode, Claude Code and Codex, thermite-trial run-experiment and thermite-trial submit-experiment. They were dev_run and submit (dev-run and submit in thermite-trial). The contract’s limits keep their names, dev_run_timeout_s and dev_run_rss_mib, and so does the dev_run event.
  • A run’s Stats tab lists every agent the rig gives a model to, with No calls yet in place of the figures of one that has made no call, and why where Thermite knows it: its part off on Edit rig, its newest line on Live that says its call had no answer, or when it works. A teacher that never worked looked the same as one never set up. model_usage over MCP and GET /v1/usage give those lines too, with configured: true, why and why_at (issue 52).
  • A run’s stop time (budget.wall_hours, Stop after (hours)) counts the time the run worked, in every mode, as a research run’s clock does: paused time, and time its agents could not work, its rig drained or its harness did not run, no longer count. A hill climb paused for tests or a backup lost those hours, and often had to start again as a new run. Its fork rounds, at a third and two thirds of the budget, count the same way (issue 50).
  • A rig’s model check waits as long as the rig says: The model check waits (seconds) on Edit rig (under Advanced in 2 · The agents), check_seconds over REST and MCP, 10 to 3600 s, 120 s unless set. A shared or self-hosted model that answers after 120 s failed every check, and its rig’s agents stayed stopped. A check with no answer now says the model is down or busy for longer than the check waits, not “is the model up?”, and Pi’s check that passes gives the time to the model’s first token.
  • Check is off while a rig takes the model control just gave it, on Settings › Rigs’ models and the rig’s Checks, with why on hover, and control refuses a model check sent then over REST or MCP: the rig checks the model itself once it has it. Before, a check pressed too soon failed with no model route: set one on control's settings page, which sent the owner back to where they had just set it; its words now say a rig given a model takes it within a minute (issue 44).
  • A research run with agents on a rig whose agents run on a Claude subscription is refused before it starts, with why and what to do: New campaign, Start a run, start_run over MCP, the REST API and thermite run start say so, where every trial ended at once after the start. An advisor whose program is missing from the supervisor’s image says so (Claude Code is not in this supervisor's image: build the supervisor with CLAUDE_CODE_VERSION), where it said spawn claude ENOENT. Use a Claude subscription says the second rig’s images need Claude Code too (issue 25).
  • OpenCode’s own provider with its key runs its paid models: under OpenCode v2 they go through the harness’s key proxy, where control dropped the key and OpenCode answered model unavailable, and Pi, Codex and the planner and advisors take them too, where Use refused them. Its -free models, and every model of the provider with no key, still run in OpenCode’s own client, with no key (issue 26).
  • Under Docker, a rig’s supervisor reaches a model server on the same computer at host.docker.internal, as control and the harness do: an advisor whose model runs there got no answer through the supervisor’s proxy (Podman names the host by itself).
  • A role’s command may move or link its files between folders under the GPU fence, as a build’s cache does: from Landlock’s second ABI any rule set refuses that unless a rule grants it, so a Zig build failed with CrossDevice (EXDEV) on one filesystem. The fence grants it everywhere, and the kernel still refuses a move that would give a file access it lacks where it is. The isolation selftest’s new probe move-across checks every role can do it. See Rig checks.
  • A measured run on a runner of another machine may take longer than five minutes: its supervisor asked its harness for the run over Node’s fetch, whose own limit of 300 seconds for an answer ended every longer run as the harness's run: fetch failed, and its engine with it, whatever the run’s own limit. A call allowed longer than that now goes over plain HTTP, bounded by its own limit alone.
  • An assignment whose project or kit has a source and names a bundle path an earlier run used, for another commit, fetches the bundle again: the supervisor relayed the bundle already there, and the harness refused it as bundle does not contain commit <commit>. See Signed commands.
  • A kit’s run may raise its candidate’s limit of processes and threads, run.processes (16 to 65536; the role’s 256 when unset), for an engine whose thread pools need more: a Flash Next engine’s first prompt failed with SystemResources at 256. See Kit manifest.
  • A failed build is remembered only for the harness build that met it: after an update to new images the judge builds the commit again, where the failure stayed for that commit and kit, even when the images fixed its cause. See Build cache.
  • CI’s run on a version tag passes the checks it had never run: the secret scan skips six made-up values in tests, each by its file and exact value; the machine check builds its stand-in from its Containerfile under Docker; the RunPod and rig image checks pull the message bus’s image first; the control check takes its view before control’s restart once the judge is idle; and an advisor’s schema that names no problems is said not to fit, where it stopped the call. A test of a run past its time gives the program’s killed child up to 2 s to be reaped, where it read it at once and sometimes found it still there. The RunPod check counts the pod’s leaf address once after its launch, and again only when a start moves it: under Docker a pod started again keeps its address. The leaf check makes its pods’ files readable before it copies them into their containers, as it does control’s: under Docker the pod’s server could not open its own config (issue 54).

[0.1.2] 2026-11-03: every agent on Live, and long messages in a terminal

Section titled “[0.1.2] 2026-11-03: every agent on Live, and long messages in a terminal”

v0.1.2 is the first fix release after v0.1.1. A v0.1.1 stack updates to it in place, from its release bundle, as Stop, start and update says: control first, then each rig. It changes no store, setting or secret.

  • Every agent on Live. Everything shows every agent’s lines from every run and rig. Each advisor has its own lines and filter, in its colour, with a line when its call starts and when it ends. A headless advisor’s tool calls show as it makes them. A run’s Live is Terminals | Lines, then whose, with each advisor’s terminal beside the lanes’. Tool calls read as what they did.
  • Long messages reach a program in its own terminal. A teacher’s message of 100 KB went in and was never sent: it now goes in as pastes, Enter goes again while the message waits in the input, and a long message has longer to start. An answer written over many lines is found whole.
  • Two contributors’ fixes: a research run’s clock leaves out the time the harness did not run, and a rig’s first start loses no live line.
  • Get started opens the docs: install, a first campaign, a second machine and a private network, with llm-setup.txt for an AI agent to set Thermite up.
  • The site opens on Get started: four steps at the top of its sidebar, Install on one machine, Run your first campaign, Add a second machine (a new tutorial) and Tailscale / WireGuard (a new page that keeps control and its message bus on a private network). Install on one machine shows how to serve the dashboard over Tailscale. The home page’s first button goes to step 1.
  • llm-setup.txt, at the site’s root: instructions for an AI agent to set Thermite up from the latest release and to find the page for what comes next. The home page and Install on one machine give a prompt to copy for it.
  • The site’s roadmap page is now its Changelog, written from this file; its old address leads there.
  • Everything, at the top of Live’s list of runs (/ui/live/everything): every agent’s lines from every run and rig as they come, each with its run, with Rig and Run to narrow them and the same filters.
  • Each advisor has its own lines on Live, and its own filter on a run’s Live: teacher-1, teacher-2, merger (the merged by model), auditor, each reviewer of the review panel, synthesizer and final writer, beside Advisors, which shows them all. Each kind of agent has its colour, on its filter and its lines. The supervisor sends an advisor’s lines under advisor:<name>, where they all went under supervisor with the advisor’s name in front. A headless advisor’s work shows too: each call of Thermite’s tools it makes (the knowledge base, the run’s code and research, the run’s MCP servers) is a tool line on Live as it happens, as an advisor in its own terminal shows its program’s.
  • Each advisor’s call has a line when it starts (teacher-2 starts, in Claude Code's own terminal) and one when it ends (teacher-2 answered after 6 min, or teacher-2 has no answer after 6 min: … with why), under its own filter, which shows from the call’s start. A teacher’s review is under its own filter too.
  • A run’s Live has two choices in one row: Terminals | Lines, then whose. Terminals has All, side by side, then a tab for each terminal at work in its agent’s colour: each lane, the planner, and each advisor, which had a tab of their own (Advisors) before. The agent chosen stays chosen from one to the other.
  • A tool call on Live reads as what it did, for every program: a command as $ … (without a shell’s bash -lc around it), a file it reads with the lines asked for, a file it edits with the lines taken out and put in, what it searches for and where, a page it fetches, a search’s words. A rig cuts a long call’s arguments by cutting each of their strings, so they stay JSON; Live also reads arguments a rig of v0.1.1 cut short, which it showed as raw JSON.
  • An advisor’s answer written over many lines (as in a fenced block) is found whole. Before, an object of one line inside it was taken for the answer, which then did not fit its role’s, so a teacher in Claude Code’s own terminal gave no review. no valid answer now names the role’s first three problems with the likeliest object, each with its place.
  • A program in its own terminal that keeps its typed message in its input gets Enter again: every 15 s with no turn begun, while its screen still shows part of the message in its input (40 of its characters, inside the input’s frame, whichever part a long message shows), Codex’s placeholder for a long paste or Claude Code’s paste chips, the harness presses Enter once more. A long message has 30 s more to start a turn for each 32 KB, past the 60 s: a program takes a while to read 100 KB in, and an Enter while it does is a new line in it. Before, Codex and Claude Code kept a teacher’s long message in their input, and the call failed as not taken. Text of 256 characters or more now goes in as a bracketed paste even with no line break: a teacher’s brief is one line of JSON, and Codex took each 16 KB piece of it as keys, one at a time, so each Enter came while it still typed.
  • A research run’s clock leaves out the time the harness did not run: the host asleep, its container paused or stopped, or a crash, for 2 minutes or more. The harness records each such gap as a harness_gap event, found by a new awake loop on a 10-second timer while it runs, and at its start from the time since the ledger’s newest event. Before, a run waiting out a provider’s limit on a laptop could reach its max_duration while the host slept (issue #45; from Mahdi3Bani’s pull request #46).
  • A rig’s first start loses no live line: the supervisor publishes them from the moment control holds the rig’s first report, on which control accepts its key. Before, the lines published first were refused for a key not yet accepted, and lost (issue #27; from Mahdi3Bani’s pull request #35).
  • Pi 1.4.0 (was 1.3.0) for the workers, the planner and the advisors in Pi, and pi-tui 1.4.0 for the Pi view, which brings marked-cjk-friendly 0.1.2; the MCP TypeScript SDK 1.32.2 and Hono 4.13.14. Thermite’s patch of pi-ai’s streaming tool-call parse (past 16 KiB, the arguments are parsed when the call ends) applies to 1.4.0 unchanged: its parser is 1.3.0’s. The checks use Biome 2.6.4 (was 2.5.15). The docs site is built with Astro 7.4.4 and Starlight 0.42.7.
  • The workflows use actions/checkout 7, actions/upload-artifact 7, docker/login-action 4 and docker/metadata-action 6, each pinned by its commit.
  • The images start from node:22.23.4-bookworm-slim as Docker built it again, pinned by its new digest: the same Node.js, on the base image’s newer packages.

[0.1.1] 2026-11-03: the message bus, agents in their own terminal, and the knowledge base

Section titled “[0.1.1] 2026-11-03: the message bus, agents in their own terminal, and the knowledge base”

v0.1.1 is the first release after v0.1.0, and it breaks from it: a rig and control now talk only on a message bus, many names changed, and the licence is now Apache-2.0. Install v0.1.1 fresh, on a new machine or a new compose project, from its release bundle. An update in place of a v0.1.0 stack is not tested; Upgrading from v0.1.0 says what it takes, for a stack you can’t start again.

  • A message bus between control and its rigs. A NATS server with JetStream beside control carries every report, event, command, live line and config, over TLS, each rig with keys of its own. Commands reach a rig within a fraction of a second, Live’s lines are pushed to the page, and control keeps every rig’s events on the bus until a retention policy you design removes some (The message bus).
  • Agents work in their program’s own terminal. Workers, the planner and the advisors run in Claude Code, Codex or OpenCode as the program draws itself, and Pi gets a terminal view of its own. On a run’s Live you watch each lane’s terminal, scroll back through it, and type straight into it, as in the program; your message to a running agent reaches it inside its turn. A full usage window is waited out instead of ending the trial (Agents).
  • Thermite’s knowledge base. One hierarchy, a collection for each campaign, its runs and their writers’ slots; every agent searches and reads it, and the roles a campaign allows write to it. The Knowledge page is an explorer with a formatted reader, and owner’s notes (The knowledge base).
  • Results you can use. A run’s champions go to a branch of your project’s repository, one commit each, and each lane’s work as patches, and Start a campaign from a champion makes a new campaign that starts at its commit (Campaigns and runs). A hill climb’s guards show beside its main metric: in each verdict, under each champion and beside each attempt. Reports shows what each finished run produced.
  • Operate it. thermite backup and thermite restore; retention policies; Metrics shows the whole system live (the bus, the stores, the rigs); Settings is a page for each area; log levels change while the parts run; every page’s status bar names the Thermite version and commit control runs (Operate).
  • Secrets kept closer. Control keeps the key that seals your providers’ keys on a volume of its own, and a backup never holds it; a rig’s harness holds its model’s key in memory only. Every request body has a limit, and Live masks the tokens it knows (Security).
  • The licence is Apache-2.0 (License).

v0.1.1 is meant to be installed fresh. For a stack that must go on, it changes how a rig and control talk, and some names that callers send: read this part before you update. An update in place is not tested.

  • Back up first, and update every part. Back up control with thermite backup before the update, and copy the backup off the machine (Back up and restore control). Control, each rig’s harness and each rig’s supervisor of v0.1.1 go together: update control and every rig.

  • The message bus is required. A rig and control now talk on a NATS server with JetStream, beside control (Run the message bus). The new compose.yaml adds the nats service, the one-shot nats-init service that gives the bus’s folders to their users, and the nats-data and nats-config volumes. Control and the stack’s supervisors reach it at nats://nats:4222, fixed in the file. Take the new compose.yaml, then run docker compose run --rm init and docker compose up -d (or podman compose). A control you run another way needs THERMITE_NATS_URL and THERMITE_NATS_CONFIG_DIR, with a server beside it as the compose file runs it: without THERMITE_NATS_URL, control stops at start with control needs its message bus: ….

  • Open the bus to rigs on other machines and clusters. They reach it on NATS’s port, 4222, over TLS. Compose publishes the port on 127.0.0.1 only: set THERMITE_NATS_PUBLISH (such as 0.0.0.0:4222), let the rigs reach the port through every firewall and a cluster’s network rules, and set THERMITE_NATS_PUBLIC_URL when the rigs reach the bus at another name or port than the host of THERMITE_CONTROL_PUBLIC_URL and 4222. Without an address for rigs on its bus, control refuses to set a rig up, to make a join command, and to update a rig on a machine or a cluster (Let rigs on other machines reach it). A rig on RunPod needs no open port: control’s server dials the pod, so the nats service needs a way out to the internet.

  • Set every rig up again. A rig of v0.1.0 reports over HTTP, which v0.1.1 no longer takes. It shows Broken until it runs v0.1.1 (A rig from before the bus):

    • the compose stack: this version’s images and compose.yaml, as above;
    • a rig control set up on a machine: Update to control’s version on its page;
    • a rig on a cluster: Update to these images on its page, or MCP’s update_rig_images;
    • a rig you joined: remove it where it runs, then make a new join command and run it there;
    • a rig on RunPod: terminate it and launch a new one;
    • a rig set up by hand: this version’s images, and THERMITE_NATS_URL and THERMITE_NATS_CA_FILE for its supervisor, as thermite rig add writes them for a new rig.

    Each of these but a rig set up by hand also gives the supervisor’s container the capabilities and the inbox it now needs (below).

  • Control upgrades its store in place. It copies control.db beside itself first (control.db.v<version>.<time>.copy, the two newest kept), then migrates it, to schema version 28. A store v0.1.0 wrote counts as version 0. At the first start, before it takes a report, control moves its events table to the bus’s EVENTS stream, checks that the stream holds the table whole, and drops it; until then every route answers 503. 100,000 rows took about 4 seconds. Control keeps a copy of the store from just before, control.db.v11.<time>.kept, outside the rotation of the copies: remove it yourself once you are sure of the stream, as after a backup of the bus’s volume. A v0.1.0 event that the bus can’t carry as it was goes on the stream under the kind legacy, and the audit log says so (event_moved_as_legacy). A move that can’t finish stops control at start, in words, and keeps the table (Troubleshooting). The knowledge base’s items move into their runs (The knowledge base).

  • Control’s secrets key moves to a volume of its own. The compose files keep it on control-secrets, at /var/lib/thermite-control-secrets/secrets.key (THERMITE_CONTROL_SECRETS_KEY_FILE). At its first start of v0.1.1, control moves secrets.key out of its state folder there: written, read back, then removed from the folder, and logged. A backup no longer holds it: keep a copy apart from the backups (Keep the secrets key).

  • Control refuses some stores. A store from before v0.1.0 (one with its rigs still called units, the first settings page’s model routes, an outside knowledge base, or a table without a column v0.1.0 added) stops control at start with this control store is from before v0.1.0 (…): start Thermite v0.1.0 on it once, …, and is kept as it is: start v0.1.0 on it once first. A store that a newer control migrated is refused, and the message names the copy to restore.

  • Ledgers upgrade in place. Each rig’s harness copies its ledger beside itself first (ledger.db.v<version>.<time>.copy, the two newest kept), then upgrades it, to schema version 29. An older harness refuses an upgraded ledger, and names the copy when it is beside the ledger. A run that closed before the update is not exported at its end.

  • Rigs enroll through control. Control no longer reads THERMITE_CONTROL_RIG_TOKENS_FILE: when it is still set, control starts and logs a warn line that says how rigs enroll. The compose stack enrolls its rigs at each up, with the new enroll service (enroll-2 with the two-rigs profile), which writes .dev/secrets/control-rig-token; run init, then up -d, and delete .dev/secrets/control-rig-tokens.json, which nothing reads. Any other control that used the file: enroll its rigs first (Rigs › Add a rig, a join command or thermite rig add), give each rig its new token, then take the setting out.

  • New names. Control’s REST, MCP and command line take only the new names. They refuse an old one before they sign anything, in words that give the new one, such as bad queue_add payload: `family` is now `direction` . Events, signed commands and contracts recorded before keep their words, and every reader takes them under the new names (src/core/names.ts).

    • An approach family is a direction: family is direction in queue_add, queue_edit, redirect’s rules and an assignment’s queue, in the planner’s answer (direction, new_directions), the teachers’ answers and portfolios, thermite-trial idea --direction, and the events (direction_added, direction_status, and direction in every body that names one). The goal contract’s min_live_families, which nothing reads, keeps its name.
    • The owner’s label for a run is note, where it was direction: in assign_campaign (thermite assign --note), in the run’s row and in its run_created event.
    • The teachers’ merged by model is merged_by and the review panel review_panel, where they were combiner and panel: in the advisors file, set_rig_model (REST and MCP), message_agent’s to, model usage records and advisor answers. An advisors file that still has the old names stops the supervisor at start, naming the field: rename them before the update. Usage records kept before show under the new names.
    • A contract’s notes is additional_information. A new contract with notes is refused (notes: `notes` is now `additional_information` ). A contract saved before keeps notes, and so its hash, and is read as its additional information; a copy or a new version of it takes the new key. examples/contracts/smoke-cpu.json uses the new key, so its hash changed.
    • A field note’s kind note is other (thermite-trial note --kind other). A rule’s kind note is planner_note, in redirect and the teachers’ recommendations.
    • A research note is a source note: the tool add_source_note (was add_note), the event source_note (was research_note), the research kind source_note in list_research and search_research, the harness’s GET /v1/research/source-notes, the research folder’s source-notes/source-note-<n>.md (was notes/note-<n>.md), an export’s source-notes.jsonl and the results repository’s source-notes/<export>.jsonl. Exports made before keep research-notes/.
    • A campaign’s part of the knowledge base is its collection, where it was its folder: MCP’s kb_folders is kb_collections.
  • A run is addressed through its campaign. A run’s name is used once on control, so every interface finds a run by its name, never through its rig. The old addresses are gone, with no redirect: a bookmark or a script that uses one needs the new one.

    • Dashboard: a run’s page is /ui/campaigns/<campaign>/runs/<run>, and each tab adds its name (/attempts, /history, /planner…), where it was /ui/campaigns/<campaign>?run=<rig>/<run>&view=<tab>. An attempt’s page is …/runs/<run>/attempts/<attempt>, where it was /ui/rigs/<rig>/attempts/<attempt>; a run’s Live is …/runs/<run>/live, where it was /ui/live?run=<rig>/<run>; its paper and champion patches are …/runs/<run>/paper.md and …/runs/<run>/champions/<seq>.patch. A rig’s page and its whole Live keep the rig’s address.
    • REST: GET /v1/runs/<run>, /v1/runs/<run>/untried, /v1/runs/<run>/directions and /v1/runs/<run>/champions/<seq>/patch, where they were under /v1/rigs/<rig>/runs/<run> and /v1/rigs/<rig>/champions/<run>/<seq>/patch. POST /v1/propagate takes {run, to_run}, where it took {from_rig, run, to_rig, to_run}; GET /v1/usage takes a run without rig, and its run scope is {kind: "run", run}; POST /v1/campaigns/<campaign>/runs takes queue_from as the run’s name, where it took {rig, run}.
    • MCP: get_run, run_queue, trial_session and model_usage take a run without a rig, and trial_session’s run is required; propagate_champion takes {run, to_run}; start_run’s queue_from is the run’s name.
    • Commands: a kind about one run (its payload’s run, such as pause_run or message_agent) goes to the run’s rig, so POST /v1/commands, MCP’s issue_command and thermite command may leave the rig out. A rig given that is not the run’s is refused in words.
    • The command line: --queue-from RUN, where it took RUN with --rig, or RIG/RUN.
  • Run and campaign names. Start a run and New campaign number a campaign’s runs across all rigs (a second rig’s run is sort-speed-2, no longer sort-speed-1), and an assignment whose name was used, over REST, MCP or the command line, is refused with a name to use instead. A campaign’s and a run’s name take no dot: up to 64 lowercase letters, digits, dashes and underscores, starting with a letter or a digit. A campaign with a dot in its name is copied under a new name to go on; repositories, kits and cases keep their dots. A name that two runs used before v0.1.1, on two rigs or again after a full reset, stands for the newest of them.

  • Answers that changed. In GET /v1/rigs/<rig>/events and a rig’s view (recent), each event’s seq is now its place in the bus’s EVENTS stream, which counts every rig’s events: page on with after set to the last event’s seq, as before. last_cursor in GET /v1/rigs is the held cursor of the rig’s current ledger. GET /v1/health answers {"ok":true,"bus":"connected","degraded":false}.

  • Settings.

    • Gone, and read by nothing: THERMITE_EXPORT_MINUTES (a rig no longer exports on a timer: Exports), THERMITE_COMMAND_POLL_SECONDS (commands come on the bus), THERMITE_KB_SYNC_SECONDS (no past research is copied to a rig) and THERMITE_CONTROL_RIG_TOKENS_FILE (above). Take them out (Settings no longer read).
    • The names from before v0.1.0 are no longer read. Set THERMITE_RIG_ID, not THERMITE_UNIT_ID: a harness or supervisor with only the old name stops at start with missing environment variable THERMITE_RIG_ID. THERMITE_K8S_NAMESPACE takes {rig}, and a template that names {unit} stops control at start. The command line refuses --instance, --unit and --direction as unknown options. A rig refuses a command signed before 2026-10-15, whose envelope names its unit, as malformed command.
    • A number setting whose value is not a number, or is outside what the setting takes, stops its service at start with the setting’s name and what it takes, such as THERMITE_POLL_SECONDS takes a number of seconds above 0, not "30s". Before, the value was used as it came, and an empty value meant 0; now an empty number setting takes its default. A deployment that set such a value fails at start after the update: Settings says what each setting takes. THERMITE_LOG_LEVEL takes debug, info, warn or error, and stops the service at start otherwise.
    • Control stops at start when THERMITE_CONTROL_PUBLIC_URL is plain http:// to another host than this computer or a compose service, unless THERMITE_CONTROL_ALLOW_PLAIN_HTTP=1.
    • The Exports switch on Edit rig (the rig setting exports) now means “export each run when it ends”, and is on by default.
    • Workers in Claude Code, Codex and OpenCode run in their program’s own terminal by default, which takes more memory: Agents. Agent programs run on Edit rig set to headless keeps v0.1.0’s way.
  • Files that are now refused.

    • A contract with search_policy, which nothing read, is refused with Unrecognized key: "search_policy": take it out. Web search comes from an MCP server the campaign names.
    • An advisors file with an api_key or a token written into it, or a claude-code runtime, which needs one, stops the supervisor at start, with where each comes from instead: a key file named by api_key_file, or a model or a Claude subscription on Edit rig.
    • The advisors file’s kb is retired, and with it the teachers’ search_outside_kb tool. A file that still has it is read without it, and the supervisor logs a warn line at each start: take it out. An outside search reaches the advisors as an MCP server that the campaign’s kit names for them.
    • Control refuses a kubeconfig that skips TLS checks or reaches its API server over plain http, unless the owner ticks I accept that this cluster’s connection isn’t checked when adding the cluster.
  • Agents no longer read a project’s CLAUDE.md or AGENTS.md. An agent’s prompt is only what its role’s definition puts together. Give the agents a campaign’s own words with the contract’s new role_additions (Agents).

  • The supervisor’s container needs capabilities. Its entry now runs as root with CHOWN, DAC_OVERRIDE, FOWNER, KILL, SETGID and SETUID, and starts the supervisor as supervisor (10001) with none. The inbox, where both had it at /inbox, is now /controller/inbox in the harness and /supervisor/inbox in the supervisor, each in a folder of its image that only root and its own group may enter. The compose files, a release’s compose.yaml, a machine’s units and a cluster’s manifests give both: a deployment of your own needs the same.

  • A rig on a cluster needs room. A rig with its CPUs and memory set needs at least 1.5 CPUs and 8 GiB of memory while its agent programs run in their own terminal, the default, and 6 GiB headless. A smaller size, a new rig in a smaller slot, or an image update of a rig too small is refused in words: give it more, or set it headless, first.

  • Images you build yourself. The control, harness and supervisor images are built from one file, containers/Containerfile, each its target: docker build --target harness -f containers/Containerfile . replaces -f containers/harness/Containerfile, and likewise for control and supervisor. A build with no target builds the harness. The one-container rig image is still built from containers/rig/Containerfile on a harness image.

  • The licence is now Apache-2.0 (License).

  • Thermite is now under the Apache License 2.0, with a NOTICE file; it was under MIT up to v0.1.0, which stays under MIT. Every published image carries Thermite’s LICENSE and NOTICE in /usr/local/share/doc/thermite/, and control’s image label is now Apache-2.0.
  • A rig and control talk only on the message bus, a NATS server with JetStream beside control: the compose file’s nats service. HTTPS keeps only the registration of a rig’s keys on the bus, its results (champions’ patches and exports, for a rig with no results repository), the kits control keeps by git, and setting a rig up (enrollment, the join command’s script and manifest, pull mode’s enrollment). On the bus, as soon as each is there:
    • reports: the supervisor publishes each signed page of events to its rig’s subject in the REPORTS stream, an inbox that keeps a report only until control has taken it or refused it for good (at most 32 of one rig);
    • events: control keeps every rig’s events on the EVENTS stream, and nowhere else, until a retention policy you make removes some: one message for each event as the rig recorded it, on a subject by its run and kind (Control’s events on the message bus);
    • commands: control publishes each command to its rig’s subject in the COMMANDS stream, a work queue, and the supervisor hands it to its harness at once, within a fraction of a second, where it waited for a 5-second poll;
    • live lines: the LIVE stream, a view that keeps a message an hour at most, 1,000 messages of each subject and 256 MiB in all;
    • config: each rig’s model route, advisors’ routes, MCP servers with their sign-ins, and its Git accesses while a live assignment names them, in the CONFIG key-value bucket, each value sealed whole to the rig’s own seal key; the supervisor applies each change at once;
    • the knowledge base’s files: the KB_FILES stream, an inbox, in chunks of 1 MiB;
    • a rig’s asks: its figures each round, the screens of the trials someone watches, its daily report, a command’s renewal while its sources stage, what it keeps for retention policies, and which of the knowledge base’s files control holds, each a request on the rig’s own subject, answered at its inbox alone (A rig’s asks on the message bus).
  • Control writes what it derives from the rigs’ events from the EVENTS stream, in the stream’s order: a run a rig created, a command’s outcome as the harness gave it, each model call’s usage and the knowledge base’s items, with the stream’s place in the same transaction. Its pages, REST and MCP read the rigs’ events from the stream too: each rig’s view is kept up to date as events are applied, and is built as control starts (about a second for 100,000 events against the bus at 256 MiB). Control opens its port at once, and until its views are built it answers every route 503, control is starting: it builds its views from the events stream first, and /v1/health 503 with "starting":true. A store restored older than the stream goes on from its place; a store past the stream’s end stops control at start, naming the stream.
  • With the bus away, its store full or the apply late, control refuses a report for now, takes none of it, and takes it when it comes again. A report page control refuses (a key that waits for the owner, a clock ahead) no longer makes every later one a gap: control’s answer to the supervisor’s figures names where control is in the rig’s events, and the supervisor sends again from there.
  • A command the bus did not take (the rig’s queue of 100 full, or the bus away) is held, pending, shown on the rig’s page and under Needs you, and published again every 10 seconds; control also publishes its pending commands again as it starts. The stream remembers a command’s id for a week, so a command reaches its rig once. A command the owner cancels is taken off the bus. A command’s outcome comes only from the harness’s events: a refused command is rejected from its command_rejected event, one whose content could not be staged is recorded as not applied, with why, and a followed command goes from sent to done. Control keeps a command whose sources still stage alive for up to 24 hours after it was issued, where it expired after its hour.
  • Live’s pages get their new lines pushed: control sends each new line to a run’s Live and a rig’s whole Live as a server-sent event (<page>/live/stream), and the browser connects again on its own from the last line it has. A page with more than 500 lines waiting keeps what it shows, gets the newest 500, and says how many it did not get. REST’s GET /v1/rigs/<rig>/live stays.
  • Each rig signs in to the bus with keys of its own. Its supervisor makes them the first time it starts, bus-key.seed (a NATS user key) and bus-seal.pem (an X25519 seal key), keeps them through a full reset, and registers their public parts with control at each start, POST /v1/rigs/<rig>/supervisor with bus_key and bus_seal_key, trying again every 30 seconds until control has them. A rig’s user may use only its own subjects: publish its own reports, live lines, asks and knowledge-base files, take its own commands and config from its own consumers, and receive replies only at its own inbox. A changed key takes effect at once and shows on the rig’s page and under Needs you (Key changed, until Seen); a key another rig or control holds is refused in words; a reload of the bus’s users that keeps failing for a minute shows as Bus users. Control’s own keys are sealed in its store.
  • Every connection to the bus is over TLS. Control makes its own certificate authority at its first start (bus-ca.pem and bus-ca.key, beside its signing key, valid for 10 years), and signs the server’s certificate for the names the server is reached by (397 days, made again 30 days before its end or when the names change, taken by the server without a restart). Each rig control sets up gets the bus’s address (THERMITE_NATS_PUBLIC_URL, by default the host of THERMITE_CONTROL_PUBLIC_URL on port 4222) and the authority’s certificate: on a machine as a file (THERMITE_NATS_CA_FILE), on a cluster in the supervisor’s environment (THERMITE_NATS_CA), and again at each update of its images; the compose stack’s supervisors read it from control’s volume (THERMITE_NATS_CA_EXPORT). thermite rig add writes keys/bus-ca.pem and THERMITE_NATS_URL in rig.env, from POST /v1/rigs’s new bus. Add a rig shows the address the rig will reach, as Message bus.
  • The bus’s server has a memory limit, 256 MiB by default (THERMITE_NATS_MEMORY), of which its Go runtime is told 78% (GOMEMLIMIT), so that it frees what a read loaded in time; THERMITE_NATS_STORE sets the most it keeps on its volume, by default 75% of the free space when it starts (Give it more memory or disk). A message holds at most 8 MiB. The server takes at most 1,024 connections, closes one that takes more than 2 seconds for TLS or 3 seconds to sign in, and runs as a user of its own, 10002, in control’s group, with no capability; nats-init gives the folders to their users before it starts.
  • A rig on RunPod is on the bus through a leaf node: the pod runs its own NATS server (the rig image gains nats-server from the bus’s pinned image), which control’s server dials at the public address and port RunPod gives the pod’s port 7422 (bus_leaf on the rig, Message bus under Pull mode). Control mints the pod’s TLS key and certificate at each launch, signed by an authority for that pod alone (THERMITE_LEAF_CERT, THERMITE_LEAF_KEY, THERMITE_LEAF_CONTROL_CA), and the connection binds to an account of the rig’s own, which may do only what the rig’s user may. Pull mode’s tunnel lets through only the registration, the results and the kits (A rig on RunPod).
  • A rig off the bus is Broken, in words, with what to do, on its page, on Rigs (Broken: off the bus) and under Needs you: one that calls over HTTPS with no keys on the bus, one that registered its keys and sent nothing on the bus within 5 minutes, and a RunPod pod with no leaf node or no public address for its port 7422. GET /v1/rigs, GET /v1/rigs/<rig>/view and MCP’s list_rigs give each rig’s bus: on, since its last registration, or off, since when, why and what to do; thermite rig list says so under a rig that is off it.
  • The bus’s state: Settings › Message bus (control’s connection, its store against control’s disk floor, the events written and applied; each rig on the bus or off it, with its reports and commands that wait, what was sent it again and its last report; each stream with what it carries, holds and keeps), GET /v1/bus and MCP’s bus_state for the owner’s and the reader’s tokens, thermite bus (--json prints control’s answer), and /metrics (thermite_bus_*, thermite_live_*, thermite_events_*).
  • An agent program in its own terminal is typed into once it is ready: for Claude Code and Codex, once it has turned bracketed paste on as its input box comes up, and for Claude Code after its SessionStart hook too. Codex typed into earlier took the message as keys and its Enter as a new line, and never sent it. After a long message, Enter goes once the program has read it all.
  • A Claude subscription’s usage windows reach Providers again from Claude Code in its own terminal: its status line, which Thermite sets, writes the 5-hour and weekly windows to the run’s signals file, and the harness reports them as a headless run’s output did. Before, a rig whose agents ran in their terminal never reported them, and the subscription said “No usage reported yet”. An advisor’s calls in their own terminal report them too, through the supervisor; one that runs headless reports none. A message that starts no turn in the program’s log within a minute ends the trial or the advisor’s call with why and the last lines of its screen; before, a lost message left it waiting for good. On a lab rig, a teacher in Claude Code waited 11 hours so, with every audit behind it.
  • The teachers work beside the auditor and the other advisors: an audit never waits for a teacher’s review. Just after the supervisor starts, before it has the advisors’ models from control, the judge goes on holding the changes that wait for an audit, where it once let them go on unaudited.
  • Workers in Claude Code, Codex and OpenCode run in their program’s own terminal UI, by default: Agent programs run, under Advanced in 2 · The agents on Edit rig, and program_mode (terminal, the default, or headless) in the rig_settings command, POST /v1/rigs/<rig>/settings, MCP’s set_rig_settings and thermite command. A trial keeps the mode it started with. The harness types each message into the program, the first among them, and reads its turns, tool calls, usage, last text and session from the program’s own session log, never the screen. A program that exits by itself is resumed in a new terminal with its session, and at the trial’s end the loop saves a snapshot of the screen in the trial’s folder (<program>-screen.ansi, at most 1 MiB). A worker’s limits note gives what its program uses in its terminal, the terminal host included: Claude Code about 550 MiB, OpenCode about 650 MiB and Codex about 300 MiB. A rig whose harness is older reports no program_mode and runs headless.
  • The harness image has a terminal host for them. The broker runs it as the lane’s user, under the lane’s limits and the GPU fence; it starts the program on a pseudo-terminal of its own (node-pty 1.1.0), keeps the screen in a headless terminal emulator (@xterm/headless 6.0.0 with @xterm/addon-serialize 0.14.0), copies the program’s session log where the harness can read it, and types, sends keys, interrupts and ends the program as each program needs, over a socket that answers only requests with its secret. The program holds only its own end of its terminal.
  • The owner watches a worker’s terminal on its trial’s Live page, and types into it there: ⌨ Terminals on a run’s Live lists each lane’s. The screen is drawn read only by xterm.js 6.0.0, kept in vendor/xterm/ and served by control, and leaves the rig only while the owner’s page watches it; the harness keeps the last screens of its 20 most recently ended trials in memory. The console sends terminal_input, a new signed command only the owner issues (the dashboard, POST /v1/commands, MCP’s issue_command with the owner’s token): text then Enter, or named keys, recorded word for word with what became of it (terminal_input events). Input typed while a new program gets ready goes in after the role’s first message (Watch and steer an agent in its terminal).
  • A worker in Pi gets a terminal too, the Pi view: Thermite draws its own Pi session with pi-tui 1.3.0, so Live shows it like the other programs. Its console makes one call in the trial’s loop for each input: words steer the agent, or, with follow_up (on terminal_input, the Live console, REST, MCP and thermite command), wait until it would stop, and the run’s History says which; Esc stops its reply without counting as its stop; /compact, /mcp, /mcp reconnect [server], /status and /skill:NAME.
  • The planner plans in its program’s own terminal too, on a rig set to In their own terminal: in Claude Code, Codex and OpenCode by a worker’s path, as the planner’s user, and in Pi in a Pi view of its own, as a process that reaches the harness only with its call’s credential and answers on its own route, POST /v1/planner/answer. Its call is planner-<run>-<round>: terminal_input types into it, the rig’s report lists it under work.terminals, and the planner’s Live page shows its terminal and console while it plans. A call answers with the newest text that ends one of its turns and holds a plan.
  • The teachers, the auditor and the review panel’s reviewers work in their program’s own terminal or Pi view too, on a rig set to In their own terminal, in the supervisor, as an advisor user. Each call is advisor-<advisor>-<run>-<n>; Live’s ⌨ Terminals lists it, and the supervisor’s Live page shows its terminal and console. The merged by model, the synthesizer and the final writer, and advisors given a command, run as before.
  • An owner’s message to a running worker in Claude Code, Codex or OpenCode reaches the agent inside its turn, where it waited until the program’s run ended, and so does one to a planner or an advisor in its own terminal: Thermite’s hooks in Claude Code and Codex give it after the next tool or, when the model stops, as the Stop hook’s reason to go on, and Thermite’s OpenCode plugin steers it into the session when the agent starts a tool. One message at a time and each once; its receipt, agent_message_read, says how it went in (how: brief, next-call, after-tool, turn-end or continuation), and the trial’s Live says so in words. A message that can’t go in inside the turn goes in the session’s continuation, as before, and none goes in after a submission. Codex’s trials run with --dangerously-bypass-hook-trust, since codex exec runs no hook that is not trusted. A worker or planner in Pi takes every message that waits before its next model call, not one a call.
  • Usage windows are waited out. A worker or planner in its own terminal waits out a full usage window instead of ending: Claude Code goes on by itself after the reset, and the harness types the trial’s continuation into Codex or OpenCode once the reset comes; the owner’s input queued meanwhile goes in after it. An advisor that meets a full window, headless, or a weekly one in its terminal, with its reset at most a week and a day away, waits for the reset instead of counting a failed try. A Codex trial whose ChatGPT plan is full waits in its own conversation and goes on, as OpenCode’s does: the model proxy answers Codex a full plan as a 429 usage_limit_reached, with the 15 minutes to wait.
  • A wait for the provider that a trial asks for holds its own run only, for at most one trial’s length (the run’s, else the rig’s, else 24 hours). A weekly window, with its reset more than a day and at most a week and a day away, holds its run until the reset itself, and Needs you shows <rig> waits for its subscription's weekly limit, with two choices: Give <rig> another model…, which gives the rig’s workers a model of another provider and ends its runs’ waits, or Wait for the reset. Each is in the rig’s history and the audit log (weekly_limit_model, weekly_limit_wait); GET /v1/rigs/<rig>/view and list_rigs give weekly_limit. Resume now on a run’s page ends its agents’ wait, as the new command end_wait does over REST and MCP (the owner’s and the operator’s tokens); the harness records agents_wait_ended.
  • Providers marks a Claude subscription whose usage credits Claude Code reports on, with the chip usage credits on and a note on why it matters; Dismiss hides the note. The providers tool gives them in usage.credits.
  • Every prompt Thermite sends an agent, and every later turn inside a call (a nudge, a warning, a continuation, an owner’s message, the lines after a compaction), comes from its role’s definition in src/agents/roles/. The prompts sent are the same, word for word. Each is kept whole, compressed, in the rig’s ledger and in the run’s export (prompts.jsonl.gz), with its turn in the call, and control gets an agent_prompt event for each, with its sections and where each comes from. A run’s new Prompts tab, the Agents page and each role’s page show them, and so do GET /v1/agents, GET /v1/agents/<role>, GET /v1/runs/<run>/prompts and GET /v1/runs/<run>/prompts/<id>, MCP’s agent_roles and agent_prompts (owner and operator), and thermite agents list and thermite agents show. The rig’s own API previews a worker’s or the planner’s next prompt with no side effect (Agent roles).
  • You can add your own words to an agent role for one campaign: the contract’s new role_additions, one text for each role by its id (worker, planner, teacher, merged-by, auditor, reviewer, or a research campaign’s research-worker, research-planner, research-teacher, research-merged-by, research-reviewer, synthesizer and final-writer), at most 20,000 characters each. Every prompt of the role carries its addition as a section of its own after the role’s text, labelled as the owner’s with the contract’s version; it never replaces the role’s text. A role the campaign’s mode never runs is refused. Set them on New campaign, on the campaign’s page (Your additions to the agents’ roles), with POST /v1/campaigns/<campaign>/additions, MCP’s set_campaign_additions or thermite campaign additions; each saves a new version of the contract.
  • Claude Code, Codex and OpenCode no longer read the project’s CLAUDE.md or AGENTS.md, and OpenCode starts with OPENCODE_DISABLE_PROJECT_CONFIG=1, so it reads no configuration of the project’s. OpenCode’s read tool still adds an AGENTS.md it finds in a folder below the workspace’s top when the agent reads a file there, which no OpenCode setting turns off.
  • A worker’s or a planner’s first message is cut to fit its model’s window, by the advisors’ rule (the compaction point less 30%): the owner’s messages and the role’s own text stay whole, and the message says what was cut and where the rest is. Before, a message larger than the window failed every call of the trial or round.
  • The synthesizer and the final writer, a research run’s writers of its answer cards and of its paper, have models of their own: Synthesizer and Final writer under The advisors on Edit rig, and agent synthesizer or final_writer in set_rig_model (REST and MCP) and rig_models. With none, each uses the first teacher’s model, as before. Their model calls are recorded under their own names on Model use, GET /v1/usage and model_usage.
  • The agent programs’ trials behave alike. An OpenCode trial’s brief states its turn cap, as Claude Code’s and Codex’s do, and a trial that reaches it says so in words. A harness image without OpenCode’s binary ends an OpenCode trial, and its model check, in words. Headless, OpenCode and Claude Code take their prompt on stdin, as Codex does, so a long brief no longer stops the program from starting on Linux. A trial in OpenCode or Codex counts toward budget.llm_usd the tokens the provider’s answers reported as the model proxy saw them.
  • At the end of each Codex trial, the harness reads Codex’s own session log and logs one info line, Codex's session log and its output differ, naming each count that differs from Codex’s --json output. The trial’s record still comes from the output.
  • A Pi worker’s reply longer than 8,000 characters reaches Live cut and marked [cut], where it was lost; every program’s long lines end the same way. The trial loop reads a program’s output at most 4 MiB at a time. A worker process’s result is read through the trial result’s schema, and anything else ends the trial with an error that says what was wrong.
  • The planner reads each rule with who set it (by), and the reasons of a rule anyone but the owner set are marked as its author’s words, data to weigh. The packages the harness hands the advisors name each run by its name and each attempt by its ID.
  • The knowledge base has one hierarchy, one word for each thing: the knowledge base holds a collection for each campaign, made with the campaign; a collection holds its runs; a run holds slots, records for Thermite’s code, owner for the owner’s notes, and one for each agent writer (planner, worker-<lane>, teachers, merged_by, auditor, review_panel, synthesizer, final_writer); a slot holds items, each a blurb or a file, with its links (supports, contradicts, follows) and its versions. An item’s address is <campaign>/<run>/<slot>/<kind>:<id>. As it updates, control moves each item it kept into the records slot of its run as its version 1, with its id. The Knowledge page lists Collections (The knowledge base).
  • Every agent has the knowledge base as tools: kb_search and kb_read for every role, and kb_write for the roles the campaign lets write, in Pi as tools and in Claude Code, OpenCode and Codex as Thermite’s own MCP server, thermite, beside the kit’s (no kit may name a server so). A write lands in the writer’s own slot. Which kinds each role writes is the campaign’s, the contract’s kb_writes, set on the campaign’s page (Who writes in its collection), with POST /v1/campaigns/<campaign>/kb-writes or MCP’s set_campaign_kb_writes. A search goes from the harness to its supervisor and on to control on the bus, and control answers from the run’s scope.
  • Writes are durable events. The harness records a write as a signed kb_item event in the run’s ledger, and keeps the item’s file once by its SHA-256 in its outbox (.kb-files/); the supervisor puts each file once on the bus’s KB_FILES stream (at most 256 chunks of one rig wait), and control checks each before it keeps it: the run is the rig’s, the slot the writer’s own, the file’s SHA-256 its name. A refused write is kb_item_refused in the audit log. Each round the supervisor asks which of its files control holds, and the harness removes those from its outbox. Nothing of the knowledge base is kept on a rig any more: the supervisor no longer copies past research into each run’s knowledge/ folder, and briefs and advisors’ packages no longer carry a past_research line (Compatibility).
  • Search by words: control keeps an FTS5 words index in the same transaction as each item, and reads it with BM25, best first, each hit with an excerpt and why it matched. Each experiment and attempt has an outcome that Thermite’s code reads from its records (won, screened, regress, neutral, broken, unclear, dropped, open). A search filters by outcome, kind, type, run, slot and collection, and answers how many items matched, next_offset, counts of where its words land, how many items hold each word, and did_you_mean. It takes up to 5 more queries, fused by reciprocal rank fusion; like, an item’s address, for more like it; seen, addresses to leave out; and mode exact, FTS5’s own syntax. Long texts are searched in passages of about 2,000 characters, and a read takes an offset, 10,000 characters at a time. No model and no download is needed. The owner searches with GET /v1/kb/search, MCP’s kb_search and thermite kb search, as themselves or as a running run’s agents (as), and reads an item whole with GET /v1/kb/item, kb_read and thermite kb read.
  • Start a run sets what a run’s agents search, fixed as it starts: the campaign’s whole collection or only the run’s own items, and which granted collections it includes (collection and granted over REST and MCP, --fresh, --granted and --no-granted on thermite run start); the run’s page says what it is. A run with no scope of its own reads only the grants its campaign had when it started.
  • The Knowledge page is an explorer, laid out as a file manager: a tree of the collections, their runs and their slots; a table of what the open folder holds, with sortable columns, dropdown filters by kind, outcome, type and writer, and pages of 50; a search of the open folder or of everything, as you or as a running run’s agents; and an address for every collection, run, slot and item, /ui/knowledge/<campaign>/<run>/<slot>/<kind>:<id>. It reads an item in a pane beside the table or on its own page, with its writer, every version, Withdrawn with why, its links both ways, which running runs’ agents can read it, and a file’s facts with Download; a file always downloads as an attachment. It sums up the open folder beside the table, and gives and ends grants there. The same reads are GET /v1/kb/list, GET /v1/kb/item and GET /v1/kb/file, MCP’s kb_list and kb_read, and thermite kb list and thermite kb read. The dashboard’s left navigation collapses to its icons on every page.
  • The reader shows an item’s Markdown formatted (headings, paragraphs, lists, quotes, code, bold and italic), with no HTML let through and no link; a file’s text of another type is shown as it is.
  • What’s stored, at /ui/kb/stored and linked from the Knowledge page: the totals, each collection’s runs, items by type, file bytes, older versions, the words index, the largest files and the file types, what waits on the bus for control to store, and the writes control refused, with why. Also GET /v1/kb/stored, MCP’s kb_stored and thermite kb stored.
  • The owner’s notes: Add a note in the Knowledge page’s reader keeps a note on an item in the owner slot of its run, linked to it, and the run’s agents find it. Also POST /v1/kb/notes and thermite kb note with the owner’s token, and MCP’s kb_note with the owner’s or the operator’s.
  • A run’s Journal tab reads its field notes from the knowledge base, each with what it is about and the trial it was written in.
  • Only the owner grants and removes a campaign’s read of another campaign’s collection: on the Knowledge page, POST and DELETE /v1/campaigns/<campaign>/kb-grants, thermite kb grant and kb revoke, and MCP. The operator’s kb_grant proposes a grant, which the owner approves or declines on the campaign’s page and under Needs you. The grant form says that a grant opens the collection to every agent of the campaign.
  • Control reads its files’ text: Markdown, plain text, web pages (no markup, scripts or styles) and PDFs, with PDF.js 6.4.299, whose legacy build’s files and licence are kept in vendor/pdfjs/ and control’s image, loaded only at their pinned SHA-256 values. It reads each file once, in a child process with no native module, within 120 seconds, a PDF’s first 2,000 pages, 5,000,000 characters and 1,024 MiB of memory; the text goes into the words index, and a file whose text can’t be read is kept whole, with why. Files kept before are read in the background as control starts.
  • A source’s archived text, a lane’s document, the research paper and each report reach the knowledge base whole, each as its item’s file, not their first 20,000 characters, and a record’s own words are no longer cut to 20,000 characters. Items a rig sent before keep the text their event carried.
  • Before a kept conversation reaches the knowledge base, the harness blanks out the values it gave the agent (its trial’s and its calls’ credentials, OpenCode’s server password, Claude Code’s sign-in token), as given, inside JSON, split across lines or in base64, and anything shaped like a key, a token or a password, with one scrubber that the log’s redaction shares (src/common/scrub.ts); then the conversation goes whole, as its item’s file. Kept conversations stored before get a scrubbed new version, which agents read, and the version before stays for the owner; on a rig, those an earlier harness kept in a run’s research folder are scrubbed too.
  • The knowledge base has settings on control: Settings › Knowledge base, GET and PUT /v1/kb/settings, MCP’s kb_settings and set_kb_settings, and thermite kb settings. They give the largest file it takes (none by default; a larger one is refused in words) and how long a file that no item names stays, by default THERMITE_CONTROL_KB_ORPHAN_HOURS, whose own default, 0, keeps every file for good. Control looks for such files at its start and every hour, and writes kb_file_removed to the audit log for each it removes.
  • A run’s own searches read an index of their own on the rig, built with the same search engine as the knowledge base’s (src/common/search/): search_research ranks by BM25, with excerpts, why, passages, did_you_mean, offset and an exact mode, and search_corpus finds an exact phrase as add_claim checks a quote, with its place and the source’s own characters, and lists near misses apart.
  • The owner can withdraw bad evidence: an attempt, for good, with every attempt built on it, or the measurements of one of the judge’s jobs, whose attempts are measured again. The signed command invalidate_evidence is the owner’s alone (an operator’s, an agent’s and an advisor’s are refused), and goes in two steps: a preview, in which the harness says what it would reach (the attempts, the verdicts it supersedes, the judge’s jobs it cancels or queues, the champions it reopens and where the run’s champion moves) and changes nothing, then the withdrawal, which names the preview’s sha and does exactly that or is refused. Nothing is deleted: each attempt reached gets an invalidated verdict over the one before, champions that rested on it are reopened, the run’s champion moves back with a champion_moved_back event and its git ref follows, a change a trial still builds on a withdrawn attempt is withdrawn as it arrives, and a published champion’s patch stays in the results repository. On the dashboard, Withdraw its evidence… on an attempt’s page shows what it decides before Withdraw the evidence, and the page then says what it reached, when, by whom and why, with the chip Withdrawn; thermite invalidate, REST and MCP’s issue_command give the preview first too. Every step is an event, so control’s views, the run’s History, the exports and the knowledge base show it. Withdraw bad evidence shows how.
  • One attempt of a run can be read by itself: GET /v1/runs/<run>/attempts/<attempt> and MCP’s new get_attempt {run, attempt} give the attempt as get_run has it, the rig it ran on, whether the run is an earlier one, and the champion it became.
  • A run’s history stays on control when its rig is retired or fully reset. Such a run stays on its campaign’s page, under Earlier runs, marked on <rig>, retired or before <rig>'s full reset, and opens like any run: its tabs, its attempts, its champions’ patches and its Live, and over REST and MCP. A command to it is refused in words (Run <run> is an earlier run: …), and Start a run can copy its untried experiments. Control keeps each champion’s patch by ledger, so a run of a reused name from before a full reset never stands in for it.
  • Every event names the run it is about, or none for a rig’s own (a command, a check, the rig’s settings): the harness’s ledger works it out as it records the event, and gives the events it already holds their run as it upgrades. Control gives it as run in a rig’s events over REST and MCP. A run’s export takes the events whose run it is, so it holds those about its attempts and the judge’s jobs for it, each verdict among them.
  • A stop of one trial (stop_trial) and a reset of a run’s workers (reset_workers) reach the scheduler as the harness records them: the trial stops at once, and a reset’s trials stop at once. A stop or a reset recorded just before the harness stopped is acted on when it starts again.
  • An assignment issued with the operator’s or an agent’s token carries the campaign’s own additions to its roles and its writers in the knowledge base, as its saved version has them. Others are refused before they are signed, in words that point to set_campaign_additions and set_campaign_kb_writes. The owner’s token assigns any.
  • A change the owner declined is never a lane’s best.
  • A run’s work can go to git (goal 185): an assignment’s results names a repository and a Git access with write rights, and the rig’s supervisor pushes thermite/<run>/champion, the run’s starting commit and a commit for each champion, each checked against the tree the rig published, and thermite/<run>/<lane>, each lane’s attempts as patches. For a project in a folder, a champion’s commit holds the whole repository. Only fast-forwards of branches under thermite/<run>/; the run’s record stays on control. The supervisor’s report says what each run’s branches hold and when they were pushed. Each export now publishes every champion of a run that no export published yet, not only the newest. On New campaign, Where each run’s work goes chooses branches of the project’s repository, a separate results repository or control only, with Push with and Check it can push (a dry run that writes nothing). On a run’s page, the Champions block says where its branch is and when it was pushed, and each champion shows its commit with a menu: Start a campaign from #n (with the run’s contract or a changed one, a grant of its collection, and its first run), Copy its commit and Download as a patch. POST /v1/campaigns/from-champion and MCP’s start_campaign do the same.
  • On a run’s Live, Terminals has a tab for each lane and Side by side; the chosen terminal fills the pane, scaled so all its columns and rows fit, and scrolls back through the 300 lines above its screen, keeping your place as new screens come. A worker’s terminal takes your keys as you press them: click it and type, and its program shows them in its own input box. Control keeps the keys until the rig’s supervisor next asks which screens are watched and hands them over with its answer, asking it back sooner while you type. Each line is recorded at its Enter; an Enter on a line that starts with ! or runs /login, /logout, /config or /permissions is held until a second Enter. The planner’s and an advisor’s terminals, and a Pi view, keep the field of typed lines. Your inputs are a menu in the terminal’s bar.
  • A hill climb’s guards show beside its main metric. The judge’s verdict event carries guards: each guard’s status, its change against the champion, its measured values and, at confirmation, its change against the starting point. On the run’s page each attempt shows its guards beside its gain (decode_tps -0.1%), and the Champions block has a chart for each guard, with each champion’s value, the starting point’s and the limit max_total_regression sets, and a line of guard values under each champion, amber within a quarter of the limit. MCP’s get_run gives each attempt’s guards. A champion judged before verdicts carried guard values gets its guards once from the rig’s ledger (verdict_guards), and its point is estimated from the judge’s change against the start, marked ≈.
  • MCP’s kit_add and POST /v1/kits/<name> take a kit repository’s git address in place of a bundle, {"git": {"url", "ref", "access"}}: control reads the repository itself, over https or with a Git access, and keeps its history as it keeps a bundle’s. An agent no longer sends a large kit as base64.
  • An assign_campaign sent by hand (MCP’s issue_command, POST /v1/commands, thermite assign) saves its campaign on control: its contract becomes that version of the campaign, with the assignment’s starting point and imports, unless control has that version already. The campaign is then on control before its first run starts, for its page, Start a run, a copy and a grant.
  • A grant of another campaign’s collection joins the search of the campaign’s runs at work at once, as a revoke leaves it at once; a collection a run left out as it started stays out.
  • A run needs only the kit’s metrics its contract decides by (its objective’s, guards’, hard limits’ and threshold goals’), in every case it runs. The kit’s other metrics are kept where a case’s check gives them, and a case without them is no longer invalid; a rate the contract does not use needs no work parameter in a case. One kit can serve campaigns that decide by different metrics.
  • The kit’s check runs as checker, a user of its own for each judge GPU, in a folder of its own with the run’s outputs and the judge’s copy of the parameters, and writes only that folder; its working folder is no longer the kit, so a check reaches the kit’s files through {kit}. A kit may name a driver, run.driver: it runs as driver, apart from the candidate’s run, the two joined by their standard input and output; it writes {out}, and the run ends with it. The isolation check probes both users.
  • The sample kit’s and the smoke kit’s benchmark give every rep inputs of its own. The smoke kit’s candidates gain one that reuses work across calls (cache), which must not win, so the rig’s Judge smoke check judges thirteen changes. The smoke kit’s commit, which the check’s campaign is named after and examples/contracts/smoke-cpu-goal.json names, is now 1abb49d1… (rig-check-smoke-cpu-1abb49d1).
  • New campaign’s help on hidden tests, and the pages on them, say what the judge checks on a hidden case: correct output, and a score no worse than the champion’s by more than twice the margin it counts as no change (1% for a campaign made on the page); in a goal campaign, each goal checked on it must be met there too.
  • Nothing sets the status blocked for a goal or a direction: a direction is live or exhausted, and a goal’s status follows its checks on the champion. Older ledgers’ tables still allow it.
  • The words for verdicts, an attempt’s states, a run’s mode and a paper’s next experiments, and a rig’s checks and lanes in its view, take any name a rig sends as plain words.
  • A rig exports each run once, on its own, when the run ends: once the run is closed, its trials and the judge’s job under way for it have ended, and a research run’s final report is written. That export holds the run alone, with its champion when no export published it yet, so a run’s result reaches the results repository, or control, with no one asking. Its manifest says "end": true, and the ledger records it as run_end_exported, so a restart exports a run that ended without one and never a run twice. Every other export is asked for: Export on a rig’s or a run’s page, or export_now, which a full reset and a RunPod pod’s stop and terminate send. An export is a run’s or full: export_now takes exactly one of run, open_runs (each run not closed, each its own export, as a RunPod pod’s stop asks for) or full, and the export of what changed since the last one is no more. The champion of a run that still runs reaches control and other rigs with a full export. There are no hourly exports (When a rig exports).
  • Exports on control are deleted only by hand: tick them on the Exports page and press Delete, which asks you to confirm in the page with each export’s rig, name and size, or use POST /v1/exports/delete, MCP’s delete_exports or thermite exports delete, each with the owner’s token only. A delete is all or none, refuses an export control is still receiving, and writes export_deleted to the audit log for each export (Delete exports).
  • A rig’s harness copies its ledger only into a full export, leaving out the held-out table in one vacuum, its pages written over with zeros; a test fails on a ledger table that is on neither the copied nor the dropped list. A run’s export leaves the rig’s outbox once the supervisor has pushed it or sent it to control, as the rig’s other exports do. At its start, the harness removes an export it stopped writing at a crash, and the judge a build it stopped storing. An export reads its events a page at a time, and the harness, the judge and the supervisor hash files a chunk at a time, so a long history no longer has to fit in memory.
  • A program that exits before it reads its input no longer stops the part that started it: an advisor’s command-line program in the supervisor, and ssh, kubectl and git in control and the supervisor. The call fails on the program’s own words instead.

  • The harness holds its model routes’ keys in memory only: model-route.json and model-route-planner.json in its state folder now name the route, its model and its revision, and a file an older harness wrote loses its key at the harness’s first start. After a restart, the harness awaits its routes, and no plan or trial starts until the supervisor hands them again, within THERMITE_MODEL_ROUTE_POLL_SECONDS (30 by default); that hand-over records no new route and runs no model check.

  • A baseline job’s kept files are written anew in the judge’s state, so they take the checkers’ group: a copy kept the judge’s own group, and a later calibration’s check could not read them (Permission denied).

  • A rig check’s row shows a result that came after a refused request, not the refusal: a new model route starts the rig’s model check by itself, and a check asked meanwhile is refused while it runs.

  • A rig keeps free space on each place it writes to: THERMITE_DISK_FLOOR_GIB, 5 GiB by default (0: none). Below it, the harness starts no trial, plan, judge’s job or export, the work under way ends as it would, the ledger records rig_disk, and the rig goes back to work once each place has 1 GiB more. A model download keeps the floor as it writes, and deletes what it downloaded when it stops there; a bundle, a model or a champion’s patch is copied into the harness only when the copy leaves the disk above it. Each report carries the free space on each place (summary.disk): the rig’s page shows it, with a card at the floor, and Needs you asks the owner to make room.

  • A drain has a deadline: the drain_rig command’s deadline_minutes, given by drain_minutes on MCP’s update_rig_images and set_cluster_rig_resources and on POST /v1/rigs/<rig>/cluster/resources, else THERMITE_DRAIN_MINUTES, 60 by default. At the deadline the drain stops waiting: the ledger records rig_drain with deadline_reached, the rig’s history gets drain_deadline, and Needs you and the rig’s page offer both choices, each with what it does: go ahead now (Update it now, Change its size now, or Stop what still runs), or End the drain. Nothing is chosen for the owner, and the rig starts nothing new until then.

  • Each part answers a health route that checks its loops: control’s /v1/health, the harness’s, and the supervisor’s own, new, on THERMITE_SUPERVISOR_HEALTH_LISTEN (127.0.0.1:8701 in its container). Each gives its long-running loops with the seconds since each came around and its limit, and answers 503 while one is late; a judge in a job and the scheduler in the planner’s call are never late. With no token, GET /v1/health gives only ok, bus, degraded, and starting while control starts; the owner’s or the reader’s token also gets control’s loops and its disk. /metrics gives the ages (thermite_loop_age_seconds, thermite_loop_late), each rig’s free disk and floor, and how long a rig has drained.

  • Every deployment checks the parts’ health and bounds their logs: compose and the release bundle give every service a health check and the json-file log driver with max-size: 50m and max-file: 3; the units of a rig on a machine log to journald with the unit’s rate limit and check health with HealthOnFailure=kill; a rig on a cluster has startup, readiness and liveness probes and a terminationGracePeriodSeconds of 30. A rig on RunPod checks its own parts: the pod’s broker asks the harness’s and the supervisor’s health routes every 30 seconds, and starts a part again that answers 503 or nothing three times running; the pod’s supervisor checks the pod’s own bus server the same way. The troubleshooting page says where each placement’s logs are.

  • Removing a rig control set up, on a machine or a cluster, is refused while any of its results waits on it, as a RunPod terminate and a full reset already were: the page says what waits and to export first. discard <rig> typed instead removes a rig whose results can’t leave, and the audit log records what had not left.

  • A full reset no longer waits an hour and fails on a judge job queued for a run it paused: it waits only for jobs under way and jobs of runs that still run. On the one-container rig image, the supervisor starts again after a full reset.

  • “Update to control’s version” keeps its step and the runs it paused in control’s store, so a control restarted in the middle of an update ends it at start and resumes those runs, and the rig’s history says why.

  • Control looks at RunPod rigs as soon as it starts, so a pod that passed its spend limit or idle stop while control was down is stopped at once, and its notes say so.

  • Each program an advisor runs in (Claude Code, OpenCode, the Codex CLI or a cli advisor’s command) runs as an advisor user of its own, advisor-1 to advisor-8 (uid and gid 10081 to 10088), one for each call, in a fresh folder with only its prompt, its model route and the call’s credentials, and every process of that user ends with the call. The supervisor’s entry closes /run/secrets, /etc/thermite, the supervisor’s state and /results to those users; in the one-container rig image the broker serves their runner. The isolation check holds the supervisor’s part: what an advisor user reached.

  • The supervisor image holds node-pty for the advisors’ terminals: its native part is built in a stage of its own, and only the built module goes into the image. The build fails if a compiler or make is in the image, or if the module does not load.

  • On a cluster, a rig’s supervisor gets 4 GiB of the rig’s memory while its agent programs run in their own terminal, and 2 GiB headless; the harness takes the rest. A new Agent programs run on a cluster rig with its CPUs and memory set changes its size too: checked first, then its pods restart, draining first while it works.

  • Settings › Addresses cluster rigs may reach: the owner lists addresses, one IPv4 or IPv6 host each with its TCP ports and a note, that cluster rigs’ harnesses reach beside DNS and the internet, such as a model server on a Tailscale address (also GET and PUT /v1/reachable-addresses, MCP’s reachable_addresses and set_reachable_addresses). Control writes the list at once into the network rules of each rig in a namespace it created, and takes a slot whose rules open exactly a listed address on its listed ports; the rig’s page says what its harness reaches. A cluster’s harness network rules leave out link-local addresses, the shared range of carrier-grade NAT and 0.0.0.0/8, beside the private ranges, and control refuses a slot whose rules let the harness reach them.

  • A slot whose rules let pods other than the rig’s supervisor connect to its harness still takes the rig; the rig’s page warns, with the NetworkPolicy that lets only the supervisor in. Pods on a cluster run under the runtime’s default seccomp profile (RuntimeDefault) with enableServiceLinks: false, and the isolation check also tries to reach the cluster’s API server from a lane, and fails when it can.

  • A rig’s name follows one rule everywhere: 2 to 40 characters of a-z, 0-9 and -, starting and ending with a letter or digit. Add a rig on a machine took up to 63 characters before, which a cluster’s namespace can’t hold.

  • The harness’s bundled kits and examples are readable only by the harness, and the isolation check proves the lanes’ and agents’ users can’t read them. The isolation check probes the inbox and a file in it in every placement, the planner’s own model route, and lane 1’s compiler cache from every other role; in the one-container rig image it also checks that no file has capabilities.

  • A research lane’s working area is saved when its trial ends one file part at a time, up to 20,000 files and 4 GiB; a larger area is not saved that time, and the harness logs why.

  • Each trial’s session log is kept on the rig when the trial ends, by run and trial, in the harness’s state (sessions/<run>/<trial>/), whichever program ran it, unless it would take the disk below its floor.

  • The local compose stack: each rig has its own way out, rig-egress and rig2-egress, in place of the one egress network both rigs shared, and its rigs enroll with control at each up (above). .dev/ is its owner’s alone (0700), set again each time npm run dev:init or thermite local init runs. GET /v1/rigs also lists the rigs retired and not brought back (retired).

  • thermite status exits with 1 when the harness answers with an error. A rig’s settings file the harness can’t read is kept aside, logged and reported, where the defaults took its place in silence. The harness’s API answers a refusal with 409, input it can’t use with 400 and a run it doesn’t have with 404, as control’s does, where some answered 500.

  • The harness logs its report key’s fingerprint at every start, the rig's report key with fingerprint: the one control shows for the rig. Accept a rig’s key reads it from the harness’s log.

  • Every page’s status bar names the Thermite version control runs and the commit its image was built from, such as control v0.1.1 · 514eb817, with the whole commit on hover.
  • The Audit log holds the commands, with their delivery and outcome, in place of the Commands page (/ui/commands opens the audit log’s commands). It shows 50 entries a page with Newest, ‹ Newer and Older › and where you are, such as 1–50 of 4,812, and drop-down filters by type, by who and by rig, with a search; MCP calls are left out unless you pick them.
  • A run’s Live chips wrap on a narrow screen instead of scrolling, so the menu of kept messages opens over the page instead of inside the chips’ bar.
  • A run’s Live opens on its agents’ terminals while they run in them: each lane’s trial at work and the planner’s call side by side, each with its own console, with Lines and Advisors as tabs beside it.
  • Reports shows what each finished run produced, newest first: a research run’s paper and final report, a hill climb’s champion against its starting point, a goal run’s outcome, with drop-down filters by mode and campaign. A full reset under way is on its rig’s page, with Cancel the reset (and in Needs you when it waits to be confirmed); the supervisors’ daily summaries are left to MCP’s rig_reports and GET /v1/reports.
  • Settings is a page for each area, in place of one long page: Providers, Model prices and Rigs’ models under Models; MCP servers and Addresses rigs may reach under Agents; Knowledge base, Space and Retention policies under Data; Message bus under System. In Settings the left navigation lists these pages, under Back to Thermite; /ui/settings is Providers, and each other page is /ui/settings/<area>.
  • The Exports page filters by rig, kind and campaign in drop-downs of several choices, with a search by run, shows 25 exports a page, and orders its columns: when it was made, its run and campaign, its kind, its rig, its size and Download.
  • Metrics shows the whole system, live, in tabs beside Model use: Bus (the server’s memory and store against their limits, each stream with its messages, bytes, subjects and rate, each consumer with what waits for it, the events’ sizes by kind, every connection with its traffic and subscriptions, the leaf nodes, and a warning for what looks wrong), Control (its CPU, memory, event loop and loops), Storage (each place in memory and on disk, used and available, and control.db’s tables from SQLite) and Rigs (each rig’s harness and supervisor, with their CPU and memory, which they now report). A click on a stream opens its messages, newest first, by subject, and a message its headers and data, read without a consumer. The same from GET /v1/system and MCP’s system_state; a stream’s messages from GET /v1/bus/streams/<stream>/messages and MCP’s bus_messages, for the owner (Metrics: the system view).
  • thermite backup and thermite restore back up control and put it back. A backup is a dated folder with control’s state (control.db, copied whole while control runs, its keys, the bus’s certificate authority, its kits and its exports), a snapshot of the bus’s EVENTS stream, and a manifest with each file’s SHA-256 and the stream’s place. A restore, with control stopped, brings a stream back only where the bus has none, and keeps one that holds the backup’s events unchanged, with or without newer ones after them, which control then applies from its place; it refuses any other, naming what differs. It moves what it replaces to a dated replaced-<time> folder; --bus-store sets the bus’s store aside first. Both sign in to the bus as the backup’s own user, which may only snapshot and restore EVENTS. The compose files get a backup service (profile backup), and the update steps begin with a backup (Back up and restore control).
  • Control keeps a disk floor too: THERMITE_DISK_FLOOR_GIB, 5 GiB as for a rig, on its state folder and in the bus’s store. Below it, control holds new runs, each refused in words (409 over REST), shows a card under Needs you, answers its health with "degraded":true and 200, and gives thermite_control_disk_free_bytes and thermite_control_disk_low in /metrics; it drops nothing, and lets go with 1 GiB more than the floor. Control also writes no file of the knowledge base while its disk is at its floor.
  • Retention policies: everything is kept everywhere by default, and the owner designs policies that remove what they choose (Remove what Thermite keeps). A policy removes one kind (control’s copy of the rigs’ events, the judge’s builds, the trials’ session logs, the advisors’ records, older versions of knowledge-base items, copies of control’s store, or the audit log), on every rig, some rigs or the rigs of some campaigns, by age, by keeping the newest so many, or by a size, keeping each lane’s champion builds, runs still open and the newest store copy unless told not to; every day, every week or only by hand, in UTC. Each starts off, shows what it would remove before it does, and Run now asks first. On Settings, over REST (/v1/retention), MCP (retention_policies, create_retention_policy and the rest) and thermite retention, the owner’s alone; every change is in the audit log. A rig’s kind goes to the rig as the signed command retention_run, and the rig records what it removed as retention_removed, which control lists under What policies removed. No policy removes a rig’s ledger, a results repository or an export.
  • A policy of control’s copy of the rigs’ events purges whole days of a run’s subjects, or a rig’s own, oldest first, and keeps each rig ledger’s floor: control never asks the rig for those events again. Control’s user on the bus may purge EVENTS by subject, and never delete it. GET /v1/rigs/<rig>/events lists the floors as removed; the rig’s ledger keeps the events. Control keeps the two newest copies of its store by itself only while no policy of its store’s copies is on.
  • Settings › Space shows the space each kind of what Thermite keeps takes: on control, the rigs’ events in the bus’s EVENTS stream, the rigs’ exports, control’s store, its copies from before updates, the knowledge base’s files and the kits; on rigs, the judge’s builds and the advisors’ records, as each rig last said, every 10 minutes. Each label says where it is kept (Kept on) and what removes it (Removed by). /metrics has the same as thermite_control_space_bytes{kind}.
  • Every log line has a level: debug, info, warn or error; the lines that say something failed are error, and those about a refusal, a retry or a fallback warn. THERMITE_LOG_LEVEL sets the lowest level a service writes (info unless set), and SIGUSR2 switches a running service to debug and back; a rig’s broker passes it on to the harness and the supervisor.
  • Log levels change while the parts run: a rig’s harness or supervisor, each on its own, by the signed command log_level {part, level} (POST /v1/commands, MCP’s issue_command for the owner and the operator, thermite log-level harness|supervisor LEVEL --rig R), until another changes it or the part restarts; and control’s own by a setting control keeps and applies at once, across its restarts (GET and PUT /v1/control/log-level, MCP’s control_log_level and the owner’s set_control_log_level, thermite log-level control). default or null goes back to THERMITE_LOG_LEVEL. Each change is said in the part’s log, log level now <level>, and written to control’s audit log as log_level_sent or control_log_level_set.
  • Control’s audit log keeps a refusal that repeats, from the same rig or token, on the same route and for the same reason, as one entry with its count and its first and last time (count, at and last_at: on the Audit log page, GET /v1/audit and control_log). A request that fails over REST is the entry of the caller its route’s token names.
  • Control’s pages read each event’s body as its kind’s (src/core/events/schemas.ts). An event whose body is not, or whose actor is longer than 200 characters, stays in the rig’s Recent and its run’s History as Control can't read this <kind> event: <why>. …, and the pages take nothing else from it; GET /v1/rigs/<rig>/view and MCP’s recent_events give it with unreadable and an empty body.
  • Every git call has a time limit of 30 minutes, unless its caller sets a shorter one (a repository probe: a minute), and a failed call names git’s subcommand with git’s own words. The supervisor’s and the command line’s calls to control and to the harness end after 5 minutes with no answer (30 minutes for a champion’s patch or a kit’s bundle), as a call that failed, made again at the next round. The command line’s calls to control carry its build in headers, as the supervisor’s do.
  • Each request body control reads has a limit, answered 413 before control reads it: each of the owner’s REST routes its own (a command 3.5 MiB, a rig launched on a GPU cloud 64 KiB, 4 KiB for a route that takes a few fields), a dashboard form 1 MiB (a kit’s archive 65 MiB, a new campaign with its files 86 MiB), and an MCP request 4 MiB (Limits and pacing). A reverse proxy in front of control takes bodies of up to 90 MB. The harness answers a body past its route’s limit with 413 too, and takes a command of at most 3.5 MiB, the largest that control signs.
  • A request answered 401 is a line in control’s output, with the address it came from, and an unauthorized entry in its audit log, one for each route with its count. An MCP call of a tool the token does not have, or with inputs that do not match the tool, is a refused entry.
  • A fault in control or the harness answers 500 internal error <id>, and the part’s log line gives its words with the same id. An MCP tool’s error has any key or token in it masked.
  • In control’s audit log, an MCP call’s inputs named like a key, token, secret or password, at any depth, and a file given as base64, are kept only as given with a text’s last four characters, for every tool and every token; other text has known credentials masked.
  • A join command’s request gets one answer, 401, for whatever is wrong, uses its token only when the rest is right, and a client whose join requests failed 20 times in a minute waits (429, join_throttled in the audit log).
  • Live masks a trial’s, the model proxy’s and the MCP gateway’s tokens, and the value of any THERMITE_*_AUTH setting, in each line before it leaves the rig.
  • An MCP server’s sign-in calls only addresses an MCP URL may name (https://, or http:// on this machine), and waits 30 s at most for each answer, of 1 MiB at most. Without THERMITE_CONTROL_PUBLIC_URL, an MCP server’s OAuth sign-in works only when control is reached as localhost, 127.0.0.1 or [::1].
  • A kit archive uploaded on New campaign is read in memory and never unpacked to disk: it may unpack to 256 MiB, and links, devices, paths that leave the kit, zip64 archives and encrypted entries are refused.
  • During the owner’s full reset of a rig, MCP’s propagate_champion refuses the agent’s token for it, as issue_command does.
  • Control reads its Kubernetes service-account token as it reads every secret file: an empty one is refused in words that name the setting.
  • Each image upgrades its base’s system packages when it is built (apt-get upgrade), so an image built again takes the distribution’s fixes. The harness’s build cache keys on the image’s packages, so a lane’s first build on an image whose packages changed starts without its cache.
  • node-pty is an optional dependency, with a native part: the harness image builds it in its last stage, and control’s image leaves it out. npm ci builds it where a compiler, make and Python are, and leaves it out with a warning where they are not; the images’ build stages run no package’s install script. The harness image’s build fails without setsid. pi-tui’s prebuilt native helpers are deleted in every image, and the release’s check refuses an image that holds them.
  • Each release’s notes and its bundle’s README.md give the SHA-256 of its compose.yaml, with the command that checks it after download; the install pages say to check it.
  • scripts/release/pins.ts lists the images Thermite pins by digest and does not build, and where each is written; with --registry it asks each image’s registry which digest its tag has now, and --update writes the new digest into every copy. tests/release/pins.test.ts holds every copy of one image’s tag to one digest.
  • The workflows pin each GitHub Action by its commit, with its version beside it, and give a step’s script the tag’s version, the registry and the repository’s address as environment variables. A version tag holds only letters, digits, dots and hyphens.
  • The tests are in tests/, in folders that mirror src/, with their helpers, fakes and stand-ins in tests/helpers/; the stand-ins that run inside images, for Claude Code, Codex, Anthropic’s API, GPUs and machines, are in tests/stand-ins/<name>/, and no published image takes anything from tests/. npm test stops if it finds a test file in src/.
  • In a checkout, a build of the command line older than its source runs the source instead, and says so, so npx thermite never checks against old rules after a git pull with no npm run build.
  • npm run check runs three more checks: the size check (npm run check:sizes: no file over 800 lines grows), the import rule (npm run check:imports: the parts of src/ never import each other, and code outside a folder that is a module imports it through its index.ts) and the loose ends (npm run check:loose-ends: an export of src/ that no other file names, and a path in a comment or a page that names no file), from the rules of shape in CONVENTIONS.md.
  • Each API between the parts has one contract in src/core/api/, each route with its path, its body’s schema, the answer its caller reads and its limits, and both sides use it; the supervisor and the command line call them through one client each (src/common/api/), with typed errors. The bus’s contracts are in src/core/link/.
  • The acceptance scripts are in scripts/acceptance/<name>.mjs, where they were scripts/<name>-acceptance.mjs. Each has an npm script, npm run acceptance:<name> (the compose smoke stays npm run smoke:compose), and they are type-checked. They run the container engine’s own commands with Podman under podman-compose, however it is started, and with Docker under Docker’s compose. New checks: the bus’s permissions and limits (scripts/acceptance/bus.mjs), a RunPod pod’s leaf node (scripts/acceptance/leaf.mjs), the terminal host (scripts/acceptance/terminal.mjs) and control’s backup and restore (scripts/acceptance/backup.mjs).
  • npm run dev:init is thermite local init .dev, run from the source with no build: it needs npm ci first, says what the local rig gets, and, on a machine with NVIDIA GPUs the rig can have, writes compose.gpu.yaml, which Git now ignores.
  • The dashboard demo and the dashboard’s tests read one demo seed, tests/helpers/demo/. A test parses every script the dashboard serves, and holds the pages to no inline script and to server constants put into a script with JSON.stringify only.
  • The research folder, the teachers’ reads of a run’s research, the knowledge base’s items and control’s view of a research run read a run’s claims, findings, hypotheses and notes through one module, src/core/corpus.ts.
  • The hourly exports and THERMITE_EXPORT_MINUTES; the supervisor’s command poll and THERMITE_COMMAND_POLL_SECONDS; the copy of past research on a rig and THERMITE_KB_SYNC_SECONDS; THERMITE_CONTROL_RIG_TOKENS_FILE; THERMITE_UNIT_ID and the other names from before v0.1.0.
  • Control’s routes for a rig over HTTP: POST /v1/rigs/<rig>/ingest, GET …/commands/pending, POST …/commands/<id>/ack, POST …/live, POST …/reports, GET …/model-route, GET …/advisor-routes, GET …/mcp-servers, GET …/kb and GET …/git-access/<name>. The supervisor’s acknowledgement of a command is gone.
  • The harness’s POST /v1/export, which nothing called, its GET /v1/knowledge and its POST /v1/kb; the planner’s and the advisors’ search_past_research, and the research workers’ search tool, which always refused. Agents search the knowledge base with kb_search.
  • Live’s live.json and its poll every 2 seconds.
  • The note The outside knowledge base is now an MCP server on Settings, from the update to v0.1.0, and its Got it. A rig’s harness no longer renames unit-signing.key, which v0.1.0 renamed to rig-signing.key.
  • The verdict neutral_simplify, which nothing ever recorded: the judge has no measure of simpler code.

[0.1.0] 2026-10-26: the first public release

Section titled “[0.1.0] 2026-10-26: the first public release”
  • Pi 1.3.0 (was 1.2.0) for the workers, the planner and the advisors in Pi, and the MCP TypeScript SDK 1.32.1. The build uses TypeScript 7.0.2 (was 5.9.3); the JavaScript it writes for the images is the same as before. The docs site is built with Astro 7.4.0.
  • The harness and supervisor images carry OpenCode 2.1.4 (was 2.0.32). An image built with Claude Code (CLAUDE_CODE_VERSION) is tested with 2.1.308 (was 2.1.305).
  • A rig whose broker stops at start with a fatal error holds it a minute before it exits (THERMITE_FATAL_HOLD_SECONDS, 60 in the one-container rig image), so the pod’s log on RunPod keeps the error and the container does not restart every few seconds. A throw while the rig’s folders are prepared is logged as fatal too.
  • The images are built on Node.js 22.23.4. A release builds every image layer again, without the build cache, so each image has its base’s newest system packages.
  • Install from published images, with no build. Each release attaches a compose file that pins the images by digest, and thermite local init makes the stack’s secret files. Building from source stays for contributors.
  • The images:
    • control, the CPU harness and the supervisor, for amd64 and arm64;
    • the CUDA 12 harness (-cuda), for Pascal to Hopper, the P100 and V100 included, and the one-container rig image for RunPod built on it, for amd64. The release’s compose file gives control the rig image, so a launch on RunPod needs no image;
    • the CUDA 13 harness (-cuda13), for Turing to Blackwell, the DGX Spark included, for amd64 and arm64. It is untested: it has not yet run on a GPU (goal 132, v0.1.2). The release’s checks hold each image to its architectures.
  • Control’s image carries kubectl, for rigs on Kubernetes clusters, with its license (Apache-2.0) as a file. The image’s license label stays MIT.
  • thermite doctor checks the machine. It covers the container engine, the compose tool, ports, disk, the clock, the GPU toolkit, and whether control and the model can be reached, and it says how to fix each problem.
  • The local rig uses what its machine has. thermite local init and thermite doctor say what it gets: the CPUs and memory of Docker or Podman (on a Mac, its virtual machine’s, and how to give it more), NVIDIA GPUs, AMD GPUs and a Mac’s Apple GPU. On Linux with NVIDIA GPUs and NVIDIA’s container toolkit, local init writes compose.gpu.yaml, which puts the rig on the CUDA harness and every GPU: through NVIDIA’s runtime with Docker, through the CDI device nvidia.com/gpu=all with Podman. One GPU is shared; with more, the judge gets 1 and the agents the rest. AMD GPUs and the Apple GPU are not used yet: the GPU images are CUDA only, and containers on macOS can’t reach the Apple GPU. The doctor’s toolkit check asks for what the engine needs, NVIDIA’s runtime for Docker and a CDI spec for Podman, and its Local rig check says what keeps a GPU from the rig, with the fix.
  • Control and every rig report their versions. Control marks a rig that is behind it, refuses a rig it cannot work with, and shows a rig that stopped reporting as stale or offline.
  • Rigs on your own machines over SSH, and on Kubernetes clusters, in a namespace that control creates or in a slot that your operators made.
  • A rig control set up on a Kubernetes cluster changes its number of GPUs, its CPUs and its memory, and keeps its volume, tokens, runs and results: GPUs, CPUs and Memory (GiB) on Edit rig, POST /v1/rigs/<rig>/cluster/resources, or MCP’s set_cluster_rig_resources. Control checks the new size as Add a rig does, reads the slot’s quota again (now with its GPUs and DRA devices, which Add a rig checks too), and applies the rig’s deployments again on the images it runs, draining a rig at work first. A new number of GPUs gives the judge half of them unless the owner sets its share; an operator’s token may change the rest. A rig never moves between none and some GPUs: add a new rig for that.
  • Rigs on RunPod GPUs, on Secure cloud, launched by the owner from the dashboard’s Launch on RunPod or over REST, MCP or the command line:
    • control connects to the pod through RunPod’s proxy, and enrolls it with a one-time code;
    • the pod runs one container that holds the harness and the supervisor;
    • control tracks each rig’s hourly price and spend, and applies a spend limit or an idle stop only when you set one. The dashboard’s launch form fills in an idle stop of 30 minutes, which you can change or clear, and a new run wakes a pod its idle stop stopped;
    • stop, start, restart, full reset and terminate, where terminate waits until every result has left the pod.
    • the rig works on the pod’s own disk, which RunPod empties at a stop, and keeps a snapshot of its state on the pod’s volume, which refuses even root a change of owner. Root takes it every 5 minutes, after each export, after enrollment and at a stop: the ledger with SQLite’s backup, only what changed, each new one swapped in by rename once it is whole. A start on an empty disk restores the newest, plain files and folders only, with the owners and modes the rig’s parts need, so the pod reports again with its keys, enrollment and token. Settings: THERMITE_STATE_SNAPSHOT_DIR, THERMITE_STATE_SNAPSHOT_MINUTES and THERMITE_STATE_SNAPSHOT_STOP_SECONDS.
    • RunPod’s volume ignores file modes, so every user in the pod could read the snapshot: it is sealed. Control makes a new key for each pod at its launch (THERMITE_STATE_SNAPSHOT_KEY, a launch value it keeps no copy of), which only root holds in the pod. Every file is encrypted and authenticated with AES-256-GCM and named by a keyed hash of its content, and the manifest, which binds every file, is sealed too. A start restores a snapshot only when its manifest and every file verify, and otherwise refuses it whole, logs the snapshot did not verify: <why> and starts empty. A pod launched without the key takes and restores no snapshot. The isolation check passes an open snapshot folder that holds only sealed files (“open, sealed”), fails on any file that is not sealed, and looks for the key in every environment it can read (state-snapshot, snapshot-key).
    • a RunPod rig’s page shows its pod log, Pod log, read from RunPod’s API with the cloud’s key in Providers: the newest lines from the container and from RunPod’s host, each marked, with Read again. Before the rig’s first report it sits under Getting it ready and is read once as the page opens: it says why a pod runs but never enrolls, with no RunPod console. Over REST, GET /v1/rigs/<rig>/runpod/logs (the owner’s token), and over MCP, runpod_rig_logs (the owner’s and the operator’s). Control stops a read after 2 seconds with no new line or 10 seconds in all, and takes what looks like a key or token out of each line.
  • A rig’s GPU set (THERMITE_GPUS) takes up to 16 GPUs, as many as control sets a rig up with on a machine. The broker refused a set of more than 8.
  • The Rigs list is a table: each rig’s state, where it runs, its version, the run at work and, on RunPod, its price.
  • Edit rig works without the browser: besides each agent’s model, a rig’s parts on or off, workers at a time, a trial’s length and turns, its promotion rule and its GPU split change with POST /v1/rigs/<rig>/settings and the MCP tool set_rig_settings, and read with GET /v1/rigs/<rig>/settings and rig_settings. They send only what differs from what the rig reports and refuse in the page’s words. An operator’s token changes all but the promotion rule and the GPU split.
  • A rig takes model files by URL and checksum, with progress. It sends its exports to control when the project has no results repository.
  • A Pi worker is reminded once 85% of its trial’s time is used (when the trial has a time limit): submit its best working change now, and, in a trial that measures rather than changes the code, record what it measured as notes, with the numbers, before it starts another measurement.

  • A teacher’s opportunity is no longer left out for a part longer than its limit, such as a gain_bound over 300 characters: the part is cut to its limit.

  • A reviewer’s answer of the review panel is no longer thrown away for a reason longer than 8,000 characters (it is cut) or for a field of its own (it is dropped). A reviewer that gives no usable answer is named, with why, in the supervisor’s error: <k> of <n> reviewers of the review panel answered (<reviewer>: <why>).

  • A research claim names the campaign question it answers. With more than one question, add_claim refuses a claim without question and records nothing (give "question": the index of the campaign question this claim answers, …), and refuses an index the campaign has no question for; with one question, a claim without it answers that one. Before, a claim with no question counted toward no question’s answer card, so a run whose workers left it out had verified claims and every question at 0, and the review panel was never asked. A research task’s yield no longer has no_question.

  • A research run’s reviewers, teachers and workers are told that the run measures nothing: its evidence is what archived sources say. A question is answered when its claims give the best answer sources allow; what only a measurement on the target could settle is named as a next experiment, for a goal or hill-climb run, and is not a gap. Before, reviewers could fail a research run for a test on its hardware that no research trial can run, so it never ended.

  • A hill climb’s baseline that failed in the rig itself, not for its starting point, is tried again at once, as a candidate’s crash_infra is; only after a second such failure does each next try wait 10 minutes. Before, the first such failure held the run for 10 minutes.

  • A research review’s package is no longer cut at 200,000 characters without a word. It takes what the reviewer’s window allows, with the criteria, the questions and the hypotheses first and the findings and other claims last, and a cut is said. Before, a run with many hypotheses lost its newest ones, and its criteria, from the end of the reviewers’ prompt, and its review could not pass.

  • A research review’s reviewer that leaves out a question or a hypothesis, or gives one in a form code can’t read, is asked once more for those alone. A verdict’s word counts in any case, and a field over its limit is cut instead of the whole item being dropped. Before, one missing verdict among a run’s hypotheses failed the whole review.

  • A review whose reviewers keep failing is posted as not passed at its fifth try, or any later one, with each reviewer that gave no answer failing it and why. Before, it waited after the fifth try with no one to post it, and a research run that waited on it could not end.

  • An advisor in Pi whose answer is refused for the same problems three times running ends its call with those problems as its error, in place of being asked again without end; the call then fails as any advisor’s call with no answer does.

  • Three campaign modes: hill climb, goal and research.

  • A hill climb’s first judge job is its baseline: the judge measures the starting point alone on every measured case, in 3 blocks, and records each case’s median, spread and count for every kit metric in a baseline event. No other job of the run is leased, no round is planned and no trial starts until it exists; Live says round <n> waits for the judge's baseline of the starting point (champion #<n>), and the A/A calibration follows it. A guard’s max_total_regression now holds against the baseline’s values too, beyond the starting point’s own spread: the guard's total allowance against the starting point is spent: …. During the baseline the kit’s check may write to THERMITE_BASELINE_DIR, a judge-only folder of at most 64 MiB and 1,000 files that later checks read and cannot change, each file’s size and SHA-256 in the record. A starting point the judge can’t measure (a failed build, a crash, a failed check, an impossible value) is baseline_failed, and the run is in setup: its rounds give each lane the judge’s reasons and ask for a change within the editable paths, never the kit, that makes the project build and run. The judge checks each such change with the baseline job: the first it can measure becomes the starting point (verdict starting_point, a new champion the gain counts from), and one it can’t gets invalid or crash_candidate with the reasons. The run’s Overview shows Where the run started, get_run has baseline, setup and baseline_waiting, and History says each step. A run from before gets its baseline at once if it is running, and when it is resumed if it is paused.

  • The New campaign page and Start a run. Start a run also works without the browser: POST /v1/campaigns/<campaign>/runs, the MCP tool start_run and thermite run start take the same fields and give the same refusals. A campaign’s project can be a folder inside a repository. A sample project and its kit are in examples/matmul-cpu.

  • Campaigns, runs and Live per run, built for scanning. The owner messages the agents and the advisors from one message box, and sees who read each message.

  • Give control a kit repository written elsewhere, as a git bundle: thermite kit add, POST /v1/kits/<name> (the owner’s token, up to 64 MiB) or the MCP tool kit_add (the owner’s and an operator’s, up to 3,000,000 bytes of bundle). Control checks the bundle, keeps every commit with its id, and makes the kit’s head the bundle’s HEAD, its only branch or the ref given, which needs a valid kit.json. A head moves only forward unless forced, and a forced one keeps the old head’s commits. Rigs fetch the kit from control by commit, as they fetch the kits New campaign writes: {"control": "kits/<name>"} as the import’s source, or thermite assign --kit control:NAME. thermite kit list, GET /v1/kits and the MCP tool kits list them; each kit taken is in the audit log as kit_added.

  • Copy a campaign under a new name, with the same contract and kit and its own empty folder, to compare two models from zero: Copy on the campaign’s page, POST /v1/campaigns/<campaign>/copy, the MCP tool copy_campaign and thermite campaign copy. The page’s panel checks the copy’s name as you type and opens the copy’s page, which says where it came from. A research campaign’s copy can name other MCP servers for its agents, such as a web search service.

  • A campaign’s brief, for every mode, on its own step of New campaign, 2 The brief, after 1 Aim: Additional information in Markdown with no length limit, with its preview beside it as you type, Starting links, one address per line, and Hypotheses, one per box. Hill climb and goal add Ideas to try; a research campaign’s topic and questions are there too, each question in its own box, as long as it needs. A research campaign’s starting links are its seeds, which the harness now fetches and archives when the run starts; its step 4 is Time and tools. The contract keeps the brief as notes, links (hill climb and goal), ideas and hypotheses, so old contracts keep their hashes. The planner gets it whole, and the advisors ahead of the evidence and the diff; each worker’s brief has it whole up to 32,000 characters, and the run’s folder keeps it whole as BRIEF.md. A research run starts with the hypotheses as its own. The campaign’s page shows it, Copy keeps it, and thermite assign and a copy over REST, MCP or the command line take it.

  • Archive a campaign, and sort campaigns into folders in the dashboard’s left navigation. Archive, under ⋯ on a campaign’s page, takes it out of the navigation, Needs you and the Overview’s lists; its runs, results and kits stay. Control refuses it, in words, while a run of it is at work or waits to start, and starts or resumes no run of an archived campaign. Campaigns › Archived lists them, with Unarchive. An archived campaign keeps its folder, which neither shows nor counts it, and Unarchive brings it back into that folder; a folder that holds archived campaigns is not deleted. The folder button beside Campaigns in the navigation makes a folder; each folder’s ⋯ renames it, or deletes it when it is empty. Drag a campaign onto a folder, or use Move to folder on its page. A folder folds, and the browser remembers it. Over REST (POST /v1/campaigns/<campaign>/archive, /unarchive and /folder, and /v1/campaign-folders) and MCP (archive_campaign, unarchive_campaign, campaign_folders, create_campaign_folder, rename_campaign_folder, delete_campaign_folder and move_campaign), each change in the audit log.

  • The left navigation lists every campaign that is not archived, finished ones too, and no longer lists the rigs: the Rigs page does. A campaign in it shows a link’s pointer, and the grabbing hand only while it is held or dragged into a folder.

  • One experiment queue for each run. The planner coordinates it. The owner, the advisors and the lanes add to it, and thermite assign --queue-from copies another run’s untried experiments.

  • The queue stays full of ideas:

    • The teachers put ideas first. Each round’s main product is a portfolio of new opportunities, as many as are distinct and evidenced, each with its mechanism, gain bound, smallest experiment and matched control, stop condition, why it is new and its evidence; each one becomes an experiment in the queue. Rules come second, only when new evidence calls for one. The merged by model drops no distinct idea, and code adds back any it leaves out.
    • Code refuses a rule that freezes the queue’s size or pins an experiment, such as “do not add queue entries” or “retry x-… first”, in the supervisor and the rig, and says why in Live and History. Such a rule of the teachers’, set before, no longer reaches the planner. Only clear words are refused: a rule about a direction or a kind of idea (“do not add more unrolling experiments”), about a project’s own queues (“back off when the submission queue is full”), or that cites an experiment as evidence stands.
    • The teachers read the run’s code and evidence, read only: the champion’s tree, each attempt’s diff and the judge’s public reasons, and why each experiment tried ended as it did. Held-out results and the kit stay out. A research run has no code, so its teachers get none of these tools; the harness refuses a code read of a research run in words, and a read of a repository that is not on the rig says so, never spawn git ENOENT.
    • The teachers read what the run itself found, read only: its sources with their archived text, its claims with the harness’s reasons for each draft, its hypotheses, findings and notes, the report and each lane’s files, a page at a time or by a search of words (list_research, read_source, read_research_file, search_research). A research run’s brief starts with a short summary: the counts, each question’s coverage and the start of the newest report. The teachers are told to read what the run found before they propose. A hill-climb or goal run’s teachers get the same once its workers recorded a source, a claim or a note. Teachers that may fetch any web page get none of these tools, and Live says so once.
    • Ideas kept ready: a campaign’s target queue depth (lanes.queue_target), two for each lane by default, set on the New campaign page and shown on the campaign’s page. A target, not a cap. Below it, the planner is asked to top the queue up, the lanes’ distinct ideas go up next, and the teachers are asked for ideas at once.
    • Lanes propose follow-ups. A worker’s brief asks it to propose each distinct follow-up before it submits or stops.
  • thermite assign --note and thermite assign-payload --note keep a text with the run, shown as Note: … on the run’s page in the knowledge base. --direction, the flag’s old name, still works in v0.1.0 and says on standard error that it is now --note: a direction is now a named line of experiments. The signed command keeps the field’s name, direction.

  • Rules for the planner name the run’s directions. The owner writes them in plain words on the run’s Planner tab, under Tell the planner, where : picks one of the run’s directions, and the planner makes the rules at its next round. An old rule in words, from before rules named directions, is converted to a rule on a named direction at the planner’s next round: it keeps its kind, author and why, shows converted from: and the old words, and is never dropped without a rule in its place.

  • Agents run in Pi, OpenCode v2, Claude Code or the Codex CLI, chosen for each rig. The harness images carry OpenCode and the Codex CLI, each with its license. Claude Code runs under the rig’s own isolation, as the other harnesses do, with its own sandbox off, and gets its sign-in on a file descriptor, never in an environment or a file. An advisor on a Claude subscription runs Claude Code the same way, with none of Claude Code’s own tools, so the images carry no bubblewrap.

  • Each kind of agent can have its own model and effort: the workers, the planner, the teachers, the auditor and the review panel, on Edit rig and over REST and MCP (set_rig_model with agent, and rig_models to read them). Each agent’s model can take a cap on its context window, to compare two models at the same window, and its own compaction point.

  • One name for each agent, on the screens, the pages, the API, MCP and the events, in two groups. The work: the workers and the planner. The advisors: the teachers, the auditor and the review panel. The teachers take one or two models; with two, a merged by model joins their advice into one set (set_rig_model’s second_model and merged_by, and rig_models’ teachers: {models, merged_by}). One teacher works with no merge. An advisor with no model of its own uses the rig default, its advisors file, whose fields are now merged_by and review_panel. The old names combiner and panel are still taken in v0.1.0, by the API, MCP and the advisors file, and the answer says what each is now. A check of the pages refuses the old names.

  • Edit rig’s agents come in two groups, The work and The advisors, each agent with its job on one line. Every line has its own Model, Effort, Runs in and Context: the context window and the compaction point, changed in a small panel under the cell. + second teacher adds a second teacher and a merged by line. The page checks each line as you type, in control’s words, and says each change in a short sentence; control saves nothing when a line is refused. The form works without the page’s script.

  • A line on Edit rig with no model names what its agent uses then, not “rig default”. An advisor’s first Model choice names the model of the rig’s advisors file, such as rig's file: opus-5.5, with the file’s program and window greyed under Runs in and Context; it says none: off when the file has none, and rig's file until the supervisor has reported. The workers’ says rig's settings: <model>, or none: can't run, and Effort’s empty choice is model's default. The supervisor reports what its advisors file sets up for each role: model, program, provider name and kind, window and point, never a key, a token, an address or a path. rig_models and GET /v1/rigs/<rig>/model give it as advisors_file.

  • Each kind of agent also runs in the program of the owner’s choice, on Edit rig and over REST and MCP (set_rig_model’s harness, for any agent): Pi, OpenCode v2, Claude Code or the Codex CLI. A model that a program can’t run is refused in words. A planner with its own model plans in its own program, and reaches its own provider through the harness’s proxy. An advisor in OpenCode or Codex runs in the supervisor in a folder of its own, with none of the program’s own tools: only the search of the run’s past research and the kit’s MCP tools, and the model through a proxy for the call that adds the key. The supervisor image now carries OpenCode and the Codex CLI with their licenses, and its license label is MIT AND Apache-2.0.

  • A conversation that grows never ends a trial, and no agent is told to submit early because of it. Near the model’s context window (by default 80% of it; compact_at, from 50 to 95, sets the share with each agent’s model over REST and MCP) the conversation is compacted and the agent goes on. In Pi, the harness first keeps the whole conversation in the run’s research folder (compactions/), where the agent can read it and the run keeps it, then asks the same model for a summary that names it. OpenCode v2, Codex and Claude Code compact on their own, with the window and the point Thermite sets in their settings, and their conversation is kept there too, with a summary that names it: Claude Code’s PreCompact hook copies its transcript and adds the line to the compaction’s instructions; Codex’s compaction prompt is Thermite’s own, and Thermite reads Codex’s record of the thread; Thermite’s OpenCode plugin keeps the session in OpenCode’s compaction hook, adds the line, and says when each compaction ends. Each compaction is a context_compacted event, in History and Live, with the summary.

  • The advisors compact too. An advisor’s call in Pi compacts as the planner’s does, and the supervisor keeps the conversation with its records of the round, never where the run’s workers read; an advisor in Claude Code, OpenCode or Codex gets the window and the point in that program’s settings, and the supervisor keeps its conversation the same way. An advisor’s package (a brief, an audit’s or a review’s) is cut to fit its window, and its input says so.

  • A campaign can set its own context window and compaction point for all its runs’ agents: the contract’s optional agents_context, set with POST /v1/campaigns/<campaign>/context, the MCP tool set_campaign_context, thermite campaign context, a copy’s agents_context (copy_campaign, thermite campaign copy --context-window --compact-at) or thermite assign --context-window --compact-at. Each agent’s window is the lower of the campaign’s and its own. Start a run says what applies to each agent, and refuses a window above what a provider lists for one of the rig’s agents’ models. On the dashboard, Agents’ context sets it on the campaign’s page, with what applies on each rig, in New campaign’s step 4 and in the Copy panel; Start a run’s panel says what the chosen rig’s workers get, and keeps Start the run off on a rig where a run would be refused.

  • On a CUDA rig, the agents find CUDA’s tools by name in every harness.

  • A claim’s quote names its source by the id fetch returned, or by the address or the research folder’s file name of exactly one archived source. The quote is then checked against that source word for word, as by its id, and the claim keeps what the worker cited. Any other source is a problem that says what it could be: fetch it first, the ids an address was archived as, or an archived address written another way. A number from a heading of the claim, such as Question 0:, is still refused, and its problem says to give the question’s number in question. The claim_added event carries the quotes’ sources and a draft’s problems, and the run’s History shows each claim, with a draft’s reasons.

  • Field notes from the workers, the planner and the advisors. Thermite keeps its own knowledge base, with a folder for each campaign, and the owner grants which campaigns may read which folders. Workers, the planner and the advisors can search what they may read.

  • Each goal’s real check, under How it is checked: in a run’s Goals block, on a campaign’s page, and on the page of a campaign saved with no run yet. It shows the kit’s verifier or the metric threshold, the cases it runs on with the parameters the kit runs each with, each case’s time limit, and the held-out cases by name only, so a contract’s goal text that drifted from the kit shows. A goal run’s rig reports it when the run starts (the goal_checks event, and goal_checks in get_run and GET /v1/rigs/<rig>/view); control also reads it from a kit it keeps, and says when it can read neither.

  • A confirmation that fails on a held-out case says how, safely. The judge records each held-out case’s name and its kind of failure, from a closed list (threshold_missed, timed_out, crashed, check_failed, non_finite, no_cell), and the measured value against its threshold where the check gives one: a threshold goal’s mean and interval, a hill climb’s ratio to the champion. The owner sees them on the attempt’s page under Held-out checks, in History, in get_run and GET /v1/rigs/<rig>/view, and in the judge’s new held_out_checked event. The agents see only that a held-out check failed and its kind, such as confirmation failed: a held-out check timed out: in the verdict’s reasons, the planner’s and the teachers’ evidence and a worker’s status. No one gets a held-out case’s inputs, its output or the check’s words. A hill climb’s failed confirmation now shows the measured cases’ figures, where it showed none before. A hill climb’s contract that names a cell in both cells and held_out_cells is refused in words: the cell would be measured at screening. Control’s agent token reads held-out checks as the agents do: in get_run and recent_events, their kinds and words, with no case, value, threshold or time limit, and a goal’s checks without its held-out cases. An error in the judge’s own work while held-out cases run reaches the agents only as a held-out check could not finish (an infrastructure failure); the rig’s log keeps it. A run’s start refuses a kit whose case, among those the contract runs, lacks a rate metric’s work parameter above zero, and New campaign scores a rate only when every case, hidden ones too, has its Work done.

  • A threshold goal whose cells mix measured and held-out cases is screened on its measured cases; before, it could never be met, for lack of a value from the held-out ones.

  • A goal report keeps what it measured. A goal checked by a kit verifier, met or not, gives each measured case’s metrics, as a threshold goal gives its mean and range: in its reasons (verifier quality passed on wiki: wiki: ppl_ratio 1.00213), as values in the goal_report event, get_run and GET /v1/rigs/<rig>/view, and under each goal in the run’s Goals block, which now shows a met goal’s line too. A case whose output the check refused gives the numbers its check reported. The planner and each worker’s brief read them; a held-out case’s values are kept nowhere.

  • While the judge checks a second time a champion that met every required goal, a goal run starts no new round or trial: a trial at work goes on, the run’s page and Live say why, and the lanes go on if the check does not agree or fails (goals_confirming in get_run). A second check queued for an earlier champion now counts as the first check of the new one, so a run never closes on one check.

  • The owner closes a run by hand, for good: Close run, under ⋯ on a running or paused run’s page, with a reason and the words close <run> typed to confirm, and close_run {run, reason} over REST, thermite command and MCP with the owner’s token (not the operator’s or an agent’s). The run closes as a run that met its goals does, with the owner’s reason: its trials stop, nothing new starts, its jobs waiting for the judge are cancelled and their changes end as not_judged (so a rig’s page no longer counts them as queued; at start the harness does the same for runs closed before), a research run gets its final report, and its results stay and reach the next export. A closed run can’t be resumed or paused again, and closing it twice is refused in words. A job the judge was measuring when a run closed no longer replaces the owner’s reason with its own.

  • Nothing waits for the owner in a closed run, however it closed. A change waiting for approval or an audit, a review the review panel passed and a “not possible” proposal leave Needs you and every count of it: the sidebar, the status bar, the Campaigns page and the run’s own pages. The rig refuses approve_promotion, decline_promotion, accept_review and conclude for a closed run in words, such as run <run> is closed: none of its changes can become the champion, the auditor gets none of its audits, and none of its changes becomes the champion by a decision. Such a change reads Run closed, and its page says why, with no buttons. A closed run’s pending_promotions is empty over REST and MCP.

  • Before a goal run closes, its champion’s second check also runs the held-out cases of every goal it meets; before, an attempt’s confirmation ran them only for the goals it newly met, so a goal met long before never ran them. The run closes only if each passes. When one fails, the goal is not met again (unmet, not confirmed: <kinds>; on the measured cells: …), the report has complete: false and not_confirmed, and the lanes go on: the planner and the workers read which goal and its kinds of failure, never which case. The owner sees each case and its kind under the run’s goals (Held-out checks of champion #n), in History, as champion_held_out in get_run and GET /v1/rigs/<rig>/view, and in a held_out_checked event on the run with champion_seq. The rig’s held_out table keeps a champion’s record by run and champion, with no attempt (ledger schema version 20).

  • A change the static checks refuse says why everywhere it is looked at, not only to the trial that submitted it: its event attempt_recorded (the first 20 violations), the attempt’s page under Refused before building, the run’s History, get_run and GET /v1/rigs/<rig>/view, the knowledge base’s attempt item, and the planner’s and the teachers’ evidence (the first 5). A worker’s status of an attempt now answers for any attempt of its campaign on the rig, such as another lane’s or an earlier run’s, with every violation; before, it answered 404 for any attempt but its own lane’s in the same run.

  • A kit’s cell can set its own time limit: {"params": {…}, "timeout_s": 1800} in cells replaces run.timeout_s for that cell’s runs only, so one slow cell no longer sets the limit for all of them. A cell of parameters alone keeps run.timeout_s, so older kits work as they are. Every time limit in a kit, the cells’, the build’s, the run’s and the check’s, is at most 86400 seconds (a day), the longest the judge runs anything; the kit check refuses a longer one in words, where before such a kit’s runs waited for ever. A request the judge’s broker refuses is answered under its own id, so the call that sent it fails at once.

  • MCP servers for the agents. The owner adds a server once in Settings, with an OAuth sign-in, a key, a client ID and secret, or none; control seals the secret and reads the server’s tools. A campaign’s kit names its servers, the agents that get each, and, when narrowed, their tools. Workers and the planner call them through the rig’s harness, the advisors through its supervisor; no agent sees a secret, and a server that fails does not stop a run. Rigs on RunPod get their servers through control’s connection to the pod. A run’s first plan and trial wait up to 2 minutes for the rig to hold the servers its kit gives the workers and the planner, so a trial no longer starts just before control hands them over and works its whole length without them; a server still missing then, or one control says it can’t give, is named in Live and History (mcp_servers_wait), and the run goes on without it.

  • The model provider decides when agents can work. A provider’s error is sorted by its kind: a rate limit waits for the time the provider gives (Retry-After, its reset headers or its words) and goes on in the same conversation; a 5xx or a broken connection waits a growing pause and goes on; no credits, a refused key or a model the provider does not serve on the key stops the run’s agents and shows the run in Needs you (Agents stopped) with the provider’s words. A 5xx or a refused request that never clears, with no model call that worked for 30 minutes, stops them the same way (no model call worked for 30 minutes): pacing, so that it shows, not a cap; when a check passes, the lane whose trial ended starts afresh. A 429 in a quota’s words is a rate limit when it says RESOURCE_EXHAUSTED or gives a time to try again; OpenAI’s insufficient_quota and a 402 are no credits. Only the provider’s own answers are sorted: a local failure, such as a workspace that could not be set up or a program that exited, never stops the agents, whatever its words. No trial or plan starts while the workers’ model check fails. The model is checked again after a minute, then less often, and at once with Check the model now; a passing check or another model ends the stop. A research run’s clock stops while its agents cannot run. Pi, OpenCode v2, Claude Code and Codex answer alike; the harness’s model proxy tells the kind from the provider’s answer.

  • A model call whose stream goes silent no longer holds a trial forever. When the provider sends no data for 600 s (THERMITE_MODEL_STALL_SECONDS; 0 turns it off), while the call waits for its answer or for the answer’s next part, the call is broken off as the provider’s passing fault: the trial waits and goes on in the same conversation, and Live says the model's stream went silent for 600 s: retried …. Keep-alive comment lines and pings are not data, so a gateway that keeps the connection open while its model sends nothing is caught too. It covers every Pi call (workers, research workers, the planner, compactions, model checks and the advisors) and the model proxy’s calls for OpenCode and Codex, which get a 504 or a broken connection. It detects silence, not a cap: a call that keeps sending data is never cut. Silent calls that keep coming stop the run’s agents after 30 minutes, as other passing faults do.

  • No hidden limits on a trial: no trial length (it was 20 minutes), no 8,192-token reply cap (a route carries the model’s maximum output from the provider’s list, else none is sent; Anthropic’s API gets 32,000), no 6-failures or 5-cut-offs rule, no 30-minute cool-down, no fixed number of nudges (a worker is told to go on while it keeps working), no time limit on a planner call (it was 180 s, or 300 s), no 3-hour cap on a round, and no 10-minute limit on a dev run’s wait. The advisors’ calls from control have no turn, time or reply limit either (24 turns, 900 s, 8,192 tokens). Each is now a setting with no default: the contract’s lanes.limits.trial_minutes, failures_in_a_row, cutoffs_in_a_row and nudges; Edit rig’s One trial lasts (minutes) and THERMITE_TRIAL_MINUTES; the advisors file’s max_tokens, max_turns and timeout_s. A rig’s settings file from before keeps no 20-minute trial: it was the old default. A research run with no max_duration runs until someone stops it: min_duration is a floor, never a stop.

  • Update to these images and update_rig_images drain a rig at work first: it starts no new trial, plan or judge job, and its pods restart once its running trials and the judge’s job under way end. A plan that ends during the drain records its round and starts no trial; a judge job whose lease ran out is not waited for, and is taken back to the queue while draining. The rig’s page shows what it waits for, with Update now, without waiting and Call the update off; update_rig_images takes wait: false. A stop of the harness records its running trials as interrupted, with their workers’ counters. Each lane’s build cache now survives a restart: the harness’s start keeps it while it empties the rest of /scratch.

  • An experiment whose trial never really tried it (interrupted by a pause, a reset or a restart before it submitted, or ended on the provider’s error) goes back to the top of the queue as the same experiment, with how many times and why (experiment_returned), not as a copy. A trial stopped after it submitted keeps its experiment done with that change, even when its worker could not report it. Every tried experiment keeps how its trial ended in words, which the planner’s and the teachers’ briefs show beside its result. A trial interrupted by a restart keeps the turns and tokens its worker last reported.

  • The planner’s tool schema and prompt state every limit its answer is held to, and a focus may be 4,000 characters. Each try that gives no plan is recorded with its real cause in the round’s epoch_started (causes) and in History: timeout, text only, cut off, the provider’s error, cancelled, or refused with which rules. A research run’s rounds count as research rounds, never as planner fallbacks. The fallback plan skips a direction whose goals the champion already meets.

  • Hands-off by default: a win the judge confirms becomes the champion with no step of the owner’s, in every mode. When the campaign calls for an audit and the rig has an auditor, the auditor’s clear is the last step, also for a gain above the audit trigger or a change with declared flaws; its block stops the change and says why. A rig with no auditor skips the audit and records it (audit_skipped); its supervisor tells the harness which advisors it has. An audit the auditor fails to answer five times goes to the owner with the error; each audit counts its own tries, so a new audit of the same change starts afresh, and a hand-off that does not reach the harness is sent again every 30 minutes until it does. A review panel’s pass meets its goal, and a “not possible” proposal ends its goal campaign. Ask me first about, on the New campaign page and in Start a run (ask_owner over REST, MCP and in assign_campaign; --ask on the command line), makes a gain above a percent, declared flaws, reviews or “not possible” endings wait for the owner; all are off by default. A run that asked for the owner’s approval of every change keeps it. Needs you no longer lists an audit that the rig’s auditor will decide.

  • The auditor sees what the judge checked before its verdict (checked in its package): in a hill climb, the cells and confirmation blocks, that the kit’s check passed on every run of the change, each guard’s allowances and result against the champion and the starting point, and the hard constraints that held; in a goal campaign, each goal’s status. It reads the change’s declared flaws against them, so a check the agent skipped but the judge ran no longer sends the change to the owner for want of evidence. Held-out results stay out of it.

  • A goal checked by a kit verifier keeps, as each measured case’s values, the numbers the kit’s check printed (a ratio, both sides of a comparison, a count), not only the kit’s metrics. A kit whose cases check different things can’t declare those as metrics, so its met goals kept no numbers before. A held-out case still keeps none.

  • A research run ends when its questions are answered (since goal 117, by the review panel’s decisions on each item, or by the progress rule: both below). Its minimum duration is its time to collect; after it, at a digest, the harness asks the rig’s review panel whether every question is answered, when every question has a verified claim, the campaign’s floor of verified claims is met (min_verified_claims, Verified claims before the check on the New campaign page; no floor by default), no review waits and the evidence is new since the last review. Each reviewer gets the run’s verified claims, findings and hypotheses, with no lane or model named, answers each question with the claims it cites or says what is missing, and gives each hypothesis a verdict. Code checks every answer as it checks a finding: every cited claim a verified claim of the run, every number in those claims. A pass of every reviewer, at least two, is accepted hands-off, or waits in Needs you when the run asks about reviews; then, once its running trials end, the run writes its final report, with Answers and Hypotheses: the review panel’s verdicts, and closes with every question is answered, which the dashboard shows as Completed. Gaps go in the run’s journal and the next workers’ briefs, and the run goes on; it is not asked again on the same evidence. A run’s Overview says where the check stands, and each digest has a line on it. max_duration still ends a run when its time is up. The review panel now reads a research run’s messages too. The harness gives research reviews only to a supervisor that asks for them (GET /v1/reviews/pending?kinds=goal,research), so an older supervisor gets goal reviews alone.

  • An answered research run ends with a research paper. Once the review panel’s pass is accepted, the run starts no new trial and waits for its paper: the supervisor’s final writer, with the model of the rig’s first teacher, writes one whole paper in Simplified Technical English (title, summary, questions, method, a result for each question, each hypothesis’s verdict, the limits of the evidence and the next experiments), citing verified claims as [c-ID], and code appends the references. It reads the run’s research with the teachers’ read-only tools, and checks its drafts with check_paper (the harness’s checks) and ste_check. The harness accepts the paper only when every cited claim is verified, every number is in the claims its paragraph cites or in the run’s figures, every question has its result, every verdict is one the review panel gave, and the STE checker, which the supervisor runs itself on the posted text, found no error. Thermite ships no STE checker: a deployment gives one with THERMITE_STE_CHECKER, and its guide goes in the writer’s prompt; without one, the paper is accepted as not checked. A refused paper is written again, with its problems, with no limit on tries. The accepted paper is paper.md in the run’s research folder and leads the final report, before the review panel’s answers and the corpus; its next experiments become experiments of the campaign’s folder in the knowledge base, which Start a run takes into a goal or hill-climb run’s queue. A rig with no final writer closes the run with the answers alone and says so. The run’s Answer check and brief say when the writer is at work and when the paper is in, and the run’s page links paper.md. Events: research_paper_requested, research_paper_refused, research_paper_accepted.

  • A research run keeps an answer card for each question: its current answer in a few paragraphs, a certainty (high, moderate, low, insufficient or none), the verified claims it cites, and its open gaps, each a gap in the evidence or a measurement for a later goal or hill-climb run. The harness lists the questions whose evidence changed since their card (GET /v1/research/cards/pending, with each card, its new verified claims, its rubric and the problems of its last refused card) and checks each card posted to it (POST /v1/research/cards/<run>): every cited claim verified and of its question (or borrowed with why), every number in the claims it cites, the certainty one of the scale’s words, the gaps classified, the answer within 6,000 characters. A card that fails is not kept, and its problems go back with the next request. Each card is kept as a version (events card_written, card_refused). Workers read the cards in their brief (answer, certainty and evidence gaps) and in cards/q<index>.md of the research folder, which INDEX.md lists; every report opens with Answers, each question’s card, and the review panel’s answers are now The review panel’s answers; the teachers’ brief carries the cards, and read_research_file reads one whole.

  • The supervisor’s synthesizer writes the answer cards: an advisor with the model of the rig’s first teacher, as the final writer has. At each round, for each running research run with a question whose evidence changed, it rewrites each such card from the card and its new verified claims, judged against the question’s rubric, rates its certainty, and sorts each gap into one a source could close and one only a measurement could settle, reading more with the teachers’ research tools. In Pi, an answer missing a card or breaking a limit goes back once within the call; the harness checks the rest. A failed call, a refused card or a question left out is tried again at the next round, with no limit. Its model usage counts as the new agent synthesizer (Synthesizer in model usage), in rounds named cards <time>. Control’s run view (get_run) carries each question’s newest card (cards) and refusal (card_refusals), and the run’s History says each card kept or refused.

  • A research run’s Research questions block names each question beside its count of verified claims. Before, its bars had no words: control read the questions’ words under a name the rig never sent. It also says what the bars measure, and what the numbers with no source are.

  • A research paper is written from the answer cards (goal 117). The final writer’s package gives each question its card, the claims the card cites with their quotes, the review panel’s decision with the answers behind it and the dissent, and what only a measurement could settle; the hypotheses that need a verdict; and the hypotheses the run proposed, which need none (proposed). A package longer than 200,000 characters is shortened in steps it states (shortened), each list counting what it left out. The harness now also refuses a results section without its certainty line (Certainty: moderate, insufficient or open) or with one that is not the review panel’s decision, a results section that cites another question’s evidence, and a proposed hypothesis given a verdict. Hypotheses are a table of verdicts, then Proposed by the run; a ### Hypothesis section with a Verdict: line still holds. Code appends a Review panel part before the references: each item’s decision, its review, how many reviewers gave it and each dissenting reviewer’s verdict and reasons. Every cut of an advisor’s prompt or of a tool’s answer to an advisor is now said; before, a package over the prompt’s fixed limit lost its end without a word.

  • A research run’s page calls its queue Tasks, not Experiments: each item is a research task (a question, a gap and a kind of task), and the tab says so (Add a task, Kind of task, Task at the head of Tried). A hill climb’s and a goal run’s page keep Experiments. “Experiment” in a research run now means only a next experiment, a measurement the run hands on.

  • A research paper’s next experiments are tagged by the run that takes them (goal 119): hill_climb, an improvement a hill climb measures against its champion, with the metric it should move; or goal, an outcome a goal campaign proves, with how a check would know it is reached. The paper’s Next experiments part has a section for each kind (### For a hill climb, ### For a goal campaign), and the harness refuses a next experiment with no kind or measure, or a missing section, in words. The tag goes with them: the knowledge base (sub and the item’s first lines), the run’s research folder and export (next-experiments.json), get_run (next_for) and History.

  • A research paper reads as a normal paper. The final writer is asked for the parts of a usual research paper, in Simplified Technical English: an abstract, an introduction, related work when the sources describe it, the method, the results, a discussion, the limitations, the future work and a conclusion. It chooses the parts and their words; code no longer checks them, nor asks for a ### Question N section with a Certainty: line for each question or a table of verdicts. Code checks instead that every question with a verified claim is answered somewhere, by a citation of its evidence, and that a verdict the paper gives a hypothesis is the review panel’s. On acceptance, each [c-ID] citation becomes the numbers of its claims’ sources, such as [1] or [1, 3], and code appends References (each source numbered, with its title, site, archive day and address), Appendix A: The review panel’s decisions (how the run ended, each question’s decision and certainty, each hypothesis’s verdict, each dissent, and the hypotheses the run proposed) and Appendix B: The claims cited (each claim with its reference numbers). The accepted event keeps each cited claim’s numbers (citations). The package no longer has certainty_line. In the final report, the run’s records after the paper are headed Supplementary material, not Appendix.

  • Research questions are numbered the same way everywhere (goal 117). Every field an agent writes, such as a claim’s question, and the records and file names take the index, from 0. Text people read counts from 1, and what agents read too names both, Question 1 (index 0): a research worker’s brief, INDEX.md, REPORT.md, a claim’s file, the research plan’s task titles, the review panel’s gaps in a worker’s brief and a claim’s item in the knowledge base. Before, the brief and INDEX.md counted from 0 and the report from 1.

  • A research run’s corroboration counts sites, not hosts (goal 117): a claim is independently sourced when its quotes come from enough registrable domains, the part of an address one owner registers, so docs.example.com and blog.example.com count as one, example.com. A short table of public suffixes of more than one label, from the Public Suffix List (co.uk, ac.uk, com.au, co.jp, github.io and others), is kept in code. The run’s count of domains, the review panel’s package and the teachers’ reads count the same way. Before, two pages of one site could corroborate each other.

  • A research tool’s answer that is too long is never cut without a word (goal 117). Reads give at most 10,000 characters, so that they fit the 14,000-character answer whole (read_source 6,000 by default; a Pi worker’s read_file was 20,000, cut at 14,000), and say where to read on with next_offset. fetch says the same of the page’s text, and how many of its links it left out. search_corpus, a Pi worker’s list_files and search_files give a page that fits, with next_skip for the next (search_corpus --skip, and skip in Pi). Any other answer too long ends … [cut: this answer has <n> characters, and only its first 13800 are shown]. It holds for Pi’s tools and for the thermite-trial research commands of OpenCode. Before, answers were cut at 14,000 characters with nothing said.

  • A note is kept once (goal 117). A research note (add_note) that repeats one of the run’s last 200 research notes word for word, case, punctuation and spaces aside, is refused in words with the note it repeats, and so is a research worker’s field note (Pi’s note tool, thermite-trial note) that repeats one of the run’s last 200 field notes. Before, one trial wrote the same two-word note thousands of times.

  • A research run’s hypotheses are bounded (goal 117). The author’s, from the brief, are its obligations; the workers’ go to a pool, each with the question it is about (propose_hypothesis --question N, an index from 0). A hypothesis whose words repeat a live one of the run, case, punctuation and spaces aside, is refused with the one it repeats, and so is an empty one or a question the run does not have. The new tool revise_hypothesis (Pi, and thermite-trial research revise_hypothesis in OpenCode) changes a pool hypothesis’s words, question or supporting claims (verified claims of the run only); the author’s change only with the brief, and a revision whose words repeat another live hypothesis is merged into it. Only the author’s hypotheses and promoted ones need a verdict (the owner promotes one on the run’s Hypotheses tab): the review panel’s package holds only those, so the pool never makes a review fail. The worker’s brief, INDEX.md, hypotheses/, the report and the teachers’ reads show each hypothesis’s status and question, the author’s first. New events hypothesis_revised and hypothesis_merged; hypothesis_added carries question and status; the knowledge base follows all three. Before, the workers of one long run proposed over 200 hypotheses, many of them twins, and each became a verdict every reviewer had to give.

  • A research run’s hypothesis changes status by command (goal 117): the new signed command hypothesis_status {run, id, status, reason} promotes one of the pool (promoted), so the review panel must give it a verdict, returns a promoted one to the pool (pool), or retires one (retired), each with why. It is the owner’s: the harness refuses it from any other issuer. Over the REST API it goes to POST /v1/commands. The author’s hypotheses, a merged one and a retired one whose words now repeat a live hypothesis are refused in words, and control refuses it for a run that is not a research run before it signs anything. Each change is the event hypothesis_status, {hypothesis, status, from, by, reason, question, text}, which the knowledge base follows.

  • Measurements stay out of a research run’s queue (goal 117). A worker’s idea or a teacher’s item whose words ask for a measurement (run a benchmark, profile the …, time each …, measure the …, an A/B test, running it on the card, on the box or on the hardware, on-box, run the litmus tests, a dev-run, or a measured experiment’s matched control and expected gain) is refused in words, with the advice to record it as a next experiment in a report section or a note. A task about what sources report is not. The workers’ brief gets only the review panel’s gaps in the evidence: a gap that asks for a measurement is counted there as a next experiment, and an answer that failed code’s check or a hypothesis with no verdict, the reviewers’ side, stays out. Before, 61 of the 79 tasks one long run tried asked for measurements, and the brief told workers to “work first” on gaps no research could close.

  • A research run’s work is driven by its answer cards’ gaps (goal 117). Without a model, the gap-driven plan gives each lane its task: the owner’s queued tasks first, as before; otherwise the question its card weighs for (how open its certainty is, plus its open evidence gaps; a question with no card as the most open), with a floor (a question with open evidence gaps and no task in the round gets one first) and a ceiling (no question takes more than half of the run’s last 2 × lanes tasks while another has open evidence gaps). A question the review panel decided, or a saturated one (its last 4 tasks added no verified claim from a new site and changed no card), gets none, unless every open question is saturated. The lane takes that question’s first queued task, whoever added it, or a new task whose title names the question and whose details start with the gap it closes (Evidence gap to close: …). Each round’s record says where each question stands. Before, the plan only filled an empty queue, least-covered question first, and the teachers’ tasks for one question could take every lane: one long run did 42% of its work on one question and 2.4% on another.

  • A research run has a model planner (goals 047 and 117). With the planner on, each research round starts with one planner call, through the same machinery as a hill climb’s (Pi, or OpenCode, Claude Code or Codex; compaction; usage; the cause of each try that gave no plan), from a research context: each question with its rubric, its answer card (answer, certainty, open evidence gaps), the review panel’s decision that stands, its share of the last tasks, what its last tasks added and whether it is saturated; the queue; the hypotheses that need a verdict and the pool’s best supported; the rules; the owner’s messages; field notes; and what a research run can’t do. Each task it gives names its question, one evidence gap it closes, the kind of source to read, a title and details. Code refuses in words a question the run doesn’t have, a decided or saturated question (unless every open question is saturated), a gap or words that ask for a measurement, a kind of task an Avoid rule names, and a queued task or idea that names no question; a refused answer goes back once, then the gap-driven plan sets the round. It may promote up to 3 pool hypotheses a round and 10 a run, so they need a verdict, and retire repeats, each with why, through the same step as hypothesis_status. The owner can now message a research run’s planner (to: planner, and all reaches it too); it reads them when its plan is used. A research run’s planner takes no MCP server: control’s refusal says so in place of “A research run has no planner”. On the dashboard, a research run’s Planner tab has Tell the planner (plain words, no directions to pick: the planner weighs the message in its tasks and makes no rule of it), its The plan card and round counts say whether the model or the gap-driven plan set each round, Live has its Planner filter and a message to everyone working names the planner too, and History says The gap-driven plan set round N (the planner is off).

  • A research run’s teachers get a research prompt and portfolio (goal 117): each research task names the question it serves (question), one evidence gap it closes (gap), the kind of source to read (source_kind), a title, details and why, with its citations and its kind of task when it has them, and no gain bound, control or measured experiment. It is queued with its question, its title labelled Question N (index K): … and its details starting with Evidence gap to close: …. The harness refuses a research run’s teacher item that names no question, as it refuses one that asks for a measurement. Their brief has question_state: where each question stands for the plan. A hill climb’s and a goal campaign’s teachers keep their opportunities.

  • A research task keeps its question and what its trial added (goal 117). The research plan’s tasks, and a worker’s idea (thermite-trial idea --question N, the idea tool’s question) or a teacher’s item that names one, keep the question as an index; so does a title labelled Question 2 (index 1); a question the run does not have is refused in words. When a research trial ends, its task keeps its yield: the verified claims its lane recorded, by question, with how many brought a site new to the question; the sites new to the run; its supported findings; and card changes (0 until answer cards come). The queue the planner and the teachers read shows both, experiment_added carries question, experiment_finished carries yield, and the knowledge base’s item for the task says what it added.

  • Control keeps a research run’s items (goals 116 and 117), from the rig’s events: its hypotheses with their status and the newest change of it (hypothesis_status, with who and why), each reviewer’s answer on each item of a review as code checked it (review_items), the review panel’s decision on each item with its dissent (item_decided), the decisions that still stand (a question’s until its card changes), each review’s times, what it saw and its counts (review_panel’s decided and open, and tries when the rig says), the progress rule’s state at each digest (research_progress), and each research task’s question and yield. MCP’s get_run carries them (hypotheses, decisions, standing, progress, and each review’s requested_at, posted_at, saw, reviewers, decided, counts and tries), run_queue gives each task’s question and yield, and the run’s History says each in words, such as In review 2, the review panel decided Question 2: answered, certainty moderate (1 reviewer dissents). A new owner command, hypothesis_status, promotes a research run’s hypothesis so the review panel must give it a verdict, returns it to the pool or retires it, with why; MCP’s issue_command takes it from the owner’s token only.

  • A research run’s page shows each question (goal 117). Its Overview’s Research questions starts with where the end stands (how many questions are answered, at the best available and open, and how many of those are saturated; the Answer check line; the progress rule as a bar, Without an improvement, against the quarter of the running time it needs; the hypotheses and reviews, with links), then each question: the review panel’s decision that stands (or to review again once its card changed, or no decision yet) with its dissent, saturated, its answer card’s answer with more, its open gaps in the evidence, its next experiments (the gaps only a measurement can settle), a refused card, its share of the last 20 research tasks, what its tasks added, and its bar of verified claims. It replaces the bars and the bare Answer check line, and keeps their information.

  • A Reviews tab on a research run’s page and on its campaign’s page, where it lists the reviews of every run of the campaign, by run (goal 116). Each review, newest first, opens to when it was asked and on what, when it was posted and by how many reviewers, its tries, its outcome with the owner’s decision and note, its model use and cost, the decision on each item with its certainty, dissent and the decision before it, each reviewer’s verdict, reasons, cited claims and the problems code found, the owner’s messages to the review panel with the answers, and the supervisor’s lines for it in Live; beside the list, each item’s decisions across the reviews. A review from a harness before item reviews shows how many reviewers found each question answered, and the gaps. Needs you’s research review links to it.

  • A Hypotheses tab on a research run’s page (goal 117): the author’s and promoted hypotheses, which need a verdict, the pool by question, best supported first, and the merged and retired ones, each with its question, the verdict that stands, who proposed it and the newest change of its status with why. Promote, Return to pool and Retire, each with an info icon and a required Why, send hypothesis_status, and the page follows the command to the rig’s answer.

  • A research run’s Experiments tab shows each task’s question, and under Tried what its trial added, such as finished: 3 verified claims, 2 from sites new to its question, 1 finding; one that added nothing is grey.

  • A review try the supervisor could not post is kept with the review, with why (goal 116): the supervisor sends it to the harness (POST /v1/reviews/<id>/tries), the run’s Reviews tab and get_run list each failed try, and History says it.

  • The review panel decides a research run’s items, and its decisions stand (goal 117). The items are the questions, each with its answer card, and the required hypotheses (the author’s and promoted ones). After the minimum, at a digest, the harness asks the review panel when every question has an answer card, no review waits, and some item has no decision that stands: a question whose card is newer than its decision, or a required hypothesis with none. A review is marked with the items it saw (items: with each card’s version and the required hypotheses), and none is asked when nothing changed. Its package holds every question’s newest card with the claims it cites (their quotes cut to 400 characters), its rubric and its standing decision, every required hypothesis with its claims and decision, which items to decide, the run’s figures and the criteria; it is shortened, and says so, from the items that stand first. Each reviewer answers each item to decide: a question answered (certainty high, moderate or low), insufficient (the best answer sources allow, with what only a measurement could settle as next experiments) or open (with its evidence gaps); a hypothesis supported, refuted or undecided. Code checks each answer as before. Each item is decided by the verdict most of the answers that pass give (an answered question’s certainty is their median); a tie, or no answer that passed, is open (a hypothesis: undecided), and the other verdicts are kept as dissent. Every checked answer and every decision is kept in the ledger, and is the event review_items (one for each reviewer) or item_decided (one for each item); review_panel also gives each question’s decision, how many items it decided (decided) and the questions still open (open). The review passes when every question has an end (answered or insufficient) and every required hypothesis a decision. The reviewers of a review now work at once, in parallel. The journal has each review’s decisions; the workers’ brief gets only the evidence gaps of the questions that stand open; the final report’s appendix is The review panel’s decisions, with the dissent. Before, every reviewer had to find every question answered in one review, and a review that failed on one question decided nothing.

  • A research run’s hypothesis that the review panel leaves undecided with no claim for or against it has no end (goal 117): the plan gives it a task to find evidence, one at a time and at most 4, and each one done shows the panel the hypothesis again. An undecided with claims behind it is an end, as before. A finding that repeats a draft word for word is refused in words, whatever its problems, with the draft it repeats; it is recorded only once it is supported, and a draft’s answer says how to fix it.

  • The progress rule (goal 117): at each digest after the minimum, research_progress says whether every open question is saturated (its last 4 tasks added no verified claim from a new site, and its card’s certainty did not rise: a card rewritten in other words at the same certainty, as the synthesizer does each round, is no progress) and how long the run has gone without an improved decision or a more certain card. When both hold for a quarter of the running time (and of the minimum), no review waits and some item is open, the run ends with its open items stated (research_stopped): the final writer writes its paper, whose package says which questions stayed open (end, stayed_open), and the run closes with research stopped adding: its open questions are saturated. No time cap is added: the rule holds only once research stopped adding. The paper’s verdicts are the review panel’s decisions, and a question with no verified claim needs no citation in its results.

  • A subscription’s usage on Providers (goal 151): a Claude or ChatGPT subscription’s row shows its windows on its closed line (5-hour 42%, Weekly 81%, marked from 80%), and in full when open, with when each resets. A ChatGPT subscription’s come from ChatGPT’s usage endpoint, which control asks with the sign-in at most every 5 minutes; a refusal is said in words, with a link to ChatGPT’s Settings › Usage. The harness reads both forms of Claude Code’s rate-limit event (all windows in unifiedWindows, or the one window the Claude Agent SDK’s type names) and keeps each window until a later event changes it, across trials too. OpenAI refuses its usage endpoint for a sign-in for open-source apps (HTTP 401), so a ChatGPT subscription’s row shows the refusal and the link. The MCP tool providers gives each subscription’s usage.
  • A run’s trial length can change while it runs: a trial_minutes rule (redirect over the command line, REST or MCP, value in minutes from 1 to 1440) comes before the contract’s lanes.limits.trial_minutes and the rig’s setting, from the next trial on. The Planner tab shows it as Trials last <n> minutes. Before, a campaign’s trial length was fixed for its runs.
  • A run’s lanes can keep working while the judge works: with the contract’s lanes.keep_working, or a run’s keep_working rule (on or off, redirect over the command line, REST or MCP), a lane’s next trial starts as soon as its last one ends and a round ends when its trials end, while the judge works through their changes, at most budget.max_open_attempts of them. Off by default: lanes wait for the judge’s verdicts, as before.
  • A round no longer holds a lane behind another lane’s trial (goal 136). It ends once every free lane has run its trials, and a lane still on a long trial joins the next round when its trial ends. A change waiting for the judge still holds the round, unless the run keeps working. In a research run, a task the round holds for a lane still on an earlier round’s trial does not count as its question’s task in the round, so a free lane can take that question too. Before, one long trial left every other lane idle until it ended.
  • A research worker’s fetch refused for a reason that lasts (too large, outside the campaign’s sites, robots.txt, a private address) is answered at once when the run asks for the same URL again, with no new download: the answer says it was refused before, how many times it was asked for, and to look for the same text elsewhere (goal 136). Before, a worker could fetch the same large PDF every few minutes for hours. A page that crossed fetch_policy.max_bytes in its last part is refused, not returned cut short.
  • A research run’s clock stops while its rig drains before an image update, as it does while the agents are stopped: its durations and its progress rule leave the drain out (goal 136). As it starts again, the harness records the drain’s end (rig_drain, drain: false, the harness started again).
  • A draining rig’s page says what the drain waits for (goal 136). A card at the top says since when it drains, who asked and why, and what it waits for, with End the drain. Under At work, each trial it waits for has a row under its lane: how long it has run, what it last did, a page it keeps asking for that the run refused (repeats a refused request), its work so far, and Stop this trial…. The new signed command stop_trial, the owner’s and an operator’s, ends one trial as interrupted and puts its experiment back at the top of the queue. While a rig drains, its report lists each open trial (work.open), and work.draining says who asked (by). Before, the page said only how many trials ran, and only on a rig control set up on a cluster.
  • A change written on an older champion is carried to the new one: when it becomes champion, the champion it replaced is ported onto it; when it loses to the current champion, the judge measures it against the champion it was written on (parent_screen, event parent_screened), and if it helped there, ports it onto the current one. A port is an experiment at the top of the queue, Port <attempt> onto champion #<n>, with the original diff, which a worker implements and the judge judges as any change (port_queued). Before, such a change was judged only against a champion it was not written on, and its effect was lost.
  • The judge builds in parallel: on a rig with several judge GPUs, each card builds as a builder user of its own (builder, builder-1 … builder-7), so two changes build at once; a change two cards need is built once, and the second waits for it. The isolation selftest probes each card’s builder, and a new probe, build-work, checks that no other role can reach a build under way. Before, every build on the rig waited for the one before it. A build’s key is now the image’s installed packages and compilers, with THERMITE_TOOLCHAIN_ID for a tool installed another way, not the image’s name: an update to new images with the same packages keeps the run’s builds.
  • A judge job no longer loses its lease to a long build. The judge renews the lease every few minutes while it builds, and while it waits for another executor’s build; before, a project whose build took longer than the 15-minute lease, with the wait, was built again and again until its job was blocked, and its attempt was never judged. A blocked job’s attempt now ends as errored (“failed in the judge”), so its lane’s round ends and the next starts; before, the attempt stayed building and the run’s lanes waited for it. When the harness starts, each blocked job is queued once more (job_unblocked), its attempt with it, and one more lost lease blocks it again.
  • Take a rig’s results off it from the dashboard. Export, in the header of a rig’s page and of a run’s page, asks which export: What changed since the last one, or Full export, which also holds a snapshot of the ledger, every run’s research folder, finished or not, and the pages each run fetched. The line at the top of the page says the export was asked for and that it shows on the Exports page when all its files reach control. A full export now holds the research folder of every research run, and of every hill-climb or goal run that recorded research, even one still running or paused, at research/<run>/folder-<export>/, so a broken run’s record leaves the rig whole; normal exports are as before. The new Exports page, in the sidebar and linked from each rig’s page, lists every export control keeps, for every rig, newest first: its kind, when it was made and completed, its events, files and size. Download gives a complete one as one <rig>-<export>.tar.gz with its manifest; over REST, GET /v1/rigs/<rig>/exports/<name>/archive with the owner’s token, and the MCP tool exports lists them for the owner and an operator.
  • The dashboard says how a run ended. A closed goal run that met its goals reads Completed; any other closed run reads Finished. With no run at work, a campaign shows its newest run’s state, so a campaign whose newest run completed reads Completed even with an older run paused. The Campaigns list counts such a campaign as finished, which the default filter hides, and counts its runs as “N completed” apart from “N finished”. get_run and a rig’s view give goals_complete.
  • A judge-only run, started with its agents off, says so: judge only after its mode under its name, a box on its page (Judge only: No workers run. …), the mark judge only on its cards and under Earlier runs, and, while experiments wait in its queue, a line on its Experiments tab: no worker takes them, and its Start a run link opens the campaign’s Start a run panel with that run chosen under Copy the untried experiments of.
  • A run’s goals show their whole text: a long goal, or the judge’s long reason, shows its start and more opens the rest.
  • A decision you make leaves Needs you at once, with its count; the change’s own page still says what you chose and what the rig did. It comes back only if the rig refuses it or the item needs you again.
  • A dashboard button that sends something to a rig says at once that the click took, then follows it to its outcome, with no refresh. The button turns into Sending…, a line says Sent to rig-1 at 03:41 UTC. Waiting for the rig to take it., then that the rig took it, then Done., and the parts of the page that it changes are drawn again. A refusal or an expired command says why, and the button comes back. A rig check’s row goes from sent to running with the time so far, such as running · 1 m 12 s, and what the check pauses, to its result in place. This covers every command’s button (Run it, Check, Pause, Resume, Restart the lanes, Export, the decisions, the experiment queue and the rules), a message to the agents, Use on Providers, Update to a version, Update to these images and the other buttons of a rig that control set up, Save changes on Edit rig, and Start a run, whose page opens the run’s own page once the rig reports it. Without the page’s script, each form posts as before, and the page it lands on says Sent.
  • Model usage. Code records each model call of the workers, the planner and the advisors, never an agent, as a model_call event of its run: its tokens in (cached and not cached), its tokens out with the reasoning, and its times, as far as Pi, OpenCode v2, Codex and Claude Code report them. A figure a program does not report is null, never 0. Control sums the records for a run, a campaign (all its runs on every rig) or the whole system, today, over 7 days or for all time, with output tokens a second (median, 10th and 90th percentile) and the cost: each call’s tokens at its model’s price (the provider’s list’s, as OpenRouter and Nous Portal give one, else one the owner sets with POST /v1/model-prices or the MCP tool set_model_price), “on a plan” for a subscription’s calls, “no price” where none is known, and a RunPod rig’s pod spend for the run it ran: GET /v1/usage and the MCP tool model_usage.
  • Model use on the screens: a run’s page shows one line (trials, rounds, its time, its time in calls) and two cards (cost; tokens in, out and tok/s) above its tabs, and its new Stats tab shows its calls in each round and the table by agent and model; a campaign’s page shows its model use in its sidebar, with each run; the new Metrics page shows the whole system’s, by model and by campaign, for today (in the viewer’s time zone), 7 days or all; Settings › Providers has one Model prices table under the providers, which takes your price for each model (In, Cached in, Out, in $ for a million tokens) and shows the provider’s own. GET /v1/usage and model_usage take tz.
  • Control’s log pages through every entry, in the dashboard, over REST and in the control_log MCP tool.
  • A worker trial’s session is read over MCP with trial_session (goal 139): with a rig alone, each trial whose lines control still has, newest first, with what it was asked, its newest tool call and how it ended; with a trial, its lines (thinking, text, tool calls, results), oldest first, a page at a time with after and next_after. Its answer keeps to 40,000 characters, as get_run’s does. The owner, operators and readers get it; a campaign’s agents don’t.
  • A model’s long tool call no longer stalls the process that streams it (goal 140). Pi’s model client parsed a tool call’s whole arguments again on each piece that streamed in, so the work grew with the square of their length: a call of about a megabyte held a rig’s supervisor for over an hour, with no report, command or live line. Past 16 KiB, the arguments are now parsed only when the call ends, as every provider already did. Thermite applies this to pi-ai 1.3.0 at install (scripts/patch-pi-ai.mjs), and the install stops on any other version until its parser is checked.
  • A hill climb’s champion is at the top of its run’s page (goal 142): a card left of Cost with the champion’s value of the main metric, such as 14.6 tok/s, and its gain over the starting point, with the starting point’s value. The judge now records the values a gain comes from: since goal 133 it read its measurement blocks with metric and case swapped, so each verdict had no values, and the gain chart gave the starting point’s value beside the champion’s gain. A champion judged before the fix shows an estimate from the starting point’s value and its gain, marked ≈; the leaderboard’s values says so with estimated.
  • An optional limit on a worker’s field notes in one reply (goal 147): the contract’s lanes.limits.notes_per_turn. A Pi worker’s note past it is refused in words and its trial goes on. There is no limit unless the contract sets one.
  • A trial’s clock (goal 144). A worker’s brief gives the time its trial ends, in UTC. From 75% of a trial’s length, each of a Pi worker’s tool results ends with the time left. A Pi worker’s command (run, dev_run) stops 3 minutes before the trial’s end, so that the worker can still submit. At the end, a tool that still runs answers at once: the trial ends as wall with its notes, not as worker process gave no result two minutes later with none. A lane’s ccache now hits across its trials: CCACHE_BASEDIR is the lane’s folder and CCACHE_NOHASHDIR is on, where each trial’s new checkout folder made it miss nearly every compile.
  • A stuck trial asks you (goal 143). A trial that makes the same tool call with the same answer 10 times in a row, that asks 10 times for a page the run refused before, or that sends no line for longer than Ask me after a quiet spell of (minutes) on Edit rig (none by default; quiet_minutes over REST and MCP), is a card under Needs you: Repeats or Stuck?, with how long it has run, what it last did, a Pi worker’s open model call (since when, and how much the model sent so far) and the calls it repeats behind the card, and Stop this trial… (the stop records why) or Leave it running. Its lane’s row gets a chip. Nothing ends a trial for being quiet or repeating.
  • A Pi worker’s repeated tool call gets a short answer (goal 143): a call with the same tool, arguments and answer as one already in the conversation is answered Same call and same answer as at <time> UTC (call <n>). …, so a model in a loop no longer fills its context window. The call still runs; a changed answer, or one after a compaction, is given in full. Every harness’s repeats show as a trial_repeating event and a line in Live.
  • What each lane’s trial is doing is in the rig’s report, in get_run’s and run_queue’s at_work (last_line_at, reply, repeating) and in the teachers’ brief (goal 143). Control keeps each open trial’s newest 400 live lines past the rig’s window of 20,000, so one trial’s flood can’t push out another’s lines.
  • A request the harness refuses says which field and why (goal 143), such as invalid request: quotes[0].quote has 9 characters; it needs at least 20, for every endpoint a worker calls, through Pi’s tools and thermite-trial alike. add_claim’s text and thermite-trial research help give a claim’s limits.
  • A run’s top cards are shorter: Tokens gives tokens in and out together (30.1M in · 995k out), with the split of tokens in under it, beside Output speed. Cost on a run’s page and in a campaign’s sidebar no longer lists the tokens with no dollars under it; the run’s Stats tab and Metrics still give them.
  • The judge’s smoke check passes on a machine with noisy timing when the only wrong result is the no-op’s inconclusive in place of neutral. It then says so in a note: the timing on this machine is noisy, and results there need more repeats. Any other wrong result still fails it.
  • The CUDA smoke check works on newer GPUs. Its kit builds native code for every GPU generation its CUDA builds for (with CUDA 12, Pascal to Hopper, sm_60 to sm_90; with CUDA 13, Turing to Blackwell, sm_75 to sm_120), with PTX for newer ones, so no run compiles the kernel when it starts. Each case does five times the work, and its known win is register tiling, several times faster than the naive kernel. Before, on an Ampere card, starting the process took most of each run, and the known win of shared-memory tiling came out at 8%, under the contract’s 10%.
  • The documentation site has tutorials, how-to guides, reference and explanation. Agents read it through llms.txt. It includes the known limits and the security model.
  • CREDITS.md, also the documentation site’s Credits page: the research, systems, programs, libraries, standards and services Thermite draws on, runs or ships, each with its licence.
  • SECURITY.md, CONTRIBUTING.md and a code of conduct (the Contributor Covenant 2.1). CI runs on every push to the development branch: the checks, the docs site, a secret scan, the compose smoke test, the rig image, RunPod against a fake API, and the quick start from the release bundle.
  • Faster checks. npm test runs the test files side by side, one for each CPU, then the judge’s and the broker’s one at a time, alone, because they time real processes (--part parallel or --part alone runs one of the two). CI runs the two parts in separate jobs at the same time, and no job waits for another. Tests that need judged attempts but do not test the judge get the real judge with a stand-in runner, which answers the test kit’s runs and checks of its stock kernels in the test’s own process; the judge’s own tests keep real runs. The heavy CI jobs (the compose smoke test, the rig image, RunPod and the quick start) run on a push or a pull request only when the change touches what they test; every job runs on a version tag and when CI is run by hand.
  • The release checks every image it publishes before the image gets the version’s tag: pulled by digest with no sign-in, it must report the release’s commit, carry no Claude Code, have no set-id file and set no key in its environment; the harnesses and the RunPod rig image must carry neither socat nor bubblewrap (scripts/check-images.ts). The control, harness and supervisor images now clear set-id bits as the rig image does, and each build fails if one is left. A maintainer can also publish a build between releases by hand, tagged with its commit only and checked the same way, for a lab to pin by digest. A release’s compose.yaml sets the RunPod test image to the release’s rig image too.
  • get_run stays a size an agent can read for a run of any length: each list that grows with the run (the journal, the timeline, the queue, the hypotheses and the others) gives its newest 10 entries by default, and the research report its first 4,000 characters, with cut saying what was left out. limit, list with offset, and full_report read the rest. A research run of a day and a half gave 3.2 million characters; it now gives about 85,000. The same is over REST, GET /v1/rigs/<rig>/runs/<run>, new.
  • get_run and its REST route keep their default answer under 40,000 characters as MCP sends it, inside an agent’s tool output: above it, the texts in the lists’ entries are cut, shorter each step, and cut says so; list gives a list whole. run_queue keeps to the same budget, every entry kept. rig_setup gives a rig’s newest 20 steps (steps asks for up to 500) with steps_total.
  • Export on a run’s page exports that run alone, as its first choice, This run: the run’s events, attempts and notes from its start, its research folder as it stands, with its paper, and the pages it fetched, with no other run and no ledger snapshot. export_now takes run for it. A run’s export stays outside the rig’s chain of exports, so the next export still holds everything since the last one; the Exports page shows it as run <id>, and MCP’s exports gives its run. Before, Export on a run’s page exported the whole rig, every run included.
  • A hill climb’s gains show the real values they come from: beside +49.0%, the main metric’s measured values, such as 412 → 614 tok/s (one case’s, or the cases’ geometric mean, each case’s on hover), in the unit of the run’s baseline, on each attempt and in the Champions list, whose first row gives the starting point’s value. The judge’s screen and confirmation verdicts carry the values (values in the verdict event: each public case’s median for the change and the champion), and get_run gives them for each attempt. A verdict from an older harness has none and shows the gain alone. The champion’s value against the starting point’s shows on the run’s gain chart, the campaign’s headline and the leaderboard; leaderboard over MCP and REST gives values: {start, champion, unit}.
  • The outside knowledge base: its copies of campaign folders, the advisors’ search grant and THERMITE_OUTSIDE_KB_SYNC_SECONDS. At the update, each one becomes an MCP server with its token, and a kit names it.
  • The old QWEN_* names of the model settings, the thermite-qwen provider ID and the qwen-api-key file migration. Use the THERMITE_MODEL_* settings.
  • The Kubernetes namespace default ar-{rig}. It is now thermite-{rig}; a rig with a namespace set keeps it.