piloop.toml is at the root of the project repo. piloop init reads it again each time you run it.
To override a key for one process, set PILOOP_PROJECT_<KEY>. Join nested keys with __, for example
PILOOP_PROJECT_RUNTIME=subprocess or PILOOP_PROJECT_ROLES__WORKER__MAX_CONCURRENT=2.
A type in the tables links to the section that describes it. A table that you leave out has the defaults of its
section.
Top-level keys
Metric
One metric. The evaluate command prints it as aMETRIC name=value line.
TrackConfig
[tracks.<name>]: one track of the project. A track is an objective with its own primary metric and brief.
A track has the name of its primary’s track, or else the name of the primary. All tracks build on one trunk. A
run starts from the head, which is the latest keep on the trunk. A keep on any track must also hold the primary of
every other track within that primary’s margin and floor. A project with one primary has one track and
needs no table. A project with several primaries needs one table for each track, and the shares must sum to 1.
Experimental: projects with more than one track.
Harness
[harness.<name>]: how to launch one coding agent.
piloop substitutes {model}, {session_id} and {cwd} (the agent’s working directory) in both argument
lists. It substitutes {prompt} (the first prompt, which names the file that holds the brief) and {log_dir}
in headless only. An element that is exactly {mcp} expands to the role’s MCP servers, in the form that
mcp names. The three built-in harnesses run without permission prompts: workers run in worktrees that the
daemon owns and throws away, and a sandbox that cannot reach the repo’s .git cannot commit. A Claude agent
moves a foreground Bash command to the background after 5 minutes.
When the argument list in use contains {session_id}, piloop creates the session id. Otherwise the runtime
reports it: Herdr detects it for every harness it knows, and the subprocess runtime needs session_id_pattern.
When the agent ends, entire session attach takes this id, and usage uses it to find the session file and
read the tokens the agent spent.
UsageSpec
[harness.<name>.usage]: how to read the tokens that an agent of this harness spent, after it ended.
reader names the code that reads them, and the other keys are its parameters. The built-in harnesses set
their specs as data. A project can override any key, or give a custom harness a complete spec. piloop checks the
names when the config loads.
Roles
One table for each role. A role without a table uses its defaults.RoleConfig
The keys that every[roles.<kind>] table accepts. Each role’s table below adds keys and sets defaults.
WorkerRole
[roles.worker]: a worker does one idea in a worktree. The queue trigger starts it.
WatchdogRole
[roles.watchdog]: the watchdog investigates the anomalies that the daemon detects and reviews progress.
ResearchRole
[roles.research]: research develops seeds, keeps ideas ready for the workers and scouts for techniques.
Scout
[roles.research.scout]: the scout mode of the research role. It is off when the table is not there.
Every interval seconds, a research sweep runs in scout mode (prompts/research_scout.md). It states the open
problems of the campaign from the evidence, and searches X, GitHub and arXiv for outside techniques that address
them. It files at most seeds of them as seeds, signed scout#<worker id>, and puts the rest in the technique
library (the techniques table). It never files a defined idea. The search tool limits what one sweep spends on
xAI (scripts/xai_search_mcp.py --max-usd). Scout sweeps stop with all other launches under [spend].
Experimental: scout mode.
McpServer
[mcp.<name>]: one MCP server, either streamable HTTP (url) or stdio (command).
Do not write API keys here. Name the environment variables that hold them. piloop passes these variables from the
daemon’s environment to the agent, and the harness puts their values into the header or the server’s environment.
Stop
[stop]: conditions that end the campaign. One is enough. Without the table, the campaign has no end.
The daemon checks the conditions on every tick, before it requests anything. When one holds, the project is
stopped (projects.status, stopped_reason). Running workers are told to commit and stop, and are ended
ten minutes later. What they committed is scored. Pending requests and queued runs are dropped
(campaign stopped). One last watchdog sweep is requested, and nothing new starts.
piloop init resumes a stopped project when this table changed.
Spend
[spend]: a hard budget in USD on what the campaign costs outside piloop, for example GPUs or APIs.
Every interval seconds, the daemon runs command to read the spend from the project (projects.spend_usd,
spend_error, spend_at). When the spend passes a notices threshold, the daemon posts one note to the
inbox at priority 1. At limit, the daemon launches no agent and starts no evaluation (no request, no
re-measure), and posts one escalation at priority 0. Work that already runs finishes and is scored. A command that
fails stops launches in the same way, with one escalation for each series of failures: an unknown spend is never
read as $0. To resume, raise limit and run piloop init.