orchestrator-autopilot

Deterministic orchestrator autopilot — keeps a subagent worker fleet at capacity. One queue store + lifecycle + tools; each host loads only its own adapter.

Packages

Package details

extensionskill

Install orchestrator-autopilot from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:orchestrator-autopilot
Package
orchestrator-autopilot
Version
0.7.0
Published
Sep 11, 2026
Downloads
546/mo · 546/wk
Author
atl-eu
License
unknown
Types
extension, skill
Size
933.1 KB
Dependencies
5 dependencies · 1 peer
Pi manifest JSON
{
  "extensions": [
    "./src/hosts/pi-extension.ts"
  ],
  "skills": [
    "./skills"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

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-autopilot

Pi loads ONLY the pi adapter (src/hosts/pi-extension.ts) plus the shared skills — 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 the operating program — the package's own prompts/orchestrate.md, read from the package and sent verbatim, with no slash indirection and no prompt template. The same procedure is available on demand as /orchestrate, a command the extension REGISTERS (pi.registerCommand); it can be run standalone without the harness.

Command precedence (measured, not inferred): on pi the EXTENSION command wins over a same-named file in $PI_CODING_AGENT_DIR/prompts/ — a locally projected /orchestrate (e.g. from your dotfiles) is listed but never runs, so edits to it are silently dead. On opencode it is the reverse: the command FILE wins and must be deleted, not merely ignored. Either way the plugin emits one warning per session naming every competing path and which one actually runs. See docs/AUTOPILOT-32-orchestrate-ownership.md.

opencode (npm plugin)

Wire the SERVER plugin into opencode.jsonc and the decision-panel TUI plugin into tui.json:

// opencode.jsonc
{ "plugin": ["orchestrator-autopilot"] }
// tui.json (the decision panel — optional)
{ "plugin": ["orchestrator-autopilot"] }

The package's default export IS the opencode plugin (src/hosts/opencode-entry.tsOrchestratorAutopilot); pi code is not reachable from that entry. It registers the queue tools (queue_add/queue_list/queue_update/ queue_dispatch/queue_review/queue_steer), autopilot (the on/off/status/capacity toggle, same semantics as pi's /autopilot), flag_for_review, the completion wiring (detached worker process-exit → queue flips + verdict routing), and the auto-dispatch/review/re-dispatch harness. The exports["./tui"] entry registers the TUI decision panel (proposals / human-review views over the SAME queue store). /orchestrate is registered by the TUI plugin itself (slashName: "orchestrate"), not projected into your opencode command config — and on opencode a same-named command FILE shadows it, so a leftover projection must be deleted, not merely ignored. See docs/AUTOPILOT-32-orchestrate-ownership.md.

Runtime prerequisites (all opencode-side — unlike the pi host, the opencode plugin does NOT spawn through pi-subagents):

  1. The subagent backend runtime = opencode itself. The plugin's backend (src/backends/opencode.ts) spawns detached headless workers/reviewers as opencode run --agent worker|orchestrator-reviewer child processes (completion = process exit). Any opencode binary works; pin a launcher that injects secrets/route via AUTOPILOT_OPENCODE_BIN (default opencode) — e.g. an oc wrapper. Framework-owned agents are projected into opencode's global agents dir at plugin load (idempotent, version-stamped).
  2. OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=1 for detached worker runs. The backend also forces it on every spawned child.

State dir — ONE store per mode layer. The server plugin and the TUI panel resolve the queue store through the framework's single resolveStateDir (src/config.ts): AUTOPILOT_STATE_DIR → the config dir's autopilot.host.json stateDir fact → a profile-scoped default. Because both surfaces read the same config dir, the queue tools and the panel read/write the same queue.json (the TUI's approve/refine keys are the server tools' queue_update semantics).

Local development (both hosts)

  1. Clone + install: bun install (Bun required; the test suite is hermetic).
  2. Wire the host you use:
    • pi: add src/hosts/pi-extension.ts to your settings extensions array.
    • opencode: add src/hosts/opencode-plugin.ts to your opencode.jsonc plugin array (requires the runtime prereqs above — the detached oc binary + OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=1).
  3. Run /orchestrate in your orchestrator session. The package owns that command — it is registered by the plugin in both hosts (pi: pi.registerCommand; opencode: a TUI keymap-layer command with slashName: "orchestrate") and injects prompts/orchestrate.md from the package. There is no per-host copy to project, sync or drift-check. If a stale orchestrate.md still sits in a host command dir, the plugin emits one warning naming every competing definition and which one wins. docs/queue-model.md describes what the framework enforces vs. what the agent decides. Your intake sources are FACTS, not procedure: they live in autopilot.config.json under workspace.intake.
  4. The package installs its own agents (reviewer + worker) into the host's agent dirs at activation — version-stamped, idempotent.
  5. Point the orchestrator at the queue store and add the skills/orchestrator-operations/ skill to your agent's skills config for the operating rules. The state dir resolves as AUTOPILOT_STATE_DIR<host config dir>/autopilot.host.json~/.local/state/orchestrator/<profile>. The host config dir is the RUNNING host's own ($PI_CODING_AGENT_DIR for pi; $OPENCODE_CONFIG_DIR, else $XDG_CONFIG_HOME/opencode~/.config/opencode, for both opencode entry points), and <profile> is that dir's basename — so an opencode session can never fall back into the pi profile's store. Declare stateDir there: the fallback is a guess, and the probe says so.

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-entry.ts ← the npm-package default export (exports["."] → the
    │                        opencode plugin opencode loads)
    ├── opencode-plugin.ts← opencode host logic (completion, sweep) + the
    │                        tool()/event() adapter + tick delivery
    └── opencode-tui.ts   ← the decision panel (exports["./tui"]; tui.json)

How the hosts wire it

  • pi: settings.json extensionssrc/hosts/pi-extension.ts (activates: installs the reviewer, registers the queue tools + /autopilot, subscribes to subagent: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, value in <duration>).
  • opencode: opencode.jsonc plugin → the npm spec orchestrator-autopilot (or src/hosts/opencode-plugin.ts in local dev). The default-export adapter (src/hosts/opencode-entry.ts) registers the six queue tools + autopilot + flag_for_review; completion = backend process-exit → handleAsyncComplete → queue flips + verdict routing. Its dispose hook stops the runner/scheduled-off timers. The decision panel loads separately via tui.json (exports["./tui"]src/hosts/opencode-tui.ts).
  • 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.md detects the active host in-session (the /autopilot command = pi) and loads only references/pi.md or references/opencode.md. Add it to your tool's skills config.
  • The flag_for_review handover 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; overrides <host config dir>/autopilot.host.json~/.local/state/orchestrator/<profile>
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