The Orchestrator is the control plane. It decides what runs, with what, when and where. It keeps the record of what happened. It never executes an automation. When a job is ready the Orchestrator spawns the Robot's own run-package command and reads the summary the Robot prints back.
#What it owns
- The queue and its work items, in
queues/andtransactions/. - Schedules, in
schedules/, with the dispatch loop that fires them. - The credential vault in
vault.json, with the key material that seals it. - The Robot registry in
robots/, including capability and environment binding. - Job records in
jobs/, with the evidence index each run writes. - Deployments and activations, in
deployments/, the control plane that decides which package a slot version resolves to. - The Action Centre in
actions/, the record of human decisions. - The mailbox ingest, which reads a mailbox at the seam.
- State inspection and garbage collection.
#Where its state lives
Everything sits under one state root. The default is orchestrator/state inside the project. TS_ORCHESTRATOR_ROOT overrides it. orchestrator run <jobdef.json> --root <stateRoot> accepts it per invocation. Two processes must point at the same root to see the same work.
There is no database by default. State is JSON files and append-only journals, written through an atomic write that writes a temporary file and renames it into place. A write that cannot land throws instead of vanishing. The one exception is the queue: setting TS_QUEUE_PROVIDER=sqlite swaps the queue and transaction queue onto <root>/queue.db. An unset or empty value keeps the file provider. An unknown value is refused by name rather than falling back.
The state root holds jobs/, evidence/, queues/, transactions/, schedules/, robots/, deployments/, actions/, requests/, mailbox/, vault.json, principals.json, approvals.json, governance.ndjson and action-centre.ndjson.
#How it relates to the Robot
The Robot contract is frozen and the Orchestrator sits beside it, not inside it. A run travels through the same channel Studio uses: the Orchestrator spawns robot run-package <package-dir> --runId <id> --artifacts <evidenceRoot> under the project's own tsx loader, with parameters and credentials delivered as environment variables. Credentials arrive as TS_CRED_<NAME>; nothing else about the vault reaches the child.
A job moves queued → claimed → running → completed or failed. A failure carries a failureClass so the reason is machine-readable: deployment, resolution, capacity, execution or claim. The runId names the evidence directory. jobs show <id> prints the row with its lineage.
The Robot is deliberately ignorant of the Orchestrator. It has no registry, no queue and no vault of its own.
#The commands
npx tsx orchestrator/cli.ts run <jobdef.json> # enqueue + dispatch one job
npx tsx orchestrator/cli.ts jobs list # recorded jobs
npx tsx orchestrator/cli.ts jobs show <id> # one job, with credential lineage
npx tsx orchestrator/cli.ts robot register <id> --capabilities web,excel
npx tsx orchestrator/cli.ts robot heartbeat <id>
npx tsx orchestrator/cli.ts robot list
#Things that catch people out
The manual orchestrator run path is the ungoverned one. A scheduled job or a label-pinned job resolves through an activation in deployments/. Those activations are managed through the Orchestrator server, not this CLI. A label schedule whose state root has no activation will refuse.
The Orchestrator is a local service, not a hosted control plane. There is no multi-tenant cloud and no shared broker. Two schedulers pointed at one root are serialised by the tick lock; two pointed at different roots will double-fire the same cron expression.