orchestrator.yaml reference

orchestrator.yaml lives at the repository root and declares the schedules for periodic role dispatches, gate-less "sweeps" that are not tied to a pipeline run. The first (and currently only) scheduled role is the Historian, which periodically reconciles documentation with run artifacts. This file is committed project policy, not per-instance configuration: any orchestrator instance pointed at the same repo derives the same sweeps.

Where and how it is read

The v1 orchestrator reads orchestrator.yaml from the default-branch tip, never from a checkout and never from the current branch. On each tick, the scheduler calls this.git.show(defaultBranch, 'orchestrator.yaml'). If the file is absent, the scheduler returns an empty schedule list. Parse errors are logged, never guessed at. That is the bounce rule applied to config.

Key: schedules

A map of role name → schedule entry. Each entry has one required key and two optional keys:

KeyRequiredTypePurpose
every yes string Interval between sweeps: <n>d | <n>h | <n>m. Sub-daily intervals clamp to one sweep per day (the slug is date-grained). Example: 7d. A missing or invalid value is a parse error and the role is skipped.
cost_limit_usd no number | absent Pre-flight cap for one sweep dispatch. Absent → no cost-cap check at all (the SB rule is skipped when cost_limit_usd is null). When present, a role estimate from the registry exceeding this cap is a config defect — the scheduler skips rather than dispatching (rule SB).
enabled no boolean Whether sweeps for this role are active. Default true. Disabled → rest (rule S0).

Interval grammar

Intervals are parsed by parseEvery() in schedule.ts. The grammar is ^(\d+)([dhm])$: a positive integer followed by one of d, h, or m (days, hours, minutes). The input is trimmed first. The function returns milliseconds, or null when the value does not match the grammar or the integer is zero, which the scheduler treats as a parse error.

Example

schedules:
  historian:
    every: 7d
    cost_limit_usd: 5
    enabled: true

Sweep lifecycle

A sweep is a gate-less mini-run: runs/<role>-<date>/ on branch run/<role>-<date>, seeded with a sweep.yaml marker. It carries no state.yaml on purpose: the gate engine and frontend recognize runs by state.yaml, so sweeps stay out of the derivation table entirely. The human surface is the branch itself: review the docs-delta and doc edits, and merge to approve (the P4 principle). The merged marker on the default branch makes the next sweep's interval derivable.

The marker carries these fields:

FieldPurpose
sweep, roleThe sweep slug and the scheduled role
every, atThe interval in force and the dispatch time (ISO-8601)
covering_sinceThe point the previous merged sweep covered up to; null on a first sweep
adapter, model, cost_limit_usdWhat ran and under what cap
tokens_in, tokens_out, cost_usdReal usage, written by the closing commit; null until then
failedPresent only when the dispatch did not complete, with the truncated error

Sweep derivation rules (S0–S4+SB)

The scheduler applies one rule per schedule entry per tick; each rule has a test in test/schedule.test.ts:

RuleConditionAction
S0Schedule disabledRest — no sweep for this role
S1A sweep branch for this role is open (unmerged)Rest — one in flight or awaiting human review per role
S2Interval since the last merged sweep has not elapsedRest — not yet due
S3Today's sweep branch already existsRest — duplicate guard; sub-daily intervals clamp to daily
SBRole estimate exceeds the schedule's cost capSkip + warn — a config defect, not a dispatch
S4Otherwise (none of the above)Dispatch the sweep

S1 and S3 read local branches and remote-tracking refs (refs/remotes/*/run/<name>) alike, and the caller fetches with --prune first, so a sweep open only on origin still counts as open.

The dispatch itself follows commit-then-launch: the intent commit seeds the sweep branch with its marker, created from ZERO_OID so two instances racing the same schedule resolve at the ref, and the loser rests. After the agent completes, a closing commit records real usage into the marker.