Skip to content

About

No description, website, or topics provided.

Resources

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Latest commit

 

History

688 Commits

Folders and files

Repository files navigation

pi-conductor

Portable agent orchestration for long-horizon coding work. pi-conductor brings Roo/Zoo-style multi-role workflows to pi without tying orchestration to an editor. Run cost-controlled workflows across budget and frontier models, local or remote providers, and terminal-native environments like SSH and tmux.

Status: pre-release. pi-conductor ships as a pi extension — install it with pi install, type /conduct <goal>, and it orchestrates a multi-role LLM workflow on top of a guarded, observable handoff state machine. The pure FSM core + SDK host driver are the engine; the extension is the UX shell around it.

Contents

What this is

pi-conductor orchestrates multi-role LLM workflows as a deterministic hub-and-spoke state machine: one orchestrator role dispatches to one or more worker roles, every transition is validated against a pinned manifest snapshot, every state change is reduced through a pure reducer, and every record is appended to a run-keyed log. Caps (per-session, per-run, per-worker visit count) are enforced as host guards that synthesize machine events through the reducer — never by mutating the checkpoint.

It ships as a pi package:

pi install ./           # from this checkout (dev)
# or, once published:
pi install npm:pi-conductor
pi install git:github.com/lynellf/pi-conductor

After install, seven slash commands are available inside any pi session:

/conduct <goal>          Start a run for <goal> using .pi/conductor.yaml
/conduct:resume <run_id> Resume a previously-started run by run_id
/conduct:list            List known runs in the conductor log
/conduct:abort           Abort the active run
/conduct:steer <message> Guide the active role before its next model call
/conduct:followup <message> Queue guidance for the next conductor prompt boundary
/conduct:copy            Copy the latest completed role response

Plus a flag:

--conduct-manifest <path>          Override the default manifest path

A thin CLI fallback (bin/conduct) also ships, for non-pi consumers and scripted runs:

node dist/bin/conduct.js .pi/conductor.yaml "ship the changelog"

The CLI resolves its Pi SDK from PI_PACKAGE_DIR when set, otherwise from locally installed peers, then from the npm Pi installation behind pi on PATH. This supports pi install npm:pi-conductor, which omits host-provided peer dependencies. If pi is a shell wrapper or its SDK cannot be discovered, set PI_PACKAGE_DIR to the directory containing Pi's package.json and its importable SDK. An invalid override is reported as an error.

The CLI also provides a machine-safe mode for benchmark adapters and other noninteractive callers:

conduct \
  --non-interactive \
  --log-dir /tmp/pi-conductor/run-123 \
  --json \
  .pi/conductor.yaml \
  "Implement the requested repository change."

--non-interactive makes ask_user fail immediately instead of reading stdin. When --log-dir <path> is omitted, the CLI writes durable run logs to <cwd>/.pi-conductor/runs (creating missing parents); --log-dir <path> selects an explicit persistent run-log directory and creates missing parents. The separate offline advisory report reads every *.jsonl log in a runs directory and does not make network requests or mutate the logs:

conduct advisory-report <runs-dir>
conduct advisory-report <runs-dir> --json

In both text and --json run modes the CLI writes one immediate run_started NDJSON event to stdout once the run handle resolves and before completion: {"schema_version":1,"event":"run_started","run_id":"…","log_dir":"…"} with an absolute log_dir. Under --json, stdout is therefore an NDJSON stream of two documents: that run_started event followed by the existing versioned terminal JSON result; prompts, warnings, and diagnostics use stderr. In text mode the same run_started line is followed by the existing human-readable terminal line. Normal conductor terminal outcomes (done, session_failed, and aborted) retain exit code 0 and are distinguished by exit_reason; setup and unexpected runtime errors remain nonzero. While a run is active, the first SIGINT or SIGTERM requests a graceful abort so terminal state can be persisted; a second signal exits immediately.

Resume a previously started run from its durable log:

conduct resume [options] <manifestPath> <runId>
conduct resume --log-dir /tmp/pi-conductor/run-123 .pi/conductor.yaml <run_id>

resume accepts the same --non-interactive, --log-dir, --json, and approval options as start, resolves the log directory with the same default and override rules, restores the original goal from the durable log, and emits the same immediate run_started event plus the same terminal result.

The engine is the same in all three surfaces — extension, CLI, and library.

Portable foreground execution

All hosts use bounded SDK file workers and foreground subprocesses. There is no native addon, compiler requirement, process-table scan, origin/owner admission, or Linux-only execution tier. Commands retain deadlines, bounded output and finite best-effort cancellation. Descendant cleanup is not guaranteed.

Ordinary interrupted execution history does not block resume or replacement, and commands are never automatically replayed. Inspect partial effects before explicitly requesting new work. Persistence ambiguity, budgets, FSM validation, tool grants and effect authorization still apply. Historical strict policy is accepted without restoring removed process policing. Git-backed workspaces and file-tool delegation no longer require Linux ownership/permission admission.

The Bubblewrap backend and built-in executable controller delivery were removed, not weakened. Explicit requests (including their approval flags) fail with migration guidance; they never silently run without isolation. See execution controls, controller migration, and the approved removal scope. Upstream Pi, external tools and user-authored commands retain their own platform requirements. macOS/Windows live acceptance must come from their CI runners, not from simulated capability tests.

Removed command sandbox and executable controller

Bubblewrap command delegation, fixed sandbox verification and built-in executable controllers are unavailable. Remove their explicit configuration before using ordinary roles and file-only delegated children. Custom portable Host/controller integrations can still use the engine's authorization and effect contracts; the production host does not provide OS isolation.

Two layers, kept strictly apart

  • Pure core (src/core, src/manifest, src/seam, src/cost, src/persistence) — the deterministic FSM reducer + manifest static checks + TypeBox emission schemas + cost roll-up. Zero pi imports. Enforced by a grep-guard test that scans source as text.
  • SDK host driver (src/host) — owns the orchestration loop, persists records, and enforces caps. Shared roles use the in-process SDK createAgentSession path; isolated worktree and copy roles use a host-owned package-local pi --mode rpc Node process whose current working directory is the provisioned role workspace.

The extension layer (extensions/conduct.ts + src/extension/) is the UX shell that wraps the engine. It does not become the engine: the production Host launches every worker role through the shared SDK or isolated RPC path, never via ctx.newSession() / ctx.fork(). A grep guard on extensions/**/*.ts rejects those two calls — the §9.5 boundary holds. While a conduct run is active in the TUI, press Esc and confirm to abort it; the standalone conduct CLI does not add that Escape interrupt.

For the full architecture rationale, see docs/archive/orchestrator-fsm-spec.md (the authority).

Quick start

1. Install

pi install ./                       # from the checkout, dev install
pi list                             # verify: pi-conductor should appear

2. Declare roles

Roles live in a single YAML manifest, .pi/conductor.yaml. The repo ships an example:

version: 1
end_request_roles: [reviewer]
roles:
  - name: orchestrator
    is_orchestrator: true
    models: [anthropic:claude-sonnet-4-5]
    max_run_cost_usd: 25.0
    system_prompt: .pi/roles/orchestrator.md
    tools: [read, bash, handoff, end]

  - name: implementer
    max_visits: 3
    max_session_cost_usd: 5.0
    models:
      - model: anthropic:claude-opus-4-5
        effort: high                       # explicit; effort defaults to "medium" when omitted
      - openai:gpt-4o                     # legacy shorthand → { model, effort: "medium" }
    system_prompt: .pi/roles/implementer.md
    tools: [read, edit, write, bash, handoff, end]

  - name: reviewer
    max_visits: 3
    system_prompt: .pi/roles/reviewer.md
    tools: [read, grep, handoff, end]

Optional shadow-only delegation advisory

An operator may opt into bounded, advisory-only TypeSafe judgments by adding this strict block to the top level of a manifest that already has a role with a delegation policy:

delegation_advisory:
  schema_version: 1
  provider: typesafe_jev
  model: jev-latest
  mode: shadow
  max_parallel: 4
  request_timeout_ms: 5000
  max_attempts: 1

Add a short description (1–500 characters) to each allowed subagent profile to make it eligible for profile-fit comparison. For example, add this field to an existing subagents: entry:

- name: api-implementer
  description: Implements one bounded API contract and its tests.

Profile fit is asked only when the parent allows at least two profiles and each allowed profile has a description; the host never uses system_prompt as a substitute. The advisory records never affect admission, prompts, scheduling, child-result normalization, or routing. Read the operator disclosure before enabling the block; it details the outbound data boundary and rollback.

3. Write role prompts

Each role's system prompt is a plain-prose .md file at the declared system_prompt path. The host loads it via DefaultResourceLoader({ systemPromptOverride }) and feeds it to the role's session. See the shipped defaults at tests/fixtures/default-conductor/.pi/roles/. A role prompt tells the role which tools it has, what its legal handoff target is, and whether it may request completion. The host force-injects both handoff and end into every role; workers return through handoff, while only the orchestrator can finalize a run.

Accepted handoffs deliver structured fields to the recipient, including returning orchestrators, and retain them across restart. The complete JSON payload is limited to 64 KiB of UTF-8; an oversized payload receives a validation error that the role can correct in the same session. Use concise public artifact locators and hashes for larger evidence. Artifact declarations still require the existing host collection and delivery checks. Older run records retain reason-only delivery where structured fields were not persisted.

A minimal starter bundle is available programmatically:

import { getDefaultBundle } from "pi-conductor";
const { yaml, prompts } = getDefaultBundle(); // default conductor.yaml + orchestrator/worker prompts

4. Run

Inside any pi session in a project with .pi/conductor.yaml:

/conduct ship the changelog for the auth refactor

You'll see the conductor's status line update as the orchestrator dispatches to workers; while a role session is active, the footer also shows model=<provider:id> · effort=<level> (or model=<default> · effort=medium on the system/default model path) for the current worker. The run reaches a terminal state and notifies with the run_id, and /conduct:list shows the same model and effort tokens for active runs. While the run is active, Esc opens a confirmation dialog; confirming aborts the run just like /conduct:abort. Use /conduct:steer to redirect the addressable active role, or /conduct:followup to carry guidance across the next handoff. /conduct:copy copies the latest completed assistant response without tool summaries and remains available for the most recently completed run in the current pi process.

Documentation

The reference material is split into focused pages:

Status & what's left

Full status is tracked in the authoritative specs:

Architecture in brief

checkpoint + event + def (pinned manifest snapshot)
            │
            ▼
        reduce()  ── pure, deterministic, host-agnostic (src/core)
            │
            ▼
   transition record + new checkpoint
            │
            ▼
   host persists record + snapshot, spawns next role (src/host)
            │
            ▼
   ┌────────┴────────┐
   ▼                 ▼
   bin/conduct    extensions/conduct.ts
   (CLI)          (pi extension /commands)

The full architecture rationale and invariants are in docs/architecture.md and the authoritative docs/archive/orchestrator-fsm-spec.md.

Repo layout

src/
  core/         FSM types + reducer + lifecycle + targets + run-memory (no pi)
  manifest/     manifest types + parse + validate + toMachineDefinition
  seam/         TypeBox emission schemas + validateEmission
  cost/         pure usage roll-up + cap predicates
  persistence/  RecordLog interface + InMemoryRecordLog
  host/         SDK driver — the ONLY place that imports pi (engine)
  extension/    UX shell helpers — wraps src/host for the extension
                (may import pi; mirrors src/host/ posture)
  bin/          conduct CLI fallback (built to dist/bin/conduct.js)
  index.ts      public barrel
extensions/
  conduct.ts    pi extension entrypoint (loaded by pi via jiti)
tests/
  *.test.ts              unit + E2E (stub-provider-driven; no API key)
  grep-guard.test.ts     asserts src/core + src/manifest (+seam/cost) have zero pi imports
  package-metadata.test.ts asserts pi extension manifest + peer-dependency posture
docs/
  archive/orchestrator-fsm-spec.md    the spec (authority)
biome.json            # linter + formatter (replaces ESLint + Prettier)
lefthook.yml          # git hooks: pre-push runs lint + typecheck + tests
pnpm-workspace.yaml   # pnpm config + supply-chain hardening (camelCase keys)

License

MIT — see LICENSE.

About

No description, website, or topics provided.

Resources

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages