> ## Documentation Index
> Fetch the complete documentation index at: https://piloop.autoresearch.inc/llms.txt
> Use this file to discover all available pages before exploring further.

# Roles

> The worker, the watchdog, the research agent and the roles that you define. Every agent that piloop starts has a role.

Every agent that piloop starts has a **role**. Every agent is a coding agent CLI (a **harness**: Claude Code, Codex or
pi) that runs in a git worktree of the project, with a brief, and reads and writes the piloop database. piloop has
three built-in roles. You can [define more](#define-a-role) in `piloop.toml`.

| Role | What it does | When the daemon starts one | Its worktree |
| - | - | - | - |
| **worker** | Implements one idea and commits. | A ready idea and a free worker slot. | At the starting point of its run. Its commits are scored. |
| **watchdog** | Looks for problems and reports them. | On an anomaly, every `interval` seconds, and once when the campaign stops. | At the head. Its commits are not scored. |
| **research** | Develops seeds into ideas and adds ideas of its own. | A new seed, or a short backlog. | At the head. Its commits are not scored. |

Every role has the same lifecycle. The daemon writes a `workers` row, starts the agent, waits until the agent ends or
times out, saves the screen transcript, reads the tokens that the agent used, attaches the session to
[Entire](/concepts/session-capture), and closes the row. `piloop status` lists the running agents with their ids.

The worker **does runs**: the run queue starts it, it works at the starting point of its run, and its commits are
scored and judged when it ends. A role that you define can do runs too ([Define a role that does
runs](#define-a-role-that-does-runs)). Every other role is a **sweeping** role. Its triggers start it, it works at the
head or in the project checkout, and nothing is scored when it ends.

The [orchestrator](/concepts/orchestrator) is not a role. The daemon never starts it.

## Configure a role

Each role has a table in `piloop.toml`. These keys are common to every role:

| Key | Meaning |
| - | - |
| `prompt` | The file that starts the role's brief, instead of the built-in one. |
| `runtime` | Where the role's agents run: `subprocess`, `herdr`, or a runtime that a package adds. Unset: the project's `runtime`. |
| `harnesses` | The harnesses that the role uses in turn. Unset: the project's `harnesses`. |
| `model` | The model for each harness, merged over the project's `[model]`. |
| `timeout` | Seconds before the daemon stops the agent. |
| `max_concurrent` | Agents of this role that run at the same time. |
| `addendum` | A file in the repo that is added to `brief.md` for this role only. |
| `mcp` | The [MCP servers](/guides/mcp-servers) that the role's agents get. |
| `writes` | What the role's agents may write, as their brief states it. |
| `files` | What the role's agents may file with `piloop idea`: `nothing`, `seeds` or `ideas`. The CLI refuses the rest. |

A role that does runs also has these keys:

| Key | Meaning |
| - | - |
| `runs` | `true`. It is what makes a table with a new name a role that does runs. |
| `takes` | The ideas that the role takes: an SQL condition on the `ideas` table, for example `priority >= 3`. Unset: every runnable idea. |
| `explore_every` | At least one run in this many of the role's runs starts from a pinned idea, when one is ready. 0 turns this off. |

A sweeping role also has these keys:

| Key | Meaning |
| - | - |
| `triggers` | When the daemon starts one of its agents. See [Triggers](#triggers). |
| `workdir` | `head` (the default): a new worktree at the head, removed when the agent ends. `project`: the project checkout itself. |
| `interval` | Seconds between two sweeps of the `cron` trigger. 0 turns the timer off. |

The defaults:

```toml theme={null}
[roles.worker]
timeout = 1800
max_concurrent = 4      # each worker is a coding agent process on this machine
explore_every = 4       # at least 1 run in 4 starts from a pinned idea, when one is ready

[roles.watchdog]
triggers = [{ kind = "anomaly" }, { kind = "cron" }]
interval = 1800         # seconds between progress reviews; 0 turns them off
timeout = 600
max_concurrent = 1

[roles.research]
triggers = [{ kind = "low_water" }, { kind = "seed" }, { kind = "scout" }]
max_ideas = 1           # ideas of its own per sweep, after the seeds
buffer = 2              # ready ideas to keep beyond one per worker slot
timeout = 900
max_concurrent = 1
```

The top-level `[max_concurrent]` table limits one harness across every role, for example `claude = 2` when your
account limits the request rate. When it is the turn of a harness at its limit, the slot goes to the next harness of
the rotation.

## Triggers

A trigger is a condition that the daemon checks on each tick, for each project. When a trigger of a sweeping role
fires, the daemon requests one more agent of that role. The agent's brief shows the reason. Each entry of `triggers` is
a table with the trigger's `kind` and its parameters:

| Trigger | Fires when | Parameters |
| - | - | - |
| `anomaly` | The daemon detects an incident that no agent got yet. The agent gets the incidents to investigate. | none |
| `cron` | The role's `interval` passed since its last routine sweep, one that got no anomaly. | none: it reads the role's `interval` |
| `seed` | A seed was filed after the role's last agent started. | none |
| `low_water` | The backlog of runnable ideas is short. Research only. | none: it reads `max_ideas` and `buffer` |
| `scout` | `[roles.research.scout] interval` passed since the last scout sweep. Research only. | none |
| `sql` | A query on the piloop database returns a row. | `when`: the query |

The daemon checks the triggers in the order of the list. It requests at most one batch for each role on each tick,
and it requests nothing while a request of the role waits to start. An `anomaly` or `scout` fire changes what the
agent does, so it gets an agent of its own: it fires alone when it is the first trigger to fire, and otherwise waits
for a later tick.

A `sql` query can use two parameters: `:project`, the project's id, and `:since`, when the role's last agent started.
Compare `:since` with a `created_at` column to fire only on what is new. `piloop init` checks the query on an empty
copy of the database: it must be one statement that only reads.

Change the triggers of a built-in role with its `triggers` key. For example, this watchdog does not investigate
anomalies, and only reviews progress every hour:

```toml theme={null}
[roles.watchdog]
triggers = [{ kind = "cron" }]
interval = 3600
```

An empty list turns the triggers off. The role then starts only when you ask (`piloop launch`).

## Define a role

A table with a new name defines a new sweeping role. It needs `prompt`, the file that starts its brief, and
`triggers`. The other keys have the defaults of the [configuration reference](/reference/configuration#sweeprole).
This reviewer reads each new keep and files a seed when it finds a problem:

```toml theme={null}
[roles.reviewer]
prompt = "briefs/reviewer.md"   # relative to the repo root
triggers = [
  { kind = "sql", when = "SELECT id FROM candidates WHERE project_id = :project AND verdict = 'keep' AND created_at > :since" },
]
workdir = "head"
runtime = "subprocess"
max_concurrent = 1
timeout = 900
files = "seeds"
writes = ["comments(finding) on a candidate", "seeds via idea seed"]
```

The brief of a reviewer is your prompt, then `brief.md`, the database procedures and its context: its signature
(`reviewer#<worker id>`), the reason it started, the project overview and the research pack. Run `piloop init` after
you change the table. `piloop launch <project> reviewer` asks for one more reviewer by hand.

A role name has at most 20 characters: lowercase letters, digits, `_` and `-`, and a letter first. `scout`, `run`,
`orchestrator`, `human` and `daemon` are taken.

A Python package can also add roles and triggers. See
[Extension points](/architecture/extension-points#roles-and-triggers).

## Define a role that does runs

A table with a new name and `runs = true` defines a role that does runs. It takes the worker's defaults, its prompt
included, so every key is optional. This role takes the cheap ideas, with a short timeout and its own harness:

```toml theme={null}
[roles.quick]
runs = true
takes = "priority >= 3"    # an SQL condition on the ideas table; unset: every runnable idea
prompt = "briefs/quick.md" # unset: the worker's prompt
harnesses = ["pi"]
timeout = 300
max_concurrent = 2
explore_every = 0
```

On each tick, the roles that do runs fill their free slots in order: the roles that you define, in the order of the
file, then the worker. So the worker takes every idea that the other roles leave. This includes an idea that waits
for a free slot of a full role. To keep the worker away from the cheap ideas, give it a condition too, for example
`[roles.worker] takes = "priority < 3"`.

Some rules apply to each role that does runs, and some to every run of the project:

* Each role: its own `max_concurrent`, its `takes`, its harness rotation, its prompt and timeout, its `explore_every`
  (counted over its own runs), and the track shares (of its own slots).
* Every run: `[stop] max_runs` and `plateau`, the attempts of an idea (`ideas.max_runs`), and the verdict. A keep of
  a run of any role moves the head. A keep against a bar that the head left behind is merged onto the head in the same
  way.
  Research keeps a backlog of one idea per run slot of every role, plus `buffer`.

`runs.role` records the role of each run, and its agents have that `workers.kind`. When `piloop init` removes a role,
its queued runs fail. They do not count as attempts, so their ideas go back to the queue.

## Worker

A worker gets one idea. Its brief has the worker instructions, your `brief.md`, the idea, the starting point and the
recent results. It may edit the code in its worktree and commit. It writes a `finding` comment on its idea at the end.
It must not change the rows of runs, candidates or measurements, and it files no ideas or seeds.

## Watchdog

The watchdog informs. It never edits code or changes a row. It writes comments: findings, notes and escalations for
the orchestrator, and seeds for research. It runs in one of two modes:

* An **incident sweep**, when the daemon detects an anomaly. Anomalies are a failed or timed-out run, a worker or
  research agent that died at startup, and a worker with no comment or commit for an hour while its jobs run.
* A **progress review**, every `[roles.watchdog] interval` seconds. It asks whether the campaign is getting
  closer to its goal. When the campaign stops, one last progress review writes the closing report.

## Research

The research agent keeps the workers supplied with ideas. A sweep:

1. Reads the **research pack** that the daemon builds: the metrics, what each source of ideas produced, the analysis
   files listed in `[roles.research] context`, what was tried for each mechanism, the latest 60 ideas with their
   outcomes (and the total count), the kept lineage, and the findings. `piloop history` prints the same pack.
2. Develops each waiting seed into an idea, or rejects it with the reason.
3. Adds ideas of its own, up to `max_ideas`, where the head loses the most.
4. Writes one `finding` on the project.

The daemon starts a research sweep when:

* A seed is filed.
* The backlog is short: fewer ready ideas than one per worker slot plus `buffer`. When a sweep adds no idea,
  research waits longer before the next one.

Ask for a sweep at any time with `piloop launch <project> research`.

### Scout mode

<Note>Experimental: scout mode.</Note>

A **scout** sweep searches outside the repo for techniques that could reduce a measured loss. It is a research sweep
in another mode:

1. It writes a problem statement first: the open problems as measured losses, what was tried, and the constraints.
2. It searches X, GitHub and arXiv by problem. It also checks a watch list: the X accounts and GitHub repos whose
   earlier seeds paid off. It uses no curated list of people.
3. It ranks the results by substance: a number against a baseline, code, a method, an ablation.
4. It files at most `seeds` of them as seeds. The others go to the technique library, the `techniques` table.
5. It writes one `finding` on the project, at priority 3, so that it reaches your inbox.

Turn it on with a table. `interval` has no default:

```toml theme={null}
[roles.research.scout]
interval = 86400   # seconds between scout sweeps
seeds = 3          # seeds that one scout sweep may file
```

Ask for one scout sweep with `piloop launch <project> research --scout`. A search tool for X needs an MCP server; see
[MCP servers](/guides/mcp-servers).

## Ask for one more agent

`piloop launch <project> research` (or `watchdog`, or a role that you defined) asks the daemon for one more agent of
that role. The daemon starts
it on its next tick, within `max_concurrent`. Use `--harness` and `--model` to choose the agent's harness and model,
and `--reason` to add a line to its brief.

An agent can do the same with SQL. The `workers` table is the launch queue:

```sql theme={null}
INSERT INTO workers (kind, project_id, state, reason) VALUES ('research', 1, 'requested', 'second opinion on idea#40')
```

## Look at an agent

* `piloop watch` shows the agents with their last output.
* `piloop attach <worker-id>` focuses the agent's tab in the piloop Herdr session and attaches your terminal.
* `piloop prompt <worker-id> "<text>"` types text into the agent's pane.

`piloop attach` needs `runtime = "herdr"`, and `piloop prompt` needs a runtime that can deliver a message, such as
Herdr. With the default `subprocess` runtime, every agent is a background process without a pane.

Each agent's brief, log and transcript are in `~/.piloop/runs/<run>/` for an agent that does a run, and in
`~/.piloop/workers/<worker-id>/` for the sweeping roles.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.