piloop status, a brief or a UI shows. Agents and roles describes them as a
user sees them.
Harnesses
A harness provider holds what piloop knows about one coding agent CLI. This one is Claude Code’s beta build, which every campaign that does not setharnesses rotates through:
The contract
A harness provider is a subclass ofHarness. Its fields are the keys of [harness.<name>] with its defaults. Its
methods hold what piloop knows about the CLI: mcp writes a role’s MCP servers in its flags, usage reads the tokens
from its session files when an agent ends, messages is what its hook prints for a message, and skills_dir is where
it reads user skills.
A table with a new name and
provider = "<harness>" is that harness over its defaults. A table with a new name and no
provider runs its argv and does nothing else: it takes no MCP servers, and records no tokens.
Harnesses lists the built-in ones.
Runtimes
piloop starts each agent through a runtime. The runtime starts the agent’s process. piloop then waits for the agent, sends it messages and stops it through the runtime.Example: run the agents under nice
This runtime starts each agent as the built-in runtime subprocess does, but with a lower CPU priority:
pyproject.toml
piloop_nice/__init__.py
piloop.toml and run piloop init .:
[roles.research] runtime = "nice". [runtimes.nice] in config.toml holds the
runtime’s settings.
The contract
A runtime is a pydantic model, a subclass ofRuntimeConfig: the name of its entry point is its name, and its fields
are the keys of [runtimes.<name>]. Its open() returns an object with the methods of the Runtime protocol. That
object starts the headless argv of a harness, with the first prompt in it. Runtimes
says what a runtime does for you.
Built in
Runtimes lists the built-in runtimes.Roles
Agents and roles defines a role inpiloop.toml. A plugin can ship one too.
Example: a role and a trigger from a plugin
This plugin adds a trigger,no_keep, that starts an agent when no candidate
was kept for some hours. It also adds a role, auditor, that uses this trigger:
{ provider = "no_keep", hours = 12 } in the triggers of a role, and gets the auditor role,
with its table in piloop.toml over the package’s defaults, at its next piloop init.
The contract
A role is a table inpiloop.toml: its prompt, when it starts (the run queue, or its triggers), where it works, how
(harnesses, models, MCP servers, timeout, concurrency) and what it may file. A role that does runs (runs = true)
takes ideas from the run queue, and its commits are scored and judged. A sweeping role starts on its triggers, and
nothing it commits is scored. A plugin’s role is a subclass of SweepRole or of ImplementerRole whose fields have
the role’s defaults, and whose PROMPT is the prompt that starts its brief: a file of its package. prompt in the
table overrides it. Roles describes each key.
Built in
The built-in roles are the same tables with defaults, so a campaign can change any key of them:Triggers
Asql trigger needs no code: its query fires the role when it returns a row, as in
Define a role. A trigger with code of its own comes from a plugin, as no_keep above.
The contract
A trigger is an entry of[roles.<name>] triggers: { provider = "<trigger>", <parameter> = <value> }. A plugin’s
trigger is a subclass of Trigger: its fields are its parameters. On each pass, for each sweeping role
without a pending request, the daemon calls fire(p) of each trigger of the role in order. A pass comes when
something changes in the campaign, or in what a trigger names in WAKES_ON, and when the time that a trigger’s
due_in returns comes. A trigger that compares a time returns when the comparison turns: there is no pass on a timer.
The rows that it reads are the models of piloop.db.models, the data model.
Built in
Triggers lists the built-in triggers.Sections
Example: the keeps per hour
piloop.sections. piloop status then shows it after the evaluations, wherever
[status] sections is not set.
The contract
A section is an entry of a list of sections:{ provider = "<section>", <parameter> = <value> }. A plugin’s section is
a subclass of Section: its fields are its parameters. Its render returns a Rendered, a title and its blocks. A
block is one of four kinds: Text, Lines, Figures (named values, a number with its unit) and Table.
piloop status, a brief, the API and a UI render each kind their own way, so a section never formats for one reader.