orchestrator-autopilot
v0.6.0
Published
Deterministic orchestrator autopilot — keeps a subagent worker fleet at capacity. One queue store + lifecycle + tools; each host loads only its own adapter.
Readme
Orchestrator Autopilot
Deterministic orchestrator framework: keeps a subagent worker fleet at capacity, routes queue items through a review lifecycle, and ships its own agents — one implementation, multiple hosts (pi + opencode).
The framework is host-agnostic software: a programmatic queue store, a deterministic lifecycle (ticks, completion attribution, verdict routing), and thin per-host adapters. Hosts only wire events, gate state, and deliver ticks — the machinery is shared.
Getting started
pi (official package)
pi install npm:orchestrator-autopilot # or git:github.com/MAnders333/orchestrator-autopilotPi loads ONLY the pi adapter (src/hosts/pi-extension.ts) plus the shared
skills and the /orchestrate prompt — the manifest in package.json scopes
this; opencode code is inert in a pi install. Prerequisite: the pi-subagents
extension must be installed (the backend spawns workers through its RPC).
Then, in a session: /autopilot on — the activation command (ships with the
extension). It enables the tick loop and injects /orchestrate, the operating
program (shipped as a package prompt). /orchestrate can also be run
standalone without the harness.
Prompt precedence: pi loads global prompts before package prompts, so a
locally projected /orchestrate (e.g. from your dotfiles) deterministically
SHADOWS the package one — your customizations win; fresh installs get the
package version.
opencode (npm plugin)
opencode.jsonc:
{ "plugin": ["orchestrator-autopilot"] }The package's default export IS the opencode plugin (src/hosts/opencode-entry.ts
→ OrchestratorAutopilot); pi code is not reachable from that entry. Requires
OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=1 for detached worker runs. The
autopilot tool toggles the harness; the /orchestrate command comes from
your opencode command config (projected separately).
Local development (both hosts)
- Clone + install:
bun install(Bun required; the test suite is hermetic). - Wire the host you use:
- pi: add
src/hosts/pi-extension.tsto your settingsextensionsarray. - opencode: add
src/hosts/opencode-plugin.tsto youropencode.jsoncpluginarray (requiresOPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=1for detached worker runs).
- pi: add
- Load the orchestrator command in your orchestrator session (a slash command
that tells the agent to run the queue loop — the
docs/queue-model.mddescribes what the framework enforces vs. what the agent decides). That command is also where you define YOUR intake sources: the framework nudges a scan when the approved buffer is low, but it does not know where your work comes from (trackers, meetings, diffs, goals — your call). - The package installs its own agents (reviewer + worker) into the host's agent dirs at activation — version-stamped, idempotent.
- Point the orchestrator at the queue store (
AUTOPILOT_STATE_DIRor the defaults below) and add theskills/orchestrator-operations/skill to your agent's skills config for the operating rules.
Architecture
src/
├── core.ts ← Autopilot lifecycle: ticks (dispatch|intake|review),
│ completion attribution, verdict routing, review cap
├── queue-store.ts ← programmatic queue (queue.json), validated transitions
├── backends/ ← runtime executor adapters (the portability seam)
│ ├── types.ts ← SubagentBackend contract
│ ├── pi.ts ← pi-subagents RPC spawn + file control channel
│ └── opencode.ts ← detached `oc run` children (completion = process exit)
├── framework/
│ ├── queue-ops.ts ← the SIX queue tools, host-agnostic (single impl)
│ ├── runner.ts ← shared tick machinery: trigger routing (completion/
│ │ settled/timer/activation) + gate + cooldown — the
│ │ hosts only WIRE their events to it
│ └── tick-router.ts ← the delivery gate (interactive/loaded/busy/
│ compacting + cooldown + message format)
├── agents/ ← framework-owned agents (canonical prompt + installer)
│ ├── install.ts ← agent registry + per-backend projection, version-stamped
│ ├── reviewer/ ← orchestrator-reviewer (Verdict: PASS/FAIL gate, read-only)
│ └── worker/ ← worker (worktree isolation, commit-early, full tools)
└── hosts/ ← one file per host (logic + tool adapter together)
├── pi-extension.ts ← pi extension: tools + ticks + lifecycle events
└── opencode-plugin.ts← opencode plugin: host logic (completion, sweep)
+ the tool()/event() adapter + tick deliveryHow the hosts wire it
- pi:
settings.jsonextensions→src/hosts/pi-extension.ts(activates: installs the reviewer, registers the queue tools +/autopilot, subscribes tosubagent:async-complete, ticks the orchestrator session)./autopilot off in <duration>(e.g.off in 1h30m, max 24h) schedules a shutdown: autopilot stays ON until the deadline, then flips OFF by itself; explicit on/off cancels, status shows the pending deadline, and a restart cannot lose it (the enable-gate backstop fires it late). Same semantics via the opencode autopilot tool (action=off, valuein <duration>). - opencode:
opencode.jsoncplugin→src/hosts/opencode-plugin.ts(registers the six queue tools; completion = backend process-exit →handleAsyncComplete→ queue flips + verdict routing). - Agents install into the host's own agent dirs at activation — the pi
reviewer resolves from the runtime (
PI_CODING_AGENT_DIR/~/.pi/agent/ agents), opencode's from its own agents dir. The framework never invents a shared path; each host owns where its agents live. Idempotent, version-stamped, never hand-edited (framework-managed between markers).
The abstraction seam: the hosts are WIRING ONLY — event sources, gate
semantics, and delivery. All logic is host-agnostic in src/:
framework/runner.ts (trigger routing + the shared deferral + the harness
queue), framework/scheduled-off.ts (the scheduled shutdown: duration parser,
due-check backstop, injected-clock/timer ScheduleManager), framework/core.ts
(the engine: flips, verdicts, ticks), framework/
auto-dispatch.ts (A/B/C automations + the B26 worktree rule),
framework/flag-review.ts (the handover + the deterministic PASS auto-flag),
tools/queue-ops.ts (the six queue tools), config.ts (state-dir resolution,
the per-session toggle, ONE autopilotCommand implementation). The backends
own run identity + completion-event building (buildCompletionEvent / the
opencode equivalent); neither host re-implements the review lifecycle, the
toggle, or the event shape.
Docs
docs/queue-model.md— the AUTHORITATIVE queue model: statuses (proposal / approved / blocked / active / ai-review / human-review / failed / done / rejected), transitions, and the tick behavior (dispatch / intake with proposal-pending suppression / review).skills/orchestrator-operations/— the GENERIC operating skill for consumers (dispatch contract, review-loop judgment, completion standards, the flag_for_review handover). Backend-conditional:SKILL.mddetects the active host in-session (the/autopilotcommand = pi) and loads onlyreferences/pi.mdorreferences/opencode.md. Add it to your tool's skills config.- The
flag_for_reviewhandover tool ships with the package (registered by the pi extension and the opencode plugin).
Config / portability
Canonical defaults; local setups override via env:
| Var | Purpose |
|---|---|
| AUTOPILOT_STATE_DIR | queue store location (default ~/.local/state/orchestrator[-personal]) |
| AUTOPILOT_LIB_DIR | override lib resolution (published layouts) |
| AUTOPILOT_OPENCODE_BIN | opencode launcher (default opencode; a local wrapper can pin its own binary) |
| AUTOPILOT_OPENCODE_RUNS_DIR | opencode run records (default ~/.local/state/orchestrator-opencode/runs) |
| AUTOPILOT_* (config) | AUTOPILOT_MAX_SLOTS, AUTOPILOT_QUEUE_LOW, AUTOPILOT_WORKER_AGENTS, AUTOPILOT_REVIEWER_AGENTS, AUTOPILOT_REVIEW_CAP, AUTOPILOT_SWEEP_INTERVAL_MS |
Tests
bun test # 120 hermetic (fake oc backend, fake pi API)
OPENCODE_E2E=1 bun test # + real `oc run` e2e
PI_E2E=1 bun test # + real pi-subagents e2e