sql query) or code in a package. A runtime is code in a package. The evaluate command is your own
program.
Roles and triggers
A role is a table inpiloop.toml: what the agent is told (its prompt), when it starts (the run queue, or its
triggers), where it works (its runtime and working copy), how it works (harnesses, models, MCP servers, timeout,
concurrency) and what it may file. The built-in worker, watchdog and research roles are the same tables with defaults.
Roles describes each key and each built-in trigger.
A role either does runs or sweeps. A role that does runs (runs = true, like the worker) takes ideas from the run
queue, works in a new worktree at the base of its run, and its commits are scored and judged. A sweeping role (like
the watchdog and research) starts on its triggers, works at the head or in the project checkout, and nothing it commits
is scored.
Define a role
Add a table with a new name, a prompt file and at least one trigger. This role reads each new escalation in the inbox and writes a plan for it, one agent at a time, in the project checkout:planner agent, and
piloop status lists it as planner#<worker id>. The agent signs its comments planner#<worker id>.
Define a role that does runs
Add a table with a new name andruns = true. The role takes the worker’s defaults, so every other key is optional.
This role runs the ideas of the speed track with Claude, two at a time, and stops each agent after 10 minutes:
takes is an SQL condition on the ideas table, so it can select by any column: priority, track, family,
harness, or a subquery. Without takes on the worker, the worker also runs the speed ideas that wait for a free
sprinter slot. Write the prompt and store the config:
speed idea for a sprinter run. The run’s agent signs sprinter#<worker id>, its brief has
the idea and the starting point as a worker’s has, and its commits are scored and judged like a worker’s.
Roles lists which rules apply to each role and which apply to every run.
Add a trigger or a role from a package
A Python package can add triggers and roles through two entry point groups:piloop.triggers: each entry point names a subclass ofpiloop.triggers.Trigger. Its name is thekind. Its fields are its parameters. Itsfire(tick)method returns aFire(the reason, and how many agents) or None.tickgives it the session, the project, the role and the queries that the built-in triggers use (piloop.triggers.Tick).piloop.roles: each entry point names apiloop.config.SweepRoleor apiloop.config.WorkerRole(a role that does runs), the defaults of the role. Its name is the name of the role.piloop initstores the role in the project’s config, with the project’s[roles.<name>]table over the defaults.
pyproject.toml, and install the package in the environment of piloop:
{ kind = "no_keep", hours = 12 } in the triggers of a role, and gets the auditor role at
its next piloop init.
Runtimes
A runtime is where an agent process runs. The daemon depends only on theRuntime protocol in
piloop/runtime/__init__.py:
A runtime class also has two class attributes, which piloop reads before it constructs the runtime:
kind: the name of the runtime. It is the name of its entry point, the value ofruntimeinpiloop.tomland the value ofworkers.runtime.invocation: the argument list of a harness that the runtime starts.headlessis the argv for a process without a terminal, with the first prompt in it.interactiveis the argv for a session in a terminal, and the runtime types the first prompt after the start.
subprocess, the default (a background
process that cannot receive messages), and herdr (a terminal pane that you can watch and type into). A project
chooses its runtime with the runtime key of piloop.toml, and a role can choose another one with its own runtime
key. piloop init refuses a name that no runtime has.
Register a runtime
A Python package adds a runtime with an entry point in thepiloop.runtimes group. This runtime starts each agent as
the subprocess runtime does, at a lower CPU priority:
piloop. A project then sets runtime = "nice", or a role sets it in its
own table. A name that a built-in runtime has already, a kind that is not the name of the entry point, or a class
without an invocation is an error that names the entry point.
piloop attach, the Herdr checks of piloop doctor and the tab column of piloop watch work with the herdr
runtime only, because they call Herdr.
Harnesses
A harness is a coding agent CLI. A[harness.<name>] table in piloop.toml says how to start it: the executable, the
argv for an interactive session (interactive, for Herdr) and for a background run (headless), and the startup
dialogs to accept. piloop fills in placeholders such as {model}, {session_id}, {cwd} and {prompt}.
pi, claude and codex are definitions of the same kind, so a project can override any key
of them. Two optional parts of a harness have their own registries in the code: the usage reader that reads the
tokens from the harness’s session files (piloop/usage/), and the mcp renderer that writes the MCP servers in the
harness’s own flags (piloop/mcp/). The configuration reference has every key.
MCP servers
An[mcp.<name>] table defines an MCP server once, and [roles.<name>] mcp gives it to the agents of a role. piloop
writes it in the format of each harness, and passes API keys only as environment variables. See
MCP servers.
The evaluate command
The evaluate command is the contract between piloop and your project. The daemon runs it in a clean worktree of each commit. The command must:- print one line
METRIC <name>=<value>on stdout for each metric inpiloop.toml; - exit with status 0, within
evaluate_timeoutseconds. Otherwise the candidate is acrash.
PILOOP_UNITS_FILE, the command can also write the result of each unit (each example, record or
file) to that path, so that the verdict can use a paired bootstrap. See
The evaluate command and per-unit results.