Every key usine.yaml accepts, with its type, default, valid values, and what the orchestrator does with it.
usine.yaml is at the top of the instance folder, next to the loops/ and agents/ folders. It contains the registry of managed repositories, the labels usine creates, the Board lanes, factory-wide settings, and the Workers that run Attempts.
The orchestrator reads it once, when usine up starts. A change takes effect at the next start.
The parser is strict:
- Keys are snake_case in the file. The parser renames them to camelCase domain fields (
Schema.encodeKeys), sopoll_intervalin the file ispollIntervalin code. - An unknown key at any level is an error (
onExcessProperty: "error"). - Duplicate keys and YAML parser warnings are errors (
parseYamlDocument). - An invalid
usine.yamlstops the whole instance from loading:usine uplogs aFactoryConfigErrorand exits with status 1. An invalid Loop or Agent file affects only that file; see Loop files.
The schema is UsineConfig in packages/config/src/factory.ts.
Complete example
Section titled “Complete example”# Schema version of this file. Required; 1 is the only accepted value.version: 1
# GitHub login recorded on operator interventions (steer, interrupt, messages).operator: octocat
# Labels usine creates in every managed repository that doesn't have them.labels: - name: ready-for-agent color: "0e8a16" description: An Agent may start this issue - name: agent-in-progress - name: ready-for-review - name: agent-review-findings - name: review-passed - name: ready-to-merge
# The registry: the repositories this factory manages.repos: - name: acme/widgets tags: [web] lanes: - name: Ready sources: [{ label: ready-for-agent }] - name: In progress sources: [{ label: agent-in-progress }] - name: Review sources: [{ label: ready-for-review }] - name: Findings sources: [{ label: agent-review-findings }] - name: Passed sources: [{ label: review-passed }] - name: Merge sources: [{ label: ready-to-merge }] - name: Pull requests sources: [{ state: pr-open }] - name: Done sources: [{ state: pr-merged }, { state: pr-closed }, { state: issue-closed }] - name: acme/gadgets tags: [web, mobile]
settings: poll_interval: 60 # seconds between poll cycles max_children: 4 # child sessions per Attempt max_attempt_minutes: 180 # omit for no wall-clock ceiling max_attempt_tokens: 20000000 # omit for no token ceiling parent_model: openai-codex/gpt-5.6-terra parent_effort: medium
# Where Attempts run. Omit the key for one local Worker named `local`.workers: - name: worker-1 kind: ssh destination: usine@203.0.113.10 identity_file: ~/.ssh/usine_worker root: /home/usine/.usine max_concurrent_attempts: 2 gateway: domain: gateway.example.com admin_url: http://127.0.0.1:7070 admin_token_env: USINE_GATEWAY_ADMIN_TOKEN llm_providers: [anthropic] - name: worker-2 kind: ssh destination: worker-2 port: 2222 root: /srv/usine
webhooks: mode: offThe smallest valid file sets version and repos, and repos can be an empty list. Every other key has a default:
version: 1repos: - name: acme/widgetsHow the instance files reference each other
Section titled “How the instance files reference each other”A Loop file references an Agent file and selects repositories from the registry. usine checks the label names in Lanes and in Loop conditions against the declared labels.
flowchart LR
loop["loops/name.md"]
agent["agents/name.md"]
subgraph config["usine.yaml"]
repos["repos[].name and repos[].tags"]
labels["labels[].name"]
lanes["repos[].lanes[].sources[].label"]
end
loop -- "agent" --> agent
loop -- "selector.repos, selector.tags" --> repos
loop -. "label names compared in trigger.if" .-> labels
lanes -. "label" .-> labels
A solid arrow is a reference that must resolve: usine quarantines a Loop whose Agent, selected repository, or selected tag doesn’t exist. A dotted arrow only logs a warning when the label isn’t declared (configWarnings).
Top-level keys
Section titled “Top-level keys”| Key | Type | Default | Description |
|---|---|---|---|
version |
1 |
required | Schema version. Any other value is an error. |
operator |
GitHub login | none | The login recorded as the author of operator interventions. |
labels |
list of labels | [] |
Labels created in every managed repository. |
repos |
list of repositories | required | The registry. An empty list is valid. |
settings |
settings | {} |
Polling, Attempt limits, and parent defaults. |
workers |
list of Workers | one local Worker named local |
Where Attempts run. Must not be empty when present. |
webhooks |
webhooks | { mode: off } |
Parsed and validated; not read by the orchestrator at this commit. |
operator
Section titled “operator”A GitHub login: 1 to 39 letters, digits, or hyphens, starting and ending with a letter or digit (OperatorLogin). When the operator steers, interrupts, or messages a session from the UI, the transcript records this login as the sender. Without it the transcript records local operator (operator.ts).
labels
Section titled “labels”Each entry declares one GitHub label (DeclaredLabel).
| Key | Type | Default | Description |
|---|---|---|---|
name |
string | required | At most 50 characters, no leading or trailing whitespace. Names must be unique, compared case-insensitively. |
color |
string | none | Six hex digits without #, such as "0e8a16". |
description |
string | none | At most 100 characters. |
During each poll cycle, usine compares the declared labels with each managed repository’s labels, case-insensitively. It creates a declared label that the repository doesn’t have, with the declared color and description (LabelReconciler). When an existing label’s color or description differs from the declaration, usine logs a warning and leaves the label unchanged. usine never edits or deletes a label.
Quote colors. YAML reads 123456 as a number and 0e8016 as a float, and the parser rejects both with Expected string.
labels: - name: ready-for-agent color: "0e8a16" description: An Agent may start this issueEach entry is one managed repository (ManagedRepo). Names must be unique.
| Key | Type | Default | Description |
|---|---|---|---|
name |
string | required | Full owner/name of the GitHub repository. Letters, digits, _, ., and - on each side of the slash. |
tags |
list of strings | [] |
Tags that Loop selectors match. Each tag is non-blank; tags are unique within a repository. |
lanes |
list of lanes | [] |
The Board columns for this repository. |
The GitHub App installation must cover every repository in the list. A Loop applies to every repository unless its selector narrows it.
repos[].lanes
Section titled “repos[].lanes”Each lane is one Board column (Lane).
| Key | Type | Default | Description |
|---|---|---|---|
name |
string | required | Non-blank. Unique within the repository. |
sources |
list of sources | required | At least one. Each source is a mapping with exactly one key, label or state. |
| Source | Valid values | Description |
|---|---|---|
label |
a GitHub label name | Places Items that have this label in the lane. A Board move into the lane adds the label. |
state |
issue-open, issue-closed, pr-open, pr-closed, pr-merged |
Places Items in this GitHub state in the lane. A Board move can’t change a state. |
A source may appear in only one lane of a repository. The Board places each Task as follows (laneFor):
- A Task whose current Item is a merged or closed pull request, or a closed issue, goes to the first lane with a matching
statesource. - Any other Task goes to the first lane, in file order, that has a
labelsource matching one of the Item’s labels. If no lane matches, it goes to the first lane with a matchingstatesource. - A Task that matches no lane goes to the
Unmappedcolumn.
A lane with at least one label source accepts Board moves. Moving a card adds the target lane’s labels and removes the labels of every other authored lane (labelMutationForBoardMove).
When lanes is empty, the Board proposes one read-only lane per label in the repository, sorted by name (effectiveLanes).
A label source that isn’t declared under labels still loads, and usine logs Board Lane references an undeclared GitHub label.
repos: - name: acme/widgets lanes: - name: Ready sources: [{ label: ready-for-agent }] - name: Done sources: [{ state: pr-merged }, { state: issue-closed }]settings
Section titled “settings”Factory-wide values (Settings).
| Key | Type | Default | Description |
|---|---|---|---|
poll_interval |
integer ≥ 1 | 60 |
Seconds between the starts of two GitHub poll cycles. A GitHub rate limit can push the next cycle later. |
max_children |
integer ≥ 1 | 4 |
The Child-session budget: child sessions one Attempt may spawn over its lifetime, sequential and concurrent together. The next spawn fails the Attempt. |
max_attempt_minutes |
integer ≥ 1 | none | Wall-clock ceiling per Attempt, in minutes. Time while the operator holds a child doesn’t count. Also the drain deadline when usine up stops. |
max_attempt_tokens |
integer ≥ 1 | none | Token ceiling per Attempt, summed over the parent and every child. |
parent_model |
string | none | Model of each Attempt’s Pi parent, as provider/model, when the Agent sets no parent_model. |
parent_effort |
effort | none | Reasoning effort of the Pi parent when the Agent sets no parent_effort. |
max_attempt_minutes and max_attempt_tokens arm the runaway guard (attemptGuard). Each armed ceiling sends the parent one warning at 80% of its limit and fails the Attempt at the limit. usine doesn’t retry a runaway failure automatically.
The effort values are off, minimal, low, medium, high, xhigh, and max. usine picks the parent’s model in this order: the Agent’s parent_model, then settings.parent_model, then Pi’s configured default model, then the first Pi model with a configured credential. It picks the parent’s effort from the Agent’s parent_effort, then settings.parent_effort, then the Agent’s own effort (resolveParentEffort). An Attempt fails before its conversation starts when Pi doesn’t know the model or the model doesn’t support the effort.
workers
Section titled “workers”Each entry is one Worker. When the key is absent, the instance has one Worker: { name: local, kind: local }. When present, the list must have at least one entry and the names must be unique (Worker).
Every kind takes these keys:
| Key | Type | Default | Description |
|---|---|---|---|
name |
string | required | Kebab-case: lowercase letters and digits separated by single hyphens. |
kind |
local, ssh, exe.dev |
required | Selects the Worker adapter and the other keys it accepts. |
max_concurrent_attempts |
integer ≥ 1 | 1 |
Attempts the Worker runs at the same time. |
Runs wait for a free Worker slot in the order their Events arrived (stepExecutorLayer). At startup, usine up runs Preflight on every Worker for every harness in the Agent roster. A failed Preflight stops startup.
kind: local
Section titled “kind: local”The machine the orchestrator runs on. It accepts only the common keys. It keeps cached clones, Attempt worktrees, and a Turbo cache under .usine/workers/<name>/ in the instance folder.
workers: - name: local kind: local max_concurrent_attempts: 2kind: ssh
Section titled “kind: ssh”A Linux machine that the orchestrator connects to with its local ssh client (SshWorker). The client runs with BatchMode=yes, so the key must work without a prompt.
| Key | Type | Default | Description |
|---|---|---|---|
destination |
string | required | A host name, an address, or a Host alias from the operator’s SSH configuration, optionally prefixed by user@. A value starting with - is rejected. |
identity_file |
string | none | Private key passed as ssh -i, with IdentitiesOnly=yes. An absolute path or a path starting with ~/, without spaces. |
port |
integer 1 to 65535 | none | Passed as ssh -p. Without it, the SSH configuration or port 22 applies. |
root |
string | required | Absolute directory on the machine, without spaces or a trailing /. The Worker keeps its clones, worktrees, and credentials under <root>/workers/<name>. |
gateway |
Gateway | none | The Gateway in front of the machine. |
workers: - name: worker-2 kind: ssh destination: usine@203.0.113.10 identity_file: ~/.ssh/usine_worker port: 2222 root: /srv/usineworkers[].gateway
Section titled “workers[].gateway”A Worker behind a Gateway has no GitHub token on its machine. The executor attaches each Attempt’s token to the Gateway instead (WorkerGateway).
| Key | Type | Default | Description |
|---|---|---|---|
domain |
string | required | The Gateway’s private DNS domain, in lowercase. The machine resolves git.<domain> for GitHub, llm.<domain> for LLM providers, and <domain> for the forward proxy. |
admin_url |
URL | required | The URL the orchestrator uses for the Gateway’s admin API, often a local port forward. http or https, without a user name or password. |
admin_token_env |
string | required | Name of the orchestrator’s environment variable that contains the admin token. The file never contains the token itself. |
llm_providers |
list | [] |
Providers whose credential is stored on the Gateway. The only value is anthropic (Claude Code). |
usine up reads the variable named by admin_token_env at startup. When it’s unset, startup fails with a gateway capability error that includes the Worker and variable names (sshWorkerAddressesOf).
A harness whose provider isn’t in llm_providers uses the operator’s login on the Worker and connects to its provider through the Gateway’s forward proxy. Codex always works this way.
workers: - name: worker-1 kind: ssh destination: worker-1 root: /home/usine/.usine gateway: domain: gateway.example.com admin_url: http://127.0.0.1:7070 admin_token_env: USINE_GATEWAY_ADMIN_TOKEN llm_providers: [anthropic]kind: exe.dev
Section titled “kind: exe.dev”An exe.dev VM named after the Worker (ExeDevWorker). It accepts only the common keys. The parser accepts this kind, but usine up doesn’t run it at this commit; see the caution above.
webhooks
Section titled “webhooks”Sets where GitHub hints would come from (Webhooks). The parser validates the key, but the orchestrator doesn’t read it at this commit; usine detects GitHub changes only by polling.
| Key | Type | Default | Description |
|---|---|---|---|
mode |
off, deliveries, endpoint |
off |
The hint source. |
public_url |
URL | required when mode is endpoint |
Not accepted with the other modes. |