Skip to content

Start a campaign from the command line

Send a contract to a rig with thermite assign. Use this when you keep contracts in files, or script your campaigns.

Terminal window
npx thermite contract validate contract.json
npx thermite assign --contract contract.json --run sort-speed-1 \
--seed https://git.example.com/you/project.git@0123456789abcdef0123456789abcdef01234567 \
--kit https://git.example.com/you/project-kit.git \
--control http://127.0.0.1:8800 --token-file .dev/secrets/control-admin-token --rig local-1

Run npx thermite in your Thermite folder, after npm ci and npm run build (see the command line). The examples use the owner’s token file of the local stack, .dev/secrets/control-admin-token. If you made an owner’s token of your own, use its file instead. They also use the local stack’s address, http://127.0.0.1:8800, and its rig local-1: for a control on another computer, use its address, such as https://control.example.com.

  • Thermite’s command line, built from a clone of its source code with Node.js 22.19 or later (the command line). Thermite’s images also have the command line, as thermite.

  • A contract file. See Write a contract.

  • For a hill climb or a goal campaign: the project and its kit in git repositories the rig can fetch. The contract names each by a short name, and the kit by its full commit too; the addresses are given here, with --seed and --kit. See Write a kit for your project.

    contract.json (part)
    "project": {
    "repo": "project",
    "kit": { "repo": "project-kit", "commit": "89abcdef0123456789abcdef0123456789abcdef" }
    }
  • For a private project or kit: thermite assign has no option to name a Git access. See If the repository is private.

  • The owner’s token in a file. No other token works with these commands. (Over MCP, an operator’s or an agent’s token can send the same payload: see Send it from an agent over MCP.) See Tokens and roles.

  • A rig that reports to control. For agents to work, it needs a model: see Connect a model provider. If the model uses a paid key, put a limit in the contract first: see Set a budget or a stop time.

  • A rig runs one run at a time: a new run pauses the run at work on that rig.

Terminal window
npx thermite contract validate contract.json

A valid contract prints its mode, name, version and hash, such as ok hill_climb sort-speed v1 sha256=5f0c…. A hill climb or goal contract without project.kit also prints a warning that the judge can’t measure it yet. A contract with mistakes prints each one as <field>: <message> and exits with status 1.

Run thermite assign as in the example at the top. Control signs the command and prints its id, such as {"id":"3f6c1d2e-…","status":"pending"}. The rig’s supervisor then fetches the two commits, and its harness, the rig’s container that holds the judge and the agents, checks them and the kit before the run starts.

OptionWhat it does
--contract FThe contract file. Required
--run IDThe new run’s name: lowercase letters, digits, dots, dashes and underscores. It must be new on that rig. Required
--seed GIT_URL@COMMITWhere the project comes from, and the full commit to start from. Without @COMMIT, the contract’s project.commit. The rig keeps the project under the contract’s project.repo name
--seed-commit CInstead of --seed: a full commit already on the rig, such as an earlier run’s champion
--kit GIT_URLWhere the kit comes from. Its commit comes from the contract. Leave it out when the rig already has that kit commit
--model NAME=URLA model file the contract lists under models: a data file your project loads, such as a model’s weights, with the address to fetch it from. Only a file with the contract’s sha256 reaches the harness. Repeat for each model. For a large file, give the assignment time with --ttl: see Bring a model’s weights to a run
--agents offA run with no agents: only the judge works. See Test a kit with known candidates
--promotion auto or ownerauto: a confirmed win becomes the champion at once. owner: it waits for you. Unset: the rig’s default
--queue-from RUNStart the new run’s queue with the untried experiments of an earlier run on the same rig, or RIG/RUN on another rig. Off unless you give it. See Start further runs
--control URL, --token-file F, --rig RControl’s address, the owner’s token file and the rig
--ttl MINHow long the command may wait to be delivered, in minutes: 60 unless set, at most 10080
--printPrint the payload instead of sending it

With neither --seed nor --seed-commit, the contract’s project.commit must already be on the rig. A research campaign needs neither --seed nor --kit. The command-line reference has every option.

  • On the dashboard, open Commands in the sidebar. The assign campaign row shows delivered once the harness took it, or failed with the reason: see If it is refused.

  • Open Campaigns. The run shows under At work now, and the campaign in the list below, once the rig reports the run, usually within a minute. Press refresh in the status bar, the line along the bottom of the page: pages don’t reload by themselves.

  • From a script, ask control with the owner’s token:

    Terminal window
    curl -s -H "Authorization: Bearer $(cat .dev/secrets/control-admin-token)" \
    http://127.0.0.1:8800/v1/commands/3f6c1d2e-…

    Its status is pending, delivered, rejected, failed or expired, and outcome holds the rig’s answer. rejected means the rig refused the command before it tried it, for example because its signature or payload didn’t pass the rig’s checks; failed means the rig tried it and it didn’t work, for example because a fetch failed. See Statuses.

Assign each further run the same way, with a new --run name. To start its experiment queue with what an earlier run had not tried yet, add --queue-from and that run’s name:

Terminal window
npx thermite assign --contract contract.json --run sort-speed-2 \
--seed-commit 0123456789abcdef0123456789abcdef01234567 --queue-from sort-speed-1 \
--control http://127.0.0.1:8800 --token-file .dev/secrets/control-admin-token --rig local-1

The new run’s queue starts with sort-speed-1’s experiments up next, in their order, then its lanes’ ideas, at most 500, as Start a run copies them. The command reads them from control, with each one’s words and author from control’s records, so it needs --control and --token-file even with --print. For a run on another rig, write rig-2/sort-speed-1. A run without agents (--agents off) can’t take them. Without --queue-from, the new run’s queue starts empty and the planner fills it.

Or let control assign it again, as Start a run on the campaign’s page does, with thermite run start:

Terminal window
npx thermite run start sort-speed --rig local-1 --queue-from sort-speed-1 \
--control http://127.0.0.1:8800 --token-file .dev/secrets/control-admin-token

It needs no contract and no commit: control reuses those of the campaign’s newest assignment, and prints the new run’s name and command id. Start a run from a script or an agent gives its other options.

Or open the campaign’s page on the dashboard and press Start a run: choose the rig under Where it runs, Agents On or Off: a try, and press Start the run. The page is /ui/campaigns/<name>, such as http://127.0.0.1:8800/ui/campaigns/sort-speed. It opens once the assign campaign row on Commands is pending or delivered, even before the rig reports the run. Until the rig does, the Campaigns list doesn’t show the campaign, and the page says Assigned to a rig over the API, MCP or the CLI; no run of it reports now. A campaign saved from the dashboard’s New campaign page says Saved on control, with no run yet. instead.

Control assigns the campaign again as it assigned its newest run, with the same contract, starting commit and imports, as a new run named <campaign>-<n>, or <campaign>-try-<n> without agents, with the first number not yet used on that rig, such as sort-speed-2. Agents, and When a change is confirmed better under More options, come from the panel, not from the old assignment. With Agents On, Experiments to start with can copy another run’s untried experiments into the new run’s queue: see Choose what a run tries next. From past research can add experiments, and background for the agents’ briefs, from the folders the campaign may read in Thermite’s knowledge base: see Use past research in a new run. thermite assign has no option for past research: use thermite run start --past, or send a payload with thermite command, as that page shows. A run on another rig needs that rig to get the project and the kit the same way: see Run a campaign on several rigs.

--print builds the payload without the control options:

Terminal window
npx thermite assign --contract contract.json --run sort-speed-1 \
--seed https://git.example.com/you/project.git@0123456789abcdef0123456789abcdef01234567 \
--kit https://git.example.com/you/project-kit.git --print

The agent sends it with the issue_command tool, kind assign_campaign, then polls command_status with the id. See Let an agent run Thermite over MCP.

Put the project and the kit in the rig’s inbox yourself, as git bundles that hold the commits. This works for a private repository too. The inbox is /inbox in the rig’s containers, and .dev/inbox in your Thermite folder on the local stack:

Terminal window
mkdir -p .dev/inbox/repos
git -C ~/src/project bundle create "$PWD/.dev/inbox/repos/project.bundle" main
git -C ~/src/project-kit bundle create "$PWD/.dev/inbox/repos/project-kit.bundle" main

For a rig elsewhere, make the bundles the same way, then copy them into its supervisor’s container:

Terminal window
# A rig on a machine: on that machine, as the user the rig runs as
podman exec thermite-rig-1-supervisor mkdir -p /inbox/repos
podman cp project.bundle thermite-rig-1-supervisor:/inbox/repos/project.bundle
# A rig on a Kubernetes cluster, in the rig's namespace
POD=$(kubectl get pod -n <namespace> -o name \
-l app.kubernetes.io/instance=rig-1,app.kubernetes.io/component=supervisor | cut -d/ -f2)
kubectl cp -n <namespace> project.bundle "$POD":/inbox/repos/project.bundle

Copy the kit’s bundle the same way.

Then thermite assign-payload names the bundles by their paths inside the inbox, and thermite command sends the payload. Use the contract’s project.repo and project.kit.repo for --seed-repo and --kit-repo, and its project.kit.commit for --kit-commit: the harness looks for the kit’s commit under that name.

Terminal window
npx thermite assign-payload --contract contract.json --run sort-speed-1 \
--seed-repo project --seed-commit 0123456789abcdef0123456789abcdef01234567 --bundle repos/project.bundle \
--kit-repo project-kit --kit-commit 89abcdef0123456789abcdef0123456789abcdef \
--kit-bundle repos/project-kit.bundle > payload.json
npx thermite command --control http://127.0.0.1:8800 --token-file .dev/secrets/control-admin-token \
--rig local-1 --kind assign_campaign --payload @payload.json

assign-payload has no --promotion option: such a run follows the rig’s default.

thermite assign has no option to name a Git access: the New campaign page is the way made for it. From the command line, use one of:

  • the bundles above, which need no access on the rig;
  • a payload that names the Git access, sent with thermite command: see Use a private repository.

The command line itself prints a refusal from its own checks or from control. A refusal from the rig shows on Commands, in the command’s row.

You seeWhereWhat to do
give --control, --token-file and --rig to issue it, or --print to show itThe terminalAdd the three options, or --print
the seed commit must be a full commit id (40 or 64 hexadecimal characters)The terminalGive the whole commit after @
--kit given, but the contract names no project.kitThe terminalAdd project.kit to the contract
a run without agents has no queue: leave out --queue-fromThe terminalLeave out --queue-from, or --agents off
-> 404 {"error":"no run sort-speed-1 on local-1"}The terminalCheck the run’s name and its rig on the campaign’s page, and write RIG/RUN for a run on another rig
-> 401 {"error":"unauthorized"}The terminalUse the owner’s token
bad assign_campaign payload: seed.repo: lowercase idThe terminalMake the contract’s project.repo a lowercase name, such as my-project
failed, run <id> already existsCommandsChoose a new run name
failed, contract <campaign> v<n> exists with other contentCommandsYou changed the contract: raise its version
pending, then expiredCommandsThe rig’s supervisor is down, or a large fetch took longer than the command may wait: send it again with a longer --ttl
failed, staging: <address> has no commit <commit>, or git’s own error after staging:CommandsThe rig could not fetch the project or the kit. Check that the address works from the rig and that the commit is pushed there. For a private repository, see If the repository is private. Then send the same command again: the run was never made
failed, the kit does not fit the contract: …CommandsThe kit lacks a cell, metric or verifier the contract names, or measures on another target: each problem is named, such as cells names cell large, which the kit does not have. Correct the contract, or commit the kit again and put its new commit in project.kit. Then send it again, with a new version if this version already ran on the rig
failed, kit <repo> at <commit> has no valid kit.json: …CommandsFix kit.json as the message says, commit it, and put the new commit in project.kit. See Write a kit for your project