@getpipher/armory-fleet
The armory suite's subagent orchestrator for the pi coding agent — a cross-harness, superpowers-native fleet where every agent is armory-native from birth.
Package details
Install @getpipher/armory-fleet from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@getpipher/armory-fleet- Package
@getpipher/armory-fleet- Version
1.4.0- Published
- Sep 6, 2026
- Downloads
- 2,278/mo · 604/wk
- Author
- rz1989
- License
- MIT
- Types
- extension
- Size
- 614.2 KB
- Dependencies
- 8 dependencies · 0 peers
Pi manifest JSON
{
"extensions": [
"./src/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
@getpipher/armory-fleet
The armory suite's subagent orchestrator for the pi coding agent — a cross-harness, superpowers-native fleet where every agent is armory-native from birth.
Why · Features · Quick start · Architecture · The fleet panel · Workflows-as-code · Cost-aware tiers · Roadmap · Ecosystem
Why
The pi-subagent ecosystem is crowded — nicobailon/pi-subagents (2688⭐), tintinweb/pi-subagents (702⭐), QuintinShaw/pi-dynamic-workflows (287⭐), kky42/pi-flow (66⭐), teelicht/pi-superagents (54⭐). Three structural gaps remain open, and armory-fleet owns all three — then reaches parity on the rest to be the best pi-subagent package in the ecosystem.
| Gap | Status quo | armory-fleet |
|---|---|---|
| Armory-native integration | No external package can integrate with the armory suite; they don't own it. | Agents that sync to armory-todo, hydrate from armory-memory, see via vision, and edit via cursor by default — uncopyable. |
| Cross-harness peers | Only one early attempt runs Claude Code + Pi as peer backends, foreground-only. | First-class dual-arsenal topology — pi and Claude Code spawn as sibling backends from one fleet. |
| Superpowers-native lifecycle | Only one attempt wraps the superpowers skill pipeline, synchronous-only. | The full superpowers lifecycle (brainstorm → plan → implement → review → finish) with checkpoints, quality gates, and lifecycle hooks baked in. |
Beyond those: a fleet TUI, cron/interval scheduling, git worktree isolation, cost accounting, quality gates, workflows-as-code, and a journaled event-bus — all in one package.
Vision, spine, and the 7-SPEC roadmap live in
PRD.md. The landscape deep-read (11+ packages mapped, 5 contenders deep-read) is inresearch/.
Features at a glance
| Capability | What you get |
|---|---|
| 🧬 Armory-native agents | Every child syncs to armory-todo, hydrates from armory-memory, sees via vision, edits via cursor — by default, from birth. No bolt-on. |
| 🏛️ Cross-harness backends | Spawn pi and Claude Code sessions as peer backends from one fleet. Auto-detect Claude; hook-parity keeps both on equal footing. |
| 🦸 Superpowers lifecycle | brainstorm → plan → implement → review → finish, checkpoint-driven, skill-loaded per phase. /fleet-implement <task> runs the whole pipeline. |
| 🎚️ Cost-aware tiers | economy / standard / frontier model tiers with cost caps + context floors. Route cheap work to cheap models, escalate when it matters. Live cost $ + context % per run. |
| 🚦 Quality gates | verification-before-completion, completeness-check, gate, verify — built-in. Register your own. Composite helpers: judgePanel, loopUntilDry, retry. |
| 🧩 Workflows-as-code | Author multi-phase workflows in a JS DSL with agent(), pipeline(), phase(), checkpoint(). 5 builtins ship: adversarial-review, code-review, codebase-audit, deep-research, multi-perspective. Journaled + resumable. |
| 🖥️ Fleet TUI | /fleet opens an interactive panel: Runs, Tiers, Lifecycle, Workflows, Conversation viewer. Live widget, mid-run Steer/Stop, edit-resume, save-as. |
| ⏱️ Scheduling | Cron expressions, intervals, one-shot ISO datetimes. PID-locked scheduler, session-scoped (no catch-up). Background runs on isolated git worktrees. |
| 🔒 Worktree isolation | Background runs get isolated git worktrees (in-place fallback for non-git cwds). Foreground runs share the session cwd. |
| 📒 RunLog + journaling | Every run is journaled; interrupted workflows recover on restart. A results inbox lets the model pull completed background runs. |
| 🔄 Edit-and-resume | Re-run a workflow by replaying the unchanged prefix and re-running only the edited suffix. |
| 📡 Vision built-in | describe_image tool is wired into child sessions — agents can see screenshots and diagrams without leaving the fleet. |
Quick start
Install
armory-fleet is a pi extension — it loads inside pi, no build step.
# 1. Add to your pi packages (~/local-dev/arsenal or your package dir)
pnpm add @getpipher/armory-fleet
# 2. Register in ~/.pi/agent/settings.json
// ~/.pi/agent/settings.json
{
"packages": [
"@getpipher/armory-fleet@0.12.0"
// + its armory siblings: armory-todo, armory-memory, vision, cursor
]
}
# 3. Reload pi (/reload) and open the panel
pi
# inside pi → /fleet
Your first subagent (model-callable tool)
The subagent tool is what the model calls to delegate a focused task. Every child is armory-native by default.
// the agent calls this — not you
subagent({
agent: "general-purpose",
task: "Audit src/auth/ for token-handling bugs; report findings.",
// optional: model, lifecycle, todoId, background, isolation, schedule, maxTurns
});
| Param | Effect |
|---|---|
agent |
Agent definition to spawn (from agents/ or discovered). |
task |
The prompt handed to the child. |
model |
Override the session model. Tip: omit to inherit the session model, or use Ollama/... when the session is on Ollama — don't cross providers. |
lifecycle |
Run the task through a superpowers lifecycle (e.g. default) instead of a single delegate. |
todoId |
Link the run to an existing armory-todo entry. |
track |
Default true (syncs to armory-todo). Pass false only for throwaway lookups. |
background |
Fire without awaiting — run goes to the async pool on an isolated git worktree. |
isolation |
worktree (default for bg in a git repo) · none (in-place) · auto. |
schedule |
Cron (0 9 * * 1-5), interval (30m), or one-shot ISO datetime. Session-scoped, no catch-up. |
maxTurns |
Per-run turn budget (default 20). Raise for complex multi-step tasks. |
Your first workflow
Workflows are plain JS files evaluated in a sandboxed vm realm. The orchestration primitives — agent, parallel, pipeline, phase, gate, judgePanel, loopUntilDry, retry, checkpoint, verify, workflow, log — are injected globals (no imports). The only thing you export is meta.
// ship-feature.js — drop into a workflows/ dir discovered by WorkflowRegistry
export const meta = {
name: 'ship-feature',
description: 'Plan → implement → 3 parallel review angles with a gate',
phases: [{ title: 'Plan' }, { title: 'Implement' }, { title: 'Review' }],
}
phase('Plan')
const plan = await agent('Plan this feature: ' + args.task, { tier: 'economy' })
phase('Implement')
const impl = await agent(`Implement the plan:\n${plan}`, { tier: 'standard' })
phase('Review')
const angles = ['security', 'performance', 'correctness']
const reviews = await parallel(
angles.map((a) => () => agent(`Review the implementation for ${a} issues.`, { tier: 'economy' })),
)
// gate: revise the synthesis until it passes a validator
const synthesis = await gate(
async (_feedback, n) => n === 0
? agent(`Synthesize ${reviews.length} reviews.`, { tier: 'economy' })
: agent('Revise synthesis per feedback.', { tier: 'economy' }),
(v) => typeof v === 'string' && v.length > 200 ? { ok: true } : { ok: false, feedback: 'more detail' },
{ attempts: 3 },
)
return { plan, impl, reviews, synthesis }
Open /fleet → Workflows, pick ship-feature, run it. The panel shows live phase progress; mid-run you can Steer (inject a message) or Stop. The realm also exposes args, cwd, and a budget object ({ total, spent(), remaining() }) so workflows can self-limit.
Your first lifecycle run
/fleet-implement Refactor the auth module to use the new session API --auto
Runs brainstorm → plan → implement → review → finish autonomously. Drop --auto for checkpointed mode (pauses at each checkpoint; continue/revise/abort from /fleet → Lifecycle).
Architecture
┌─────────────────────────────────────────────┐
│ pi host session │
│ (loads @getpipher/armory-fleet extension) │
└───────────────────────┬─────────────────────┘
│
┌───────────────────────────────────────┼───────────────────────────────────────┐
▼ ▼ ▼
subagent tool fleet tool /fleet panel
(model-callable) (workflow runner) (FleetView TUI)
│ │ │
▼ ▼ ▼
createAgentSession() WorkflowController Runs · Tiers · Lifecycle
(pi SDK child) + ConcurrencyPool Workflows · Conversation
│ + adapters + live widget
├─→ armory-todo sync + journal/resume
├─→ armory-memory hydrate │
├─→ vision (describe_image) ▼
├─→ lifecycle + gates backend registry
└─→ tier routing (pi | Claude Code)
Core engine
- Engine primitive:
createAgentSession()from the pi SDK — child Pi sessions, in-memory or file-backedSessionManager,ResourceLoader. Each child is wrapped to emitsession_initon subscribe so the fleet can track it from the first event. - Child loader:
buildChildLoader()threads armory-todo, armory-memory, and vision into every child's resource + tool set — armory-native from birth, cwd-agnostic. - Concurrency: a single-slot lock for foreground runs + a
ConcurrencyPoolfor parallel workflow branches. - Turn budget:
engine/turn-budget.tscaps each child's run; thesubagenttool surfaces exhaustion as a structured status (not a silent truncation).
Armory integration (the uncopyable layer)
| Sibling | What the fleet wires in | Where |
|---|---|---|
| armory-todo | Every run syncs to the cross-session TODO store. Pass todoId to link. |
src/todo-sync/ |
| armory-memory | Children hydrate project memory on spawn. Shared port, cwd-agnostic. | src/memory-hydrate/ |
| vision | describe_image tool is wired into child sessions. |
src/vision/ |
| cursor | Children edit through the cursor extension when present. | (via child loader) |
Cross-harness backends
src/backend/ ships a backend registry with pi (default) and Claude Code as peer backends. detectClaude() auto-discovers Claude; PI_HOOK_PARITY / CLAUDE_HOOK_PARITY tables keep both backends on equal footing. hook-parity.ts normalizes lifecycle/event hooks across harnesses. A ResumeStore persists backend session IDs so cross-harness runs can resume.
Superpowers lifecycle
The default lifecycle (src/lifecycle/default.ts) is the superpowers-native 5-phase pipeline:
| Phase | Skills loaded | Checkpoint? | Gates |
|---|---|---|---|
brainstorm |
brainstorming |
✅ | — |
plan |
writing-plans |
✅ | completenessCheck |
implement |
executing-plans, test-driven-development, verification-before-completion |
❌ | verification-before-completion, completenessCheck, gate |
review |
requesting-code-review, receiving-code-review |
✅ | — |
finish |
finishing-a-development-branch |
— | — |
Custom lifecycles: drop a YAML file in your lifecycles/ dir, register via discoverLifecycles(). Gates are registered on a GateRegistry (fleet-register-gate command for runtime extensibility).
Quality gates
Built-in (src/lifecycle/gates/): verification-before-completion, completeness-check, gate, verify.
Composite helpers (src/workflows/helpers/) — usable from any workflow:
| Helper | What it does |
|---|---|
judgePanel |
Run N judge agents; majority/weighted verdict. |
loopUntilDry |
Re-run an agent until a dry-run gate passes. |
retry |
Retry an agent with backoff on failure. |
checkpoint |
Pause a workflow for human review. |
completeness-check / gate / verify |
Gate wrappers for workflow use. |
Cost-aware tiers
src/tiers/ ships three built-in tiers:
| Tier | Models | Cost cap | Context floor |
|---|---|---|---|
economy |
inherit |
— | — |
standard |
inherit |
— | — |
frontier |
inherit |
— | 200k ctx |
The shipped defaults use the inherit sentinel — each tier resolves to your active session model, so tier routing works on any provider out of the box. To route across models, override a tier by name with a concrete provider/id chain in ~/.pi/agent/fleet/tiers.json (global) or <project>/.pi/fleet/tiers.json (project):
[
{ "name": "economy", "models": ["Ollama/minimax-m3:cloud"] },
{ "name": "standard", "models": ["Ollama/glm-5.2:cloud", "inherit"] },
{ "name": "frontier", "models": ["anthropic/claude-sonnet-4"], "costCap": 5, "contextFloor": 200000 }
]
Models are an ordered fallback chain (primary first; a spawn retries the next candidate if model creation is rejected); the inherit sentinel (case-insensitive) may appear anywhere in the chain as a provider-agnostic fallback and always resolves to the session model without catalog or floor checks. contextFloor skips catalog models below the window size; costCap aborts a run whose live cost exceeds the cap (a no-op on flat subscriptions). Tier routing applies to pi-backend agents — backend: "claude" agents receive the resolved string via --model and the claude CLI expects its own model names, so route those by model: instead.
Live cost $ and context % are tracked per run and surfaced in the Tiers view. Override per-run with model, or let the tier registry route based on the task class.
Fleet settings (v1.1.0)
~/.pi/agent/fleet/settings.json (global) and <project>/.pi/fleet/settings.json (project — wins per-field) hold fleet-wide defaults that aren't env-shaped. Currently one field:
{
"defaultSubagentThinking": "high"
}
defaultSubagentThinking (#78) sets the thinking level for every subagent whose frontmatter does not pin thinkingLevel — so a controller can think at max while the fleet runs at high, without replacing builtin agents (agent-frontmatter overrides still win; per-dispatch overrides are not exposed yet). Valid values: off | minimal | low | medium | high | xhigh | max. On flat subscriptions (z.ai/GLM coding plans, Ollama) this is the quota lever — thinking tokens on every cheap subagent call are pure waste.
Invalid values and unknown keys produce a startup warning naming the file, the field, and the bad value — never a silent no-op. Absent files are normal.
Agent frontmatter thinkingLevel is validated with the same strictness (#90): an invalid value (typo'd level, wrong type) fails that agent's load with an actionable warning naming the file, the bad value, and the valid levels (off | minimal | low | medium | high | xhigh | max) — never a silent no-op. A YAML-empty value (thinkingLevel: with nothing after it) counts as absent.
Operational runtime
src/runtime/ — the async/scheduling spine:
async-runner.ts— background dispatch (fire-and-forget).run-journal.ts+run-log.ts— durable run records;reconcile.tsreattaches orphaned runs on restart.concurrency-pool.ts— bounded parallel branches.results-inbox.ts— the model pulls completed background runs via thefleet_resultstool.resume.ts— scan for resumable runs + workflows.
Scheduling + worktree
src/scheduling/ — cron expressions (expressions.ts), a Scheduler with PID-locking (pid-lock.ts), session-scoped (no catch-up). src/worktree/ — WorktreeService for isolated bg-run worktrees + DiffService for reviewable diffs.
The fleet panel
/fleet opens an interactive TUI panel (TUI-only; in non-interactive modes use the subagent tool).
| View | What it shows |
|---|---|
| Runs | Running + recent subagents; status, cost, context %, agent, model. Action submenu: Steer, Stop, View conversation. |
| Tiers | Per-tier model lists, cost caps, context floors. Configure routing. |
| Lifecycle | Active lifecycle runs; Continue/Revise/Abort at checkpoints. |
| Workflows | Registered workflows + live runs. Run, edit-resume, save-as, view result, checkpoint. |
| Conversation | The full message timeline for any selected run. |
A live FleetWidget can render in the pi footer/overlay for at-a-glance fleet status while you work.
Slash commands
| Command | Purpose |
|---|---|
/fleet |
Open the interactive fleet panel (TUI). |
/fleet-implement <task> [--lifecycle <name>] [--auto] |
Run a task through the superpowers lifecycle. |
/fleet-register-gate |
Register a custom gate on the fleet gate registry (extensibility). |
Model-callable tools
| Tool | Purpose |
|---|---|
subagent |
Delegate a focused task to a child agent (sync foreground or async background). |
fleet |
Run + control fleet workflows (JS orchestration: agent, pipeline, phase, checkpoints). |
fleet_results |
Pull completed background run results from the inbox. |
Workflows-as-code
Workflows are authored in a JS DSL (src/workflows/source.ts parses; vm-realm.ts evaluates). 5 builtins ship in src/workflows/builtin/:
| Workflow | Description | Phases |
|---|---|---|
adversarial-review |
Red-team + blue-team review with judge panel | Attack → Defend → Judge |
code-review |
7 parallel review angles plus verification | Review → Verify |
codebase-audit |
File-tree scan with completeness check | Scan → Audit |
deep-research |
3-round discovery loop with de-duplication | Discover → Synthesize |
multi-perspective |
4 personas review the same artifact | Review → Merge |
Every workflow run is journaled (workflows/journal.ts) and resumable. edit-resume replays the unchanged prefix from cache and re-runs only the edited suffix. runtime/controller.ts orchestrates; runtime/pause-gate.ts handles checkpoints; runtime/adapters.ts binds the controller to the fleet's spawn + accounting.
Full JS DSL API reference: docs/workflows.md — export const meta, agent()/parallel()/pipeline()/phase(), the 7 helpers, the script context, worked examples, and the error surface.
Migration (v0.13.0 — SPEC-6-5 cwd isolation)
cwdparam on thesubagenttool (default = the session cwd, backward-compat). Pass it to scope a child's working dir + context (AGENTS.md cascade, skills, memory) to a dispatch target outside the session cwd — the #20 confabulation fix. Cross-cwd dispatches surface a↗<basename>glyph in the fleet widget + a spawn-time notify.userMemorydefault flip: the global cross-project user memory scope (/__armory-fleet-user__) is no longer hydrated by default. If you populated that dir + relied on it, adduserMemory: trueto the agent frontmatter (only meaningful withmemoryHydrate: true). TS consumers constructingAgentDefliterals must now includeuserMemory: boolean(required field; usefalsefor the old default behavior).- Lifecycle
cwdfield: lifecycles accept an optionalcwdfrontmatter field to pin a target repo; absent → the entry-point cwd (the panel's chosen cwd, or the dispatchingsubagenttool's cwd/session cwd). When present, it overrides the entry-point cwd for all phases. - Panel Run-action: a 3rd
cwdinput step (task → name → cwd), prefilled with the session cwd; Enter accepts, Escape cancels. - bg/scheduled + worktree cwd-isolation (#62): the
cwdparam now scopes background and scheduled runs too — in-place bg runs pass it as the lifecycle entry cwd, andisolation: 'worktree'/'auto'resolve isolation (and create the worktree) against the dispatch cwd's repo, not the session's. Cross-cwd bg worktrees land in<child-cwd>/.pi/fleet/worktrees/.
Event bus + RPC (v1.0)
armory-fleet publishes every run on pi's cross-extension event bus and answers an RPC verb set — so your own extensions can spawn, steer, observe, and abort subagents programmatically.
Event stream (broadcast, pi.events)
Two tiers, every event enveloped as { runId, seq, ts, ...payload } (seq = per-run
monotonic, one space per source store):
| Channel | Payload |
|---|---|
fleet:run:started |
{ agent, model?, cwd?, sessionCwd?, mode: foreground|background|scheduled|workflow, task } |
fleet:phase:started / completed / failed |
{ phase, … } (lifecycle runs) |
fleet:run:ended |
{ status: completed|failed|aborted, result?, error?, filesTouched?, toolCallCount?, durationMs? } |
fleet:child:message |
{ role, text } (journal excerpts) |
fleet:child:tool |
{ toolName, args, result, isError } (one per completed tool call) |
RPC (fleet:rpc → fleet:rpc:result)
Emit { id, verb, params }, get exactly one reply { id, ok, data } or
{ id, ok: false, error: { code, message } }. Verbs: spawn (returns { runId }
immediately; result arrives via fleet:run:ended), schedule (#83 — returns
{ scheduleId, nextFire }; schedules run lifecycles, not single delegates), status,
observe (replay dump — subscribe to the broadcast channels + dedupe by
(channel, runId, seq) for the live tail), steer, abort.
spawn params: agent, task, background?, lifecycle? (named lifecycle — with
background: true routes through the bg runner; without it runs as a detached
foreground-semantics lifecycle), modelFallback? (per-request retry-once on a retryable
provider failure — the retry mints a fresh runId and relinks the primary's todo), cwd?,
isolation?, maxTurns?, model?, skills?, readOnly?, track?, todoId?.
schedule params: task, expression (cron or interval), lifecycle?, auto?,
isolation?, cwd? — no agent: the scheduler runs lifecycles only. Registration emits
no run events; on fire, the normal fleet:* stream flows with mode: "scheduled".
Error codes: E-CONTROL-DISABLED, E-RUN-NOT-FOUND, E-RUN-FINISHED, E-BAD-VERB,
E-BAD-PARAMS, E-STEER-UNSUPPORTED, E-INTERNAL.
Client helper (~15 lines)
type FleetReply = { id: string; ok: true; data: any } | { id: string; ok: false; error: { code: string; message: string } };
let n = 0;
export function fleetRpc(pi: { events: { emit(c: string, d: unknown): void; on(c: string, h: (d: unknown) => void): () => void } }) {
return <T = any>(verb: string, params?: Record<string, unknown>): Promise<T> => {
const id = `fleet-${Date.now().toString(36)}-${++n}`;
return new Promise<T>((resolve, reject) => {
const unsub = pi.events.on("fleet:rpc:result", (raw) => {
const r = raw as FleetReply;
if (r.id !== id) return;
unsub();
r.ok ? resolve(r.data as T) : reject(new Error(`${r.error.code}: ${r.error.message}`));
});
pi.events.emit("fleet:rpc", { id, verb, params });
});
};
}
// const rpc = fleetRpc(pi); const { runId } = await rpc("spawn", { agent: "scout", task: "look" });
Control gate
spawn/steer/abort/schedule are on by default. Set ARMORY_FLEET_RPC_CONTROL=0 (or false) to
reject them with E-CONTROL-DISABLED; read-only observe/status stay available. Honest
threat model: in-process extensions already have full system access through pi itself — the
switch guards accidents, not adversaries.
Live conversation viewer
/fleet → Runs → open a running run: the timeline overlay streams the child's
conversation as it happens (tail-follows; scroll up to read, back to the bottom to re-pin).
Finished runs replay from the journal exactly as before.
The #62 tail of SPEC-6-5: subagent({ cwd }) now scopes background and scheduled runs too, not just foreground —
- bg/scheduled cwd — in-place bg runs pass the dispatch cwd as the lifecycle entry cwd (full context scoping: cascade, skills, memory); schedules persist the pinned cwd and honor it on every fire.
- Per-dispatch worktrees —
isolation: 'worktree'/'auto'resolve against the dispatch cwd's repo, not the session's; cross-cwd worktrees land in<child-cwd>/.pi/fleet/worktrees/and are cleaned up via the same service. - bg lifecycle parity — the bg adapter now honors the lifecycle cwd chain (
lifecycle.cwd→ dispatchcwd), which also fixes a pre-existing mismatch where bg isolated runs spawned phases in the session cwd but committed in the worktree.
RPC parity + fleet settings (v1.1.0)
Post-v1.0 batch (issues #78/#83):
fleet.defaultSubagentThinking(#78) — a fleet-dir settings home (~/.pi/agent/fleet/settings.jsonglobal,<project>/.pi/fleet/settings.jsonproject override) whose first field sets the thinking level for every subagent that doesn't pin its own — the quota lever for flat subscriptions. See Fleet settings.- RPC spawn parity (#83) —
spawngainslifecycle(bg routing, or a detached foreground-semantics lifecycle run) andmodelFallback(per-request retry-once; the retry mints a fresh runId and relinks the primary's todo). - New
scheduleverb (#83) —{ task, expression, lifecycle?, auto?, isolation?, cwd? }→{ scheduleId, nextFire }. Schedules run lifecycles only, sospawn's uniform{ runId }reply stays unbranched; the frozen surface needed zero renames and zero new error codes.
Provider-agnostic tiers (v0.15.0)
Small-backlog batch (issues #57/#63/#64/#65):
- Tier
inheritsentinel (#64) — builtin tiers no longer hardcode a provider:economy/standard/frontiernow resolve to your active session model out of the box (frontier keeps its 200k context floor; the $5 cost cap moved to the override example — a no-op on flat subs). Override by name intiers.jsonwith concreteprovider/idchains for real multi-model routing;inheritcan appear mid-chain as a provider-agnostic fallback. See Cost-aware tiers. - Self-correcting model errors (#57) — dispatching with a model the runtime doesn't have now lists the session's available (authed) models in the error, so the orchestrating model can pick a valid one on the retry instead of guessing.
- Panel Escape semantics documented + dead code removed (#63) — Escape always cancels the active panel flow; defaults are accepted via Enter-on-blank. (Also fixed: ctrl+c could trigger the never-documented "escape accepts default" callbacks.)
- README example tier names fixed (#65) — the
ship-featureexample now uses real tier names (economy/standard).
MCP governance (armory-gateway integration)
When armory-gateway is resolvable, fleet
registers an MCP governance provider at session start: every MCP call made through the
gateway passes fleet's mcpDeny policy before it executes.
~/.pi/agent/fleet/settings.json (global) and <cwd>/.pi/fleet/settings.json (project,
wins per-field):
{
"mcpDeny": [
"github__delete_repo",
"internal-tools"
]
}
- Entries: bare
server(deny the whole server) orserver__tool(deny one exact tool). - Invalid entries produce an actionable warning and are dropped; valid entries stay enforced.
- Policy is re-read per call — edits take effect immediately.
- Gateway absent (the default for public fleet installs)? Nothing changes: registration
is skipped silently and fleet behaves exactly as before. Check the gateway's
statusoutput —interceptors governance=✗means standalone. - Deliberately deferred to later slices: per-call cost accounting, per-agent/per-run policy
scoping, and child-session MCP access (see
docs/SPEC-1b-2-fleet-governance-adapter.md§15 for the rationale and revival conditions).
Dogfood reliability (v0.14.0)
Four fixes from dogfooding the fleet on itself (issues #58–#61):
ARMORY_FLEET_MODEL_FALLBACK=auto— resolve the global fallback per session from the configured+available model snapshot: a different provider than the session model is preferred, else a different model id; unresolvable (single-model setup) stays off with a one-time warning. Non-autoenv values are used verbatim; per-dispatchmodelFallbackstill wins.- No-fallback hint — a retryable provider failure (stopReason
error) with neither a per-dispatchmodelFallbacknor the global default surfacesno modelFallback configured — pass modelFallback or set ARMORY_FLEET_MODEL_FALLBACKso silent no-retry failures are visible. - Masked primary errors fixed — when a fallback retry also fails, the surfaced error now names both attempts (
primary '<model>' failed: …; fallback '<model>' failed: …) instead of only the fallback's. - Zero-tool-call flag (#61) — a run that "completes" without a single executed tool call (the premature-return shape: narrate a plan, end) is prefixed with
[FLEET] zero-tool-call run — likely a premature returnin the tool result;details.toolCallCountexposes the count. Verify (git status/log) before trusting such a result. - Richer run journal —
run:endednow carrieserror(failure reason),filesTouched(#49 parity in the durable journal — real SDK args are captured fromtool_execution_start; the end event has none), andtoolCallCount.
Model-quality drift signals (v1.2.0)
Cheap, additive signals for the output-quality drift observed on flat/cheap model tiers (issues #88/#89). Both are flagging, not blocking — findings were sound every time; the controller decides.
- Language-drift flag (#88) — when a completed run's final report is majority CJK-family script (Han/Hiragana/Katakana/Hangul ≥ 30% of letters, min 40 letters), the tool result is prefixed with
[FLEET] language drift — final report is N% CJK-family script (#88)…anddetails.languageDrift/languageDriftRatioare set. Observed: glm-5.3-flash drifted into Chinese in 4/7 dispatches, even with an explicit English-only instruction in the prompt. Quoted CJK content inside an English report stays clean;run:endedjournals the flag for post-hoc diagnosis. Latin-script non-English (fr/de/…) is deliberately not detected (see the spec's non-goals). - Reviewer cross-domain references (#89, guidance) — reviewer subagents on cheap tiers sometimes fabricate plausible-sounding process references (briefs, roll-ups, sibling-repo fixes) while their verdicts and code findings remain verifiable. Rule for controllers: treat any reviewer reference to an artifact outside the provided brief/report/diff as suspect — verify findings by git + file contents, never by report prose. The fleet's structured result fields (
details.*) and the journal are the trustworthy surface; the narrative tail is not.
Roadmap
armory-fleet follows a PRD → SPEC-N (brainstorm → spec → plan → implementation) pipeline. 16/16 phases done through v0.12.0.
| SPEC | Headline | Status | Artifact |
|---|---|---|---|
| PRD | Master PRD | ✅ done | PRD.md |
| RESEARCH | Landscape research (11+ packages, 5 deep-reads) | ✅ done | research/ |
| SPEC-1 | Core engine + armory-todo sync | ✅ done | PR #1 · 547319b |
| SPEC-2 | Deep armory integration (memory/vision/cursor) | ✅ done · @0.2.0 | PR #2 · c6e727c |
| SPEC-3 | Cross-harness peers (pi + Claude Code) | ✅ done · @0.3.0 | PR #4 · 5bb75fb |
| SPEC-4 | Superpowers-native lifecycle | ✅ done · @0.4.0 | PR #5 · 67ff9b4 |
| SPEC-5a | Operational runtime (async/scheduling/worktree) | ✅ done · @0.5.2 | PR #6 · 52e3477 |
| SPEC-5b-1 | RunLog seam + Runs view | ✅ done · @0.6.0 | PR #7 · 54b1b10 |
| SPEC-5b-2 | Live widget + FleetView + Q9 | ✅ done · @0.7.0 | PR #8 · 9266a7 |
| SPEC-5b-3 | Conversation viewer + timeline fix | ✅ done · @0.8.0 | PR #9 · adc0034 |
| SPEC-5b-4 | Mid-run steering (Steer) + Stop | ✅ done · @0.9.1 | PR #10 + #11 + #12 |
| SPEC-6-1 | Cost-aware tiers + cost $ + context % + Tiers view | ✅ done · @0.10.x | PR #15/#16/#17 |
| SPEC-6-2 | Quality gates + lifecycle hooks | ✅ done · @0.11.0 | PR #18 · cda5e2b |
| v0.11.1 | bg dispatch isolation split (non-git cwd fix) | ✅ done · @0.11.1 | PR #19 · 51956e0 |
| SPEC-6-3 | Workflows-as-code (release-gate completion) | ✅ done · @0.12.0 | PR #21 · 9986ad1 |
| SPEC-6-4 | Event-bus RPC + live conversation viewer → v1.0 | 🚧 next | — |
See the full release history and the PRD §8 for the roadmap rationale.
Ecosystem
armory-fleet is the orchestrator in the getpipher armory suite — the default substrate it runs agents on:
| Package | Role |
|---|---|
| armory-todo | Global cross-session TODO store (the fleet syncs every run to it). |
| armory-memory | Project memory hydration for child agents. |
| vision | The describe_image tool, wired into fleet children. |
| cursor | Custom editor component for the pi TUI. |
Conventions
- No build step — extensions ship raw
.tsvia tsx at pi runtime.pnpm typecheck+pnpm test:runbefore release. - Tests —
node:testvia tsx intest/*.test.mts, importing from../src/.... 593 passing. - Publish — CI on
v*tags using the getpipherNPM_TOKENorg secret (release.ymlmirrors armory-todo: idempotent npm publish + GitHub Release). - Interactive-first UX — every capability lands as a
/fleetpanel tab/view + action submenu first, then the model-callable tool action.
Verify locally
pnpm install
pnpm typecheck
pnpm test:run --test-timeout=30000 # 593/593
Release-gate smoke (mandatory before any release)
pi --no-extensions -e ./src/index.ts --no-session --approve
# inside: /fleet → Workflows → verify the 5 builtins render + a workflow runs end-to-end
Compatibility
- pi
^0.81.1 - Node
>=22(tsx runtime) - Platform: macOS, Linux, WSL
License
MIT — see LICENSE. © RECTOR (@rz1989s).
Built with Ihsan · Maintained by RECTOR · getpipher
The redesign unifies the three fleet surfaces under one visual language: your pi theme's tokens only (accent/text/muted/dim/warning/success/error), a single glyph vocabulary (▶ ⏸ ✓ ✗ ⏳, braille spinners, box-drawing cards), and the usage — honesty rule (missing data renders —, never an estimate).
Glyph presets. Set ARMORY_FLEET_GLYPHS=ascii for a fully glyph-free fallback (dumb terminals — +---+ cards, - spinner, | footer separators), or =nerd for FontAwesome PUA icons (Nerd Font required). Default is unicode. Invalid values fall back to unicode with a one-time stderr warning.
Fleet-tab preview row. With a running run highlighted in the /fleet Fleet tab, a line under the list mirrors the transcript card's state line exactly (same segments, unthemed); it blanks when nothing running is selected.
In-transcript run cards. When a subagent dispatch fires, the tool row becomes a live card — spinner, agent, model, task, and a state line (last event · turn · elapsed · tok · ctx%) driven by the child run's events. On settle it collapses to one honest line:
╰─ ✓ reviewer · 4m12s · 598K tok · $0.30 · ✎3 · verdict: Ship
(Expand — the native tool-expand key — for the full envelope; failed runs show ✗ … · — · — with the reason.)
Orchestration + findings entries. While a burst of runs is live, a TUI-only entry (zero LLM tokens) shows the waiting-on tree, the fleet TODO projection, and the gate state; when the burst settles, a findings block records each run's outcome plus degradations (fallback used, zero-tool flag, language drift):
── findings ────────────────────────────────
✓ reviewer 4m12s 598K tok $0.30 — Ship
✗ scheduler — — — worker exited without result (TODO reverted ⚠)
Widget + panel. The above-editor widget is now a colorized component (totals strip when >1 active; one status-token segment per run). The panel gains a totals header, status-colored rows, a state-machine footer (keys that matter now), and capability-aware actions (aborted runs offer re-run, not stop). All existing keybindings are unchanged.
P2 (structure): t lineage tree (Runs+Fleet), real-width overlays, live scroll separator, unified run-card frame (#108).