pi-dynamic-workflows-oc-style
v0.2.3
Published
Dynamic multi-agent workflows for Pi — fan a task out across 100s of subagents with model routing, token/cost accounting, resume, git-worktree isolation, an interactive /workflows TUI, /deep-research, and OpenCode-style per-subagent context governance (ma
Maintainers
Readme
pi-dynamic-workflows-oc-style
Dynamic multi-agent workflows for Pi. Fan a task out across
hundreds of subagents with model routing, context governance, resume, and
git-worktree isolation. The runtime supports phases, fan-out (parallel/pipeline/dag),
model-tier routing, context modes, saved workflows, and the Issue Delivery
closed-loop workflow (Scout → Thinker → Worker → Checks → Verifier → PR).
Independently maintained. Originally derived from
@quintinshaw/pi-dynamic-workflows(MIT) and substantially extended; see PROVENANCE.md for the relationship; the projects have since diverged and upstream is treated as a read-only idea source.
- Security model: workflow scripts are trusted code, not sandboxed; see SECURITY.md
- License: MIT (see LICENSE)
Docs index
- CONTEXT.md — glossary / ubiquitous language for this project's domain terms (harness, conductor, fugu, hopper, clean-skip, scout firewall, model tiers, worktree isolation, workflow lock, agent-result channel, effort mode, gardener, …).
- docs/context-toolchain.md — the context-handling companions that make long Pi sessions work: autocompactor (compaction + durable-artifact preservation +
vcc_recall) and Hindsight memory + thememory_gardenersaved workflow. - docs/architecture.md — full architecture diagram with expanded detail.
- docs/context-modes.md — per-subagent context governance (
focuseddefault). - docs/workflows/catalog.md — workflow catalog and naming lock.
- docs/prompt-guidance-style.md — how we write behavioral guidance for agents (principles, anti-patterns, "say this, not that").
- docs/issue-delivery-smoke-test.md, docs/model-routing-specialization.md, docs/herdr-integration.md, docs/supervisor-telemetry-env.md — topic deep-dives.
Quickstart
Point Pi's agent settings at this checkout, build, and restart Pi:
// ~/.pi/agent/settings.json
{ "packages": [ "/path/to/this/repo" ] }npm run build # tsc -> dist/
npm test # full gate: biome check . && build && unit tests
# then restart piIf you also install @amaster.ai/pi-telemetry, list this package before it in
Pi settings. pi-dynamic-workflows-oc-style ships an early
extensions/telemetry-scrub.ts package extension that clears stale inherited
PI_TELEMETRY_* values before telemetry snapshots process.env; loading
telemetry first is unsupported because the stale values are already captured.
Optional Langfuse workflow tracing is enabled when LANGFUSE_PUBLIC_KEY and
LANGFUSE_SECRET_KEY are present. It emits workflow traces plus compaction
policy spans from the runtime API (emitCompactionTelemetry) and the local
autocompactor JSONL bridge (~/.autocompactor/pi/stats/events.jsonl). Payloads
and absolute run paths stay redacted unless LANGFUSE_INCLUDE_PAYLOADS=true.
For tmux/supervisor-launched sessions, see
Supervisor telemetry/env policy: never pass
a blank HINDSIGHT_API_URL=, load Langfuse secrets via a secret-managed env file,
and use the exported prepareSupervisorTelemetryEnv() helper for secret-safe
launch diagnostics.
Slash commands
| Command | Usage | What it does |
|---------|-------|--------------|
| /workflows | [list] \| status <id> \| watch <id> \| stop <id> \| pause <id> \| resume <id> \| rm <id> \| save <name> [runId] | Manage runs. No args (with a UI) opens the interactive navigator. watch streams live progress to the status bar and prints the final snapshot. save registers a finished run as a reusable /<name> command. |
| /code-review | [high\|xhigh\|max] [--mode <name>] [--harness-type <id>] [--harness-config <id>] [target] | Multi-angle code review: scope → find (N angles) → verify → sweep → synthesize. All agents tagged tier: "big". Used as an in-session sanity checkpoint, not a PR/merge gate. The first token is the effort level (high default; xhigh/max add a sweep phase) and is consumed before the target — so a target literally named max must be disambiguated. |
| /deep-research | [--mode <name>] <question> | Research a question across the web with cross-checked sources. |
| /adversarial-review | [--mode <name>] [--evidence[=web_fetch,github\|web_search]] [--no-evidence] [--reviewers N] [--threshold N] <task> | Investigate a task, then cross-check each finding with skeptical reviewers. Evidence mode adds a source-ledger phase using no-key web_fetch/GitHub evidence by default. Runs through the shared workflow manager in the background so /workflows, the task panel, and result delivery stay live. |
| /foundation_ui_compliance | <JSON args object> | Run the packaged Foundation gate → scoped fix/re-gate → optional visual verification/delivery loop. Requires a vendored design-system foundation implementing the documented gate contract; delivery defaults to false and is blocked while gates remain red. See the setup and usage guide. |
| /issue-delivery | [--mode <name>] [--prototype] [--finish] <task or issue> | Autonomous Scout → Thinker → Worker → LocalChecks → Verifier workflow with DAG scheduling and draft-PR delivery. Intended for scoped issue-to-PR tasks; it plans, edits, verifies, commits, pushes, and opens a draft PR. --finish is a delivery path for an already-repaired failed run: it runs LocalChecks, then a Worker delivery agent that commits/pushes/opens the draft PR, then the finalization gate (it does not re-run Scout/Thinker/Verifier). |
| /fugu | [--mode <name>] [--prototype] <task or issue> | Deprecated compatibility alias for /issue-delivery. |
| /modes | — | List context-inheritance modes (built-in + project-defined) and what each expands to — see Context modes. |
| /harness-configs | — | List harness configs: id, harness_type, wired status, and trigger summary — see Harness configs. |
| /effort | off \| high \| ultra | Standing workflow effort — auto-arms a workflow for substantive messages. |
| /maxeffort | [off] | Standing maximal-effort mode; /maxeffort off to stop. |
| /workflows-models | — | View and edit model tiers (small/medium/big). |
| /workflows-trigger | on \| off \| status | Keyword trigger: when on, typing the exact workflow-run phrase auto-arms workflows mode. |
| /workflows-progress | compact \| detailed \| status | Bottom progress-panel render mode. |
| /workflows-progress-max | <1-1000> | Cap agents shown per phase in detailed mode. |
| /workflow-telemetry-report | [window=24h\|since=<iso>] [until=<iso>] [runId=<id>] [sessionId=<id>] [json=true] | Summarize workflow cache, cost, context, trace, and compaction telemetry across recent runs. All arguments use key=value form. |
How it works
flowchart TD
U["User types a slash command"] --> CMD{"Command source"}
CMD -->|"builtin"| BC["pi.registerCommand<br/>src/builtin-commands.ts<br/>src/issue-delivery.ts<br/>src/workflow-telemetry-command.ts"]
CMD -->|"saved workflow"| SW["Saved-workflow command<br/>src/saved-commands.ts<br/>package defaults + project/user overrides"]
BC --> WM
SW --> WM
WM["WorkflowManager.startInBackground<br/>src/workflow-manager.ts"] --> WT{"worktreeRequired /<br/>isolation.worktree?"}
WT -->|"yes"| ISO["createWorktree<br/>src/worktree.ts<br/>branch pi/wf/runId"]
WT -->|"no"| HB
ISO --> HB["Harness broker<br/>selectHarness / resolveHarnessLayers<br/>src/harness-selector.ts<br/>harness_type: pi | opencode | hermes<br/>harness_config descriptor"]
HB --> RUN["Workflow engine<br/>parseWorkflowScript + runWorkflow<br/>src/workflow.ts"]
RUN --> VM["Trusted-code VM — phases, phase(),<br/>agent / parallel / pipeline / dag"]
VM --> JR[("Journal + resume<br/>stable call-seq<br/>ManagedRun.runStatePath")]
VM --> AR["agentType registry<br/>resolveAgentType<br/>src/agent-registry.ts"]
VM --> MT["Model tiers<br/>resolveTierModel<br/>src/model-tier-config.ts<br/>~/.pi/workflows/model-tiers.json<br/>small / medium / big"]
AR --> SA["Subagent execution<br/>createAgentSession + applyToolPolicy<br/>tools / disallowedTools<br/>src/agent.ts"]
MT --> SA
SA --> CS["Conductor status<br/>setSemanticStatus<br/>src/conductor-types.ts<br/>spawned, workflow-running,<br/>workflow-complete-pane-open, finalizing,<br/>completed / failed / needs-human"]
CS --> HR["herdr pane reporting<br/>installHerdrReporter<br/>src/herdr-reporter.ts<br/>HERDR_PANE_ID"]
CS --> TP["Task panel + result delivery<br/>installTaskPanel / installResultDelivery<br/>src/task-panel.ts"]
CS --> DEL["Delivery — branch + commit + push + draft PR<br/>checkFinalization gate<br/>src/issue-delivery.ts"]Slash commands split by command: /deep-research and /code-review run foreground via runWorkflow() directly (no WorkflowManager, no task-panel/background delivery, inline result to the TUI), while /adversarial-review and /issue-delivery (and saved-workflow commands) route through WorkflowManager.startInBackground() when a manager is available (falling back to inline runWorkflow()), so the manager can spin up a git worktree for isolation, resolve the harness config, and hand off to the workflow engine. Inside the trusted-code VM, agent() calls are routed through the agent-type registry and model-tier config, then executed as subagent sessions with tool policies applied. Conductor status propagates to the herdr pane, task panel, and — for Issue Delivery — through PR delivery and the finalization gate. The full architecture diagram with expanded detail lives in docs/architecture.md.
Guides
Foundation UI compliance
Version 0.2.3+ bundles /foundation_ui_compliance; no manual saved-workflow install is required. The target app must vendor a design-system foundation that provides scripts/run-foundation-gates.mjs and its rule docs. Invoke the command with one JSON object describing the app source, foundation path, URLs, and allowed edit globs. See the Foundation UI setup, gate contract, argument schema, and complete example.
Workflow tool
The workflow tool is the model-facing primitive for multi-agent orchestration. It accepts a trusted JavaScript string with a required meta header and at least one agent() call. Scripts run inside a Node vm context that exposes agent(), parallel(), pipeline(), dag(), phase(), log(), args, budget, and quality helpers (verify, judgePanel, loopUntilDry, completenessCheck). loopGuard, tokenBudget, and compactionPolicy are run/tool options passed to the workflow tool (and agent() for per-call compactionPolicy), not VM globals — referencing them inside the script throws ReferenceError. Background runs are the default; pass background: false to block inline. Every agent() call is journaled for resume safety.
Full reference: see the Workflow tool reference in the full docs or docs/workflows/catalog.md for the workflow catalog and lock.
Model-tier routing
Agents are routed to concrete models by tier (small/medium/big), keeping workflow source portable. The mapping lives in ~/.pi/workflows/model-tiers.json and is editable via /workflows-models. Untagged agents fall back to the medium tier (or the session main model when no tier config exists).
Each tier holds one model, not a fallback chain, and retries do not escalate automatically. Encode small -> medium -> big attempts explicitly; /workflows-models warns when adjacent tiers point to the same model. The config is snapshotted once per run, and changing a tier's concrete model invalidates affected journal entries on resume. Optional routingNotes are injected into the model-facing workflow-authoring prompt for machine-specific specialization. Local-first routing is not a quality waiver: use host checks plus a big semantic verifier for consequential work. See Model routing and specialization.
Context modes
Per-subagent context governance, OpenCode-style: rules you put on the main
agent don't leak into the subagents it spawns. AGENTS.md stays small and
shared (general instructions for all agents); main-agent-only rules live in
.pi/APPEND_SYSTEM.md and are kept out of subagents so children stay focused.
The default mode focused needs zero config; legacy restores full
inheritance (byte-identical to before).
[!IMPORTANT]
focusedis the default for every subagent — this is the standard behavior, no flags required. Subagents inherit the sharedAGENTS.mdand skills, but the main agent's rules (.pi/APPEND_SYSTEM.md) are blocked by default so they don't leak into children. This is a deliberate change from the pre-feature behavior, where subagents inherited everything. To restore full inheritance, setcontextMode: legacy(orinheritMainRules: true) at the agent.md, theagent()call, or the run level (--mode legacy).
| Mode | context (AGENTS.md) | main-rules (.pi/APPEND_SYSTEM.md) | prompt | skills | Posture |
|------|------|------|--------|--------|---------|
| focused (default) | in | out | append | in | Shared context+skills, main rules blocked. |
| isolated | out | out | replace | out | True clean room (role replaces base). |
| scoped | in | out | replace | out | Reviewer — facts in, own persona, no skills. |
| legacy | in | in | append | in | Pre-feature behavior — everything inherited. |
A subagent's prompt has four independent channels — base, the main-rules append
channel, AGENTS.md, and skills — each governed by one primitive
(systemPromptMode, inheritMainRules, inheritProjectContext, inheritSkills).
Select a posture at the agent .md (contextMode: scoped), the agent() call
(agent(p, { contextMode: 'legacy' }) or { inheritMainRules: true }), or the
run level (/code-review --mode legacy). Precedence, highest first: **per-call
field > per-call mode > agent .md field > agent .md mode > run-level --mode
focused**. Run/modesto list modes. (Conversation context is already isolated — subagents spawn with fresh sessions.)
Full reference: docs/context-modes.md.
Per-agent skills allowlist
Skill inheritance is not binary. A focused subagent inherits all user + project
skills, so a cheap-tier mechanical agent (e.g. a grep-only scout) carries irrelevant
skill descriptions as pure context tax. Narrow it per call:
await agent("grep the repo for X", { label: "scout", skills: ["langfuse"] })skills: ["name", ...] loads only the named skills (matched by skill name),
regardless of inheritSkills/contextMode. An empty array is a fence —
skills: [] loads zero skills (equivalent to inheritSkills: false), not "all
skills". Omitting the option preserves today's behavior (the resolved context
posture decides whether skills load). Names that match no discovered skill warn
and are skipped; the run never fails on an unknown name.
Precedence for the skills channel: skills > inheritSkills/contextMode.
When skills is set, a custom resource loader is always constructed (even under
legacy) and a skillsOverride filter keeps only the named skills, so the
allowlist also busts the resume cache (changing the list re-runs the agent).
Harness configs
A harness_config is a JSON descriptor that declares how a workflow harness is configured. Descriptors live as schemaVersion 1 JSON files under user-level (~/.pi/workflows/harnesses/) or project-level (.pi/workflows/harnesses/) directories. The harness_type field (pi, opencode, hermes) determines runtime wiring; only pi is connected. Run /harness-configs to list active configs.
Issue Delivery
/issue-delivery is the built-in issue-to-draft-PR coordinator. The normal production path:
Task / issue text
↓
Scout (small tier): codegraph exploration stack returns a compact Code Map
↓
Thinker (big tier): plan a DAG from the compact Code Map
↓
Deterministic scheduler: run dependency-ready steps with parallel()
↓
Worker (small → medium → big tier on retry): edit one focused step
↓
LocalChecks (host stageCheck): run tsc/Biome mechanically with zero LLM tokens
↓
Verifier (big tier): strict semantic pass/fail review after checks pass
↓
Feedback Compactor: failed checks/verdicts become a bounded Correction Delta for retry
↓
PR delivery + Telemetry finalization: branch, commit, push, PR, clean/pushed/checks gateComponents:
| Component | Role |
|-----------|------|
| Scout | Runs code-scout on the small tier to gather targeted citations and API/test hints via the codegraph exploration stack (codegraph_explore/context, ffgrep/fffind, ctx_read). The Thinker receives this compact Code Map instead of large raw files. |
| Thinker | Plans from the task plus Code Map, then emits structured JSON: summary plus steps[] with id, file, instructions, expectedOutput, and optional dependencies. Same-file edits should be sequential; independent files can stay dependency-free. |
| DAG scheduler | Runs inside the workflow VM, not inside a model. It repeatedly finds steps whose dependencies are complete, starts them together with parallel(), and rejects cyclic/deadlocked plans. |
| Worker | Receives exactly one step and edits the repo directly with coding tools. First attempt uses small, second medium, third big; it sees only the current Correction Delta, not raw history. |
| LocalChecks | Calls host-side stageCheck() (TypeScript and Biome by default) and fails fast before the LLM Verifier when mechanical checks fail. |
| Verifier | Performs strict semantic LLM review with schema output { passed, feedback }, only after host checks pass. |
| Feedback Compactor | Converts failed stage checks or verifier feedback into a bounded, redacted Correction Delta (maxTokens: 512) for the next Worker attempt. |
| State writer | Writes transient diagnostic progress to .issue-delivery/status.json so long runs have inspectable local state. This is scratch state, not intended for commits. |
| Failed-run handoff | When LocalChecks/Verifier exhaust repair attempts before PR delivery, writes .issue-delivery/handoff.md with the final findings, completed steps, transient files to remove/ignore, and --finish instructions. |
| PR delivery / Telemetry | After all steps pass, creates a safe branch, commits, pushes, opens a draft PR, then runs the deterministic finalization gate. If the task mentions an issue like #42, the PR body should include Closes #42. --finish enters this checks → PR delivery → finalization lane directly after a human repairs a failed run. |
Model routing: built-in Issue Delivery uses tiers rather than hard-coded provider IDs — Scout/state/PR: small; Thinker/Verifier: big; Worker: small → medium → big on retry; LocalChecks: host stageCheck() (zero LLM tokens).
Prototype mode: --prototype defaults to dryRun=true, worktreeRequired=true, maxSteps=4, maxRepairRounds=1, maxReviewRounds=1. Dry-runs use read-only agents and stop before Worker edits, git push, and PR creation. --prototype --dry-run=false allows bounded local edits but still stops before PR delivery.
Operational notes:
- Start from a clean git working tree when possible.
- If a run fails before PR creation with useful changes, inspect
.issue-delivery/handoff.md, repair, then rerun/issue-delivery --finish .... ghmust be authenticated and the repo must allow pushing branches.- Prefer focused issue-sized tasks. Broad roadmap requests should be broken into issues first.
- Use
--mode <name>to choose the context-inheritance posture. - Issue Delivery opens a draft PR; it does not auto-merge.
Adversarial review evidence mode
Baseline /adversarial-review <task> preserves the original fast workflow: investigate → skeptical refutation → consensus. Add --evidence to insert an Evidence phase before refutation:
/adversarial-review --evidence check this claim against https://github.com/org/repo/blob/main/README.md
/adversarial-review --evidence=web_search,github --reviewers=3 --threshold=0.75 check this external claimOptions: --mode <name>, --evidence[=<components>], --no-evidence, --reviewers <N>, --threshold <N>, -- (stop option parsing). Evidence components: web_fetch, github, web_search, all, or off.
Saved workflows
Run a workflow, then register it for reuse:
/workflows save research_topic # saves the most recent run with a script
/workflows save research_topic <runId>This creates a /<name> command (with key=value args). Saved-workflow slash commands start through the shared WorkflowManager, print the run ID immediately, show live progress in the task panel//workflows, and deliver the final result back to chat when complete. Call a saved workflow from another workflow via await workflow('research_topic', { key: 'value' }). Storage is WorkflowStorage (workflow-saved.ts).
UI & notifications
Three surfaces show workflow state, by design serving different moments:
- Below-editor "workflows running" panel (
installTaskPanel) — live agents/phases/tokens while a run is in flight. Informational only (it takes no input) — run/workflowsto open the interactive navigator. Mode controlled by/workflows-progress. - Status bar (
ctx.ui.setStatus) — one-line live progress while watching a run (/workflows watch <id>). - Chat
<task-notification>(installResultDelivery) — the canonical final status delivered when a background run finishes. Modeled on Claude Code's XML:<status>,<usage>(agent_count,subagent_tokens,tool_uses,duration_ms), and on failure a<recovery>block withfile://links to the on-disk agent transcripts and the persisted run-state JSON (ManagedRun.runStatePath, set regardless of transcript persistence).
Foreground dedup. A foreground (
background: false) tool run used to stream live progress into chat and the below-editor panel at the same time. Now, when a UI is present, live progress shows only in the panel (installTaskPanel, which subscribes to the manager directly) and chat receives just the final result; in headless/RPC mode (no panel) it still streams to chat as a fallback. Background runs are unchanged: the panel shows live progress and chat gets the final<task-notification>.
Conductor statuses
Workflow runs carry an engine status (running, completed, failed, etc.) and
an optional semantic status that layers conductor-level intent on top. This
helps distinguish a completed workflow whose tmux pane is still open from one that
is truly active, and signals when a repair run needs finalization attention.
| Semantic status | When it appears |
|-----------------|------------------|
| spawned | tmux pane created, workflow not yet started |
| workflow-running | workflow is actively executing |
| workflow-complete-pane-open | workflow finished, pane still open for inspection |
| needs-finalize | repair/delivery invariants (clean, committed, pushed) not yet met |
| finalizing | finalization in progress (e.g. checks pending) |
| completed | run finished, worktree clean and delivered |
| failed | run failed |
| needs-human | blocked; requires human intervention |
The semantic status is displayed in /workflows list and /workflows status <id>
output alongside the engine status. On startup, stale persisted running runs
without a live owner process are reconciled to paused engine state. Issue Delivery
sidecars (state.env, .issue-delivery/state.env, and
.issue-delivery/status.json) are used to surface needs-finalize or
needs-human instead of leaving misleading stale-running state. Sidecars must
carry a matching run id (runId, workflowRunId, CONDUCTOR_RUN_ID, or
WORKFLOW_RUN_ID) before they are trusted for recovery, so stale scratch files
from earlier runs do not affect a new crashed run.
The finalization gate checks:
- Worktree clean (transient
.issue-delivery/paths are ignored) - Branch pushed to upstream
- Local HEAD matches remote HEAD
- PR head SHA matches (when known)
- GitHub checks green or clearly pending
When any invariant fails, the run reports needs-finalize or needs-human
with an actionable nextAction instead of silently claiming success.
Status & acknowledgements
Status: the full npm test gate (biome + build + unit tests) is green on main. Issues are tracked on GitHub; command naming is governed by the workflow catalog.
Originally derived from @quintinshaw/pi-dynamic-workflows
(MIT, by QuintinShaw; original pi-dynamic-workflows by Michael Livs), now
independently maintained and substantially extended. See
PROVENANCE.md for the derivation history and upstream relationship.
License
MIT, retained from upstream. See LICENSE.
