orchestrator-autopilot
Deterministic orchestrator autopilot — keeps a subagent worker fleet at capacity. One queue store + lifecycle + tools; each host loads only its own adapter.
Package details
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.ts
→ OrchestratorAutopilot); 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):
- The subagent backend runtime = opencode itself. The plugin's backend
(
src/backends/opencode.ts) spawns detached headless workers/reviewers asopencode run --agent worker|orchestrator-reviewerchild processes (completion = process exit). Any opencode binary works; pin a launcher that injects secrets/route viaAUTOPILOT_OPENCODE_BIN(defaultopencode) — e.g. anocwrapper. Framework-owned agents are projected into opencode's global agents dir at plugin load (idempotent, version-stamped). OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=1for 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)
- 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 (requires the runtime prereqs above — the detachedocbinary +OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=1).
- pi: add
- Run
/orchestratein 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 withslashName: "orchestrate") and injectsprompts/orchestrate.mdfrom the package. There is no per-host copy to project, sync or drift-check. If a staleorchestrate.mdstill sits in a host command dir, the plugin emits one warning naming every competing definition and which one wins.docs/queue-model.mddescribes what the framework enforces vs. what the agent decides. Your intake sources are FACTS, not procedure: they live inautopilot.config.jsonunderworkspace.intake. - 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 and add the
skills/orchestrator-operations/skill to your agent's skills config for the operating rules. The state dir resolves asAUTOPILOT_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_DIRfor 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. DeclarestateDirthere: 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.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→ the npm specorchestrator-autopilot(orsrc/hosts/opencode-plugin.tsin 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. Itsdisposehook stops the runner/scheduled-off timers. The decision panel loads separately viatui.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.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; 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