Skip to main content
A harness is the coding agent CLI that an agent runs in. A runtime starts and steers the agents. A role says what an agent does, and its triggers say when it starts. A section is one part of what 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 set harnesses rotates through:

The contract

A harness provider is a subclass of Harness. 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
Install it, then select it in piloop.toml and run piloop init .:
A role can select its own: [roles.research] runtime = "nice". [runtimes.nice] in config.toml holds the runtime’s settings.

The contract

A runtime is a pydantic model, a subclass of RuntimeConfig: 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 in piloop.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:
Any campaign can then use { 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 in piloop.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

A sql 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

Declare it with an entry point in 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.

Built in

Sections lists the built-in sections.