flow
Reusable multi-agent workflows and CodeRabbit-style code audit — making workflows simple.
flow lets you describe a multi-agent workflow in ~15 lines of YAML (four knobs: agents, phases, loops, groups) and run it end-to-end with no human gates. It also ships battle-tested plan/implement/audit skills.
It unifies pair's plan/implement/review simplicity with @quintinshaw/pi-dynamic-workflows' dynamic orchestration and a CodeRabbit-style audit rigor. It supersedes earlier workflow packages (see the migration guide).
The mental model (read this first)
Flow has three layers, kept deliberately separate. Confusing them is the #1 source of confusion, so here is the whole picture:
| Layer | What it is | Where it lives | Who writes it |
|---|---|---|---|
| Agent | A role's behavior — a system prompt + frontmatter (model:, tools, thinking, isolated, …). The .md is the agent's single definition, model included; flow passes no model at dispatch. | ~/.pi/agent/agents/<name>.md (global) or .pi/agents/<name>.md (project overrides global) | flow ships 12 defaults; you edit/add freely (write-once) |
| Workflow | What runs, in what order — either a built-in skill (Tier 1) or a YAML file (Tier 2). | Tier 1: built-in skills · Tier 2: ~/.pi/sf/flow/workflows/<name>.yaml (global defaults) or .pi/sf/flow/workflows/<name>.yaml (project override) | flow ships skills + 5 example YAMLs (/sf-flow-seed); you add YAMLs |
| Config | Runtime settings — audit thresholds + worktree (models live in the agents' .md files). | ~/.pi/sf/flow/config.json (global) + .pi/sf/flow/config.json (project) | you (partial is fine) |
⚠️ Agents are defined in exactly one place: their
.mdfile Each agent (reviewer, researcher, developer, planner, auditor, synth, designer, elicitor, notifier, scanner, explorer, analyst) is defined as a
.mdfile — frontmatter (model:,tools,thinking,isolated, …) plus the body (its system prompt). Discovery: project.pi/agents/<name>.mdoverrides global~/.pi/agent/agents/<name>.md. Neitherconfig.jsonnor workflow YAMLs define agents or their models: config carries only runtime settings (audit,worktree), and a workflow only names the agents its phases use. Set an agent's model by editing its.md.
Where the model comes from:
- Every agent, in every tier — its
.mdfrontmattermodel:(project.pi/agents/<name>.mdoverrides global~/.pi/agent/agents/<name>.md); a.mdwith nomodel:inherits the orchestrator. Flow passes no model at dispatch.
Installation
pi install npm:@pi-stef/flowFlow's skills are discovered natively via pi.skills. To author flows that pull from Jira/PRDs, also install @pi-stef/atlassian.
Quickstart
# 1. Audit your current diff — zero config, runs the 7-angle triad + dual-blind gate
/sf-flow-audit
# 2. Plan, then implement a feature (agent models come from their .md files)
/sf-flow-plan add OAuth login
/sf-flow-implement 2026-07-20-oauth-login
# 3. Run a reusable flow end-to-end (seed the 5 examples to ~/.pi/sf/flow/workflows via /sf-flow-seed)
sf_flow_auto code-review "review the auth changes"You can also drive everything in natural language:
"Plan a feature for adding user authentication" # reviewer model: edit reviewer.md
"Implement the plan in ai_plan/2026-07-20-oauth-login"
"Run the code-review flow on the staged diff"Built-in agents
Twelve write-once agent definitions ship in packages/flow/agents/ and are copied to your global discovery dir (getAgentDir()/agents/, default ~/.pi/agent/agents/) by /sf-flow-seed (or lazily on first use of a Tier 1 skill):
| Agent | Role | tools | thinking | Tier |
|---|---|---|---|---|
planner | Workflow Planner — milestones + stories | read, grep, find, ls, write, edit | medium | opus |
designer | Workflow Designer — design via brainstorming (2–3 approaches → recommend 1) | read, grep, find, ls | high | opus |
developer | TDD Developer — red/green/refactor | read, grep, find, ls, write, edit, bash | medium | opus |
reviewer | Plan/Implementation Reviewer | read, grep, find, ls | high | sonnet |
auditor | Code Auditor (CodeRabbit-style) | read, grep, find, ls | high | sonnet |
synth | Synthesis / Report Writer | read, write | medium | opus |
scanner | Route/File Scanner — enumerate files for fan-out | read, grep, find, ls | low | haiku |
elicitor | Requirements Elicitor — clarifying questions | read, grep, find, ls | high | sonnet |
researcher | Researcher — codebase + web + private-source research, cited claims | read, grep, find, ls, bash, ext:web/* + ext:atlassian/* | medium | opus |
notifier | Notifier — Telegram completion summary (opt-in, Tier-2) | bash | low | haiku |
explorer | Explorer — fast read-only codebase/document scout, cited findings | read, grep, find, ls | low | sonnet |
analyst | Analyst — deep-research intake coordinator + synthesis report writer | read, write, bash | high | opus |
- Write-once: flow never overwrites an existing agent file, so you can edit any of them freely.
- Model resolution: each agent's model comes from its
.mdfrontmatter — project.pi/agents/<name>.mdoverrides the global one; a.mdwith nomodel:inherits the orchestrator. Never pass a model at dispatch. - Project overrides global: a
<repo>/.pi/agents/reviewer.mdshadows the global one (pi-subagents semantics). - Every shipped
.mdcarries a commented model hint naming the tier the agent needs — opus-class (frontier reasoning: planner, designer, developer, researcher, analyst, synth), sonnet-class (review/scout: reviewer, auditor, elicitor, explorer), or haiku-class (super-light: scanner, notifier) — with cross-provider example IDs. Uncommentmodel: <provider>/<modelId>and set your preferred model before your first run (fullprovider/modelId; never a bare alias, which one pi-subagents spawn path silently drops). With nomodel:the agent inherits the orchestrator; flow never passes a model at dispatch (see Model resolution).researcheris the only agent withisolated: falseandextensions: [web, atlassian](declared in its.mdfrontmatter) — see Agent Isolation & Auth.notifieris an opt-in agent that sends a one-line completion summary via the bundlednotify-telegram.shwhenTELEGRAM_BOT_TOKEN/TELEGRAM_CHAT_IDare set (returnsskippedsilently otherwise) — name it in a workflow'sagents:block and run it from a finalnotifyphase.explorer+analystpower thedeep-researchflow (bound viaagentType).
Add a new agent: just drop a <name>.md at ~/.pi/agent/agents/ (global) or .pi/agents/ (project), then reference it by name in a workflow's agents: block. sf_flow_create_workflow will also write a write-once stub for any agent you declare that doesn't yet exist.
Built-in workflows (examples)
Five reference flows ship in packages/flow/workflows/. They are global defaults — copy them once with /sf-flow-seed (or they seed lazily on first use) into ~/.pi/sf/flow/workflows/, where they're available in every project:
| Workflow | File | What it does |
|---|---|---|
code-review | code-review.yaml | Audit↔fix loop (auditor gates, developer fixes, re-verify) |
ship-feature | ship-feature.yaml | Clarify → design → plan → implement → audit, with find→fix→re-verify group loops |
auth-audit | auth-audit.yaml | Scan route files, fan out audits, dedup, synthesize a report |
research-report | research-report.yaml | Multi-perspective research with cross-checking + synthesis |
deep-research | deep-research.yaml | Clarify scope via a research brief, then parallel code + web research with an analyst write-up |
- Global defaults live at
~/.pi/sf/flow/workflows/; a project override at<repo>/.pi/sf/flow/workflows/<name>.yamlshadows the global one (resolved project→global bysf_flow_auto). /<name>commands (/code-review, …) register at pi startup from the global + current-project workflow dirs.- Re-seed safely:
/sf-flow-seednever clobbers your edits — if a file differs from the bundled default, the new default is written as<name>.newbeside it.
# Seed the defaults globally, then run one from any project:
/sf-flow-seed
sf_flow_auto ship-feature "add a rate limiter to the API"Tier 1 — the built-in skills
Five prose skills (fixed, battle-tested step sequences) plus a worktree helper:
| Skill | Slash | Tool | Purpose |
|---|---|---|---|
| Plan | /sf-flow-plan | sf_flow_plan | Multi-milestone plan with parallel research + iterative review |
| Implement | /sf-flow-implement | sf_flow_implement | One worktree, TDD per story, audit gate before commit |
| Audit | /sf-flow-audit | sf_flow_audit | CodeRabbit-style audit (7 angles + dual-blind AND-gate + fix-apply) |
| Auto | /sf-flow-auto | sf_flow_auto | Run any defined flow end-to-end, no human gates |
| Create Workflow | /sf-flow-create-workflow | sf_flow_create_workflow | Adaptive wizard: suggests building blocks from local examples, validates, writes, registers /<name> |
| Seed | /sf-flow-seed | sf_flow_seed | Copy default agents + example workflows to their global locations |
| — | /sf-flow-finalize | sf_flow_finalize | Remove a flow worktree dir, preserve its branch |
sf_flow_plan
Create a multi-milestone implementation plan with parallel research and iterative reviewer approval. Produces ai_plan/<slug>/.
| Parameter | Required | Description |
|---|---|---|
prompt | No | The task to plan |
Phases: (1) fan out N researchers in parallel → codebase map; (2) gather requirements one question at a time; (3) design via brainstorming; (4) plan via writing-plans (milestones + S-MN{seq} stories); (5) iterative reviewer loop (fix P0/P1/P2, max 10 rounds); (6) write plan files; (7) optional Telegram notify.
sf_flow_implement
Execute an approved plan in one worktree (flow/<slug>, git-only), TDD per story, with the audit triad as a non-optional gate before commit.
| Parameter | Required | Description |
|---|---|---|
path | Yes | Plan folder slug or path under ai_plan/ |
Per-milestone loop: TDD each story → reviewer loop → commit to the worktree branch → update the tracker. After all milestones: run sf_flow_audit on the accumulated diff; on REVISE (any P0/P1/P2) loop back to the failing story (not the whole plan), bounded by audit.max_rounds (default 5). Finish with sf_flow_finalize (removes the worktree dir, preserves the flow/<slug> branch for a PR).
sf_flow_audit
CodeRabbit-style audit returning P0–P3 findings + a verdict (APPROVED / REVISE). See the audit triad below.
| Parameter | Required | Description |
|---|---|---|
target | No | Diff target: a git ref range, a file path, or workdir. Defaults to git diff HEAD (staged + unstaged) |
apply_fixes | No | If true, run respond-review to apply must-fix / should-fix |
sf_flow_auto
Run a defined flow end-to-end with no human gates.
| Parameter | Required | Description |
|---|---|---|
workflow | Yes | Flow name (resolved project→global: .pi/sf/flow/workflows/<name>.yaml overrides ~/.pi/sf/flow/workflows/<name>.yaml) |
input | Yes | prompt · path to a markdown file · prd:<path> · jira STORY-123 |
Input forms: prompt (verbatim), md-file (file contents), prd:<path> (parsed PRD), jira STORY-123 (resolved via @pi-stef/atlassian). Phases run sequentially; intra-phase fan-out via parallel(); loops run to a terminal state (success / no-op / blocked / exhausted).
The auto-proceed directive is built into the tool's ready message — the orchestrator continues without stopping, so no manual 'Proceed' is required. Every phase runs to completion or a terminal state.
sf_flow_create_workflow
Adaptive wizard that consults local bundled example workflows to suggest building blocks by task archetype. Validates each section incrementally (partial) or full cross-field (complete). Writes the YAML + registers /<name> (the interview also emits write-once agent stubs for any agent without an .md).
| Parameter | Required | Description |
|---|---|---|
name | No | kebab-case flow name |
description | No | One-liner |
input | No | prompt / md-file / prd / jira |
agents_yaml | No | Pre-formed agents YAML to skip the interview — a LIST of names (e.g. - scanner\n- auditor) |
phases_yaml | No | Pre-formed phases YAML |
loops_yaml | No | Pre-formed loops YAML |
groups_yaml | No | Pre-formed groups YAML |
overwrite | No | Replace an existing workflow of the same name |
sf_flow_finalize
Remove a flow worktree directory while preserving its branch. Call after sf_flow_implement finishes.
| Parameter | Type | Description |
|---|---|---|
worktree_path | string | Absolute path of the flow worktree to remove |
Tier 2 — declarative YAML flows (4 knobs + phase contracts)
This is the heart of flow. Describe a workflow with four knobs (agents / phases / loops / groups) plus an additive phase-contract layer (inputs / outputs / worktree) and the generator compiles it into a pi-dynamic-workflows script. Contracts make a tier-2 flow self-enforcing: a phase that skips or fails its declared outputs starves the next phase's required inputs → a concrete blocked state, never a silent skip.
# .pi/sf/flow/workflows/auth-audit.yaml
name: auth-audit
description: Audit auth coverage across route files
input: prompt
agents: [scanner, auditor, synth] # names only — the .md files define the agents
phases:
- { id: scan, agent: scanner, prompt: "List every route file under src/routes/.", out: files }
- { id: audit, agent: auditor, fanout: files, prompt: "Audit {{item}} for missing auth checks.",
schema: { verdict: "APPROVED|REVISE" }, out: findings }
- { id: verify, agent: auditor, verify: findings, threshold: 0.66, out: confirmed }
- { id: report, agent: synth, in: confirmed, prompt: "Write a cited report from these findings." }
loops:
audit: { until_dry: true, max_rounds: 3, dedup_key: "{{file}}:{{line}}:{{summary}}" }Run it: sf_flow_auto auth-audit "check the API routes".
Knob 1 — agents
A list of agent names — just the names this flow uses. The agents themselves (system prompt, model:, tools, thinking, isolated — everything) are defined in their .md files (project .pi/agents/<name>.md overrides global ~/.pi/agent/agents/<name>.md); a workflow never defines an agent or its model. To change an agent's model or tools, edit its .md.
Knob 2 — phases
An ordered list. Each phase runs exactly one of agent / skill / raw / questions:
| Field | Type | Description |
|---|---|---|
id | string | Phase identifier (referenced by loops) |
agent | string | Run an agent (must be declared in agents) |
skill | string | Run a built-in skill (e.g. sf-flow-audit) — opaque to the generator |
raw | string | Run a raw pi-dw snippet — opaque to the generator |
questions | string | Run an elicitor agent with a built-in clarifying-questions follow-up loop |
max_rounds | integer | Max follow-up rounds for questions phases (default 5) |
prompt | string | Prompt template; the fanout item and prior out vars are interpolated (see examples) |
fanout | string | Iterate a list — a prior phase's out var or an args.* runtime input (agent phases only) |
verify | string | Cross-check a prior out; pass when >= threshold of items survive |
threshold | number | Verify pass ratio (default per flow) |
in | string | string[] | Feed prior out(s) into this phase (shorthand for inputs.require + inject) |
out | string | Name this phase's output (referenced by later phases / fanout / verify) |
inputs | object | Contract inputs: { require: [name…], inject: ["… …"] } |
outputs | object | Contract outputs (see Phase contracts) |
worktree | enum | none · prepare · finalize — engine-owned worktree lifecycle |
schema | object | The PHASE's structured-output contract (e.g. { verdict: APPROVED|REVISE, findings: array }); required on gate phases for until: approved (+ findings for canonical-delta), { questions: array } on elicitor phases |
Phase contracts — inputs / outputs / worktree
A phase may declare a contract. The generator compiles it into named steps backed by helper tools that the orchestrator runs verbatim (no hidden runtime; follow the steps exactly): sf_flow_contract (derive-slug / materialize / assert), sf_flow_checkpoint (load-required / complete / load-all), and for the worktree lifecycle sf_flow_prepare (prepare) / sf_flow_finalize (finalize); canonical-delta loops additionally call sf_flow_gate.
- id: plan
agent: planner
out: plan_doc
inputs: { require: [design_doc], inject: ["Design: {{design_doc}}"] }
outputs:
slug: { from: input, prefix: date }
dir: "ai_plan/{{slug}}"
artifacts:
- { file: milestone-plan.md, template: "@flow/plan/milestone-plan.md" }
assert: [nonempty]
publish: { slug: "{{slug}}", plan_dir: "{{dir}}", plan_doc: plan_doc }inputs.require— names an earlier phase must publish (or a built-ininput/flow); a missing one blocks the phase. Each is destructured into a JS const the prompt references.inputs.inject— lines appended to the prompt;resolves to the in-scope const at codegen (not a runtime placeholder).in:is shorthand forrequire+ an inject of the same name.outputs.slug/outputs.dir— derive the run slug and the artifact dir (ai_plan/).outputs.artifacts—{ file, template? };@flow/plan/…templates live inpackages/flow/templates/. Materialized resume-safe (non-empty files are never clobbered).outputs.assert—nonempty(every target.mdexists + non-empty),tracker_valid,tracker_updated(the milestone tracker). A failure blocks.outputs.publish— values fed to later phases:,, a bareoutname, or a literal. Validation guarantees every emitted ref is in-scope.worktree—preparecreatesflow/<slug>and publishes{worktreePath, branchName, baseSha};finalizerecovers the handle (resume-safe) and removes the worktree, preserving the branch.
Enforcement + resume. Every phase ends with one atomic sf_flow_checkpoint({mode:"complete"}) (publish + mark success + persist). The terminal result reads load-all: {status, finalPhase, artifacts, worktree, resumeState}. sf_flow_auto derives args.slug once and pre-seeds ai_plan/<slug>/.flow-state.json; resume re-enters at the first non-success phase (or group).
Writing a tier-2 workflow with contracts — annotated ship-feature
name: ship-feature
description: Clarify, design, plan, implement, and audit a feature end-to-end
input: prompt
agents: [planner, developer, auditor] # names only — the .md files define the agents
groups:
audit-loop: { phases: [review-audit, fix-audit] } # find→fix→re-verify
phases:
- id: plan
agent: planner
out: plan_doc
inputs: { require: [design_doc], inject: ["Design: {{design_doc}}"] }
outputs:
slug: { from: input, prefix: date }
dir: "ai_plan/{{slug}}"
artifacts:
- { file: original-plan.md, template: "@flow/plan/original-plan.md" }
- { file: milestone-plan.md, template: "@flow/plan/milestone-plan.md" }
- { file: story-tracker.md, template: "@flow/plan/story-tracker.md" }
- { file: continuation-runbook.md, template: "@flow/plan/continuation-runbook.md" }
assert: [nonempty]
publish: { slug: "{{slug}}", plan_dir: "{{dir}}", plan_doc: plan_doc }
- id: implement
agent: developer
out: impl_result
inputs: { require: [slug, plan_doc], inject: ["Slug: {{slug}}", "Plan: {{plan_doc}}"] }
outputs: { dir: "ai_plan/{{slug}}", assert: [tracker_updated] }
worktree: prepare
- id: review-audit
agent: auditor
in: impl_result
schema: { verdict: "APPROVED|REVISE", findings: array } # the GATE PHASE's contract
prompt: "Audit the implementation. Return findings P0-P3 + verdict."
- id: fix-audit
agent: developer
in: impl_result
prompt: "Fix the audit findings (TDD)."
loops:
audit-loop:
until: approved
fail_on: [P0, P1, P2]
max_rounds: 5
protocol: canonical-delta # carry [Fn] findings across rounds, AND-gate via verificationThe plan phase derives the slug, materializes four plan files, asserts them non-empty, and publishes {slug, plan_dir, plan_doc}. The implement phase requires {slug, plan_doc}, prepares a worktree, and asserts the tracker advanced (tracker_updated). The audit-loop runs canonical-delta: round 1 is a fresh review; round ≥2 verifies each prior [Fn] and AND-gates via verificationApproved (the gate phase's verdict + findings schema is required).
Knob 3 — loops
A map of phase-id → loop. Two kinds:
| Field | Applies to | Description |
|---|---|---|
until_dry | discovery | Keep running the phase until it stops finding new things. Requires fanout. Optional dedup_key (a template over item fields, e.g. file:line) and consecutive_empty (stop after N empty rounds) |
until | gate | until: approved — run until the gate result's verdict is APPROVED. Requires the gate PHASE to declare a schema.verdict |
fail_on | gate | Severities that block: [P0, P1, P2] (default) |
max_rounds | both | Bound on iterations (default per flow) |
protocol | gate | raw (default — fresh review each round) · canonical-delta (carry [Fn] findings across rounds, AND-gate via verification each round ≥2; group-only, requires the gate PHASE's findings schema + until: approved) |
Knob — groups (optional)
A map of group-name → { phases: [gate, ...fixers] }. A group is a named collection of phases where the first phase is the gate (must have a verdict schema) and the rest are fix phases (all must be agent phases). When a loops key matches a group name (instead of a phase id), the generator emits a find→fix→re-verify loop: the gate runs → if REVISE with blocking findings, the fix phases run with findings appended → gate re-verifies → until APPROVED or max_rounds.
| Field | Type | Description |
|---|---|---|
phases | string[] | ≥2 phase ids; all must be agent phases; first = gate, rest = fix |
Loop keys resolve group-first: if a loops key matches both a group name and a phase id, the group wins.
Validation rules
validateFlowYaml enforces these cross-field rules so a loop/fanout is never silently swallowed (invalid flows fail at registration, not at runtime):
| # | Rule |
|---|---|
| 1 | Each phase sets exactly one of agent / skill / raw / questions |
| 2 | agent must reference a name declared in agents |
| 3 | questions must reference a name declared in agents |
| 4 | questions and fanout are mutually exclusive |
| 5 | questions and verify are mutually exclusive |
| 6 | fanout is allowed only on agent phases (skill/raw are opaque) |
| 7 | fanout requires out (parallel results must be captured) |
| 8 | verify must reference a prior phase's out |
| 9 | out names must be unique across phases |
| 10 | Every phase in groups.<name>.phases must exist and be an agent phase |
| 11 | A phase may belong to at most one group |
| 12 | Every groups.<name> must have a matching loops.<name> |
| 13 | loops.<key> that matches a group: until_dry is not allowed (use until: approved) |
| 14 | loops.<key> that matches a group with until: approved: the GATE PHASE must declare a schema.verdict |
| 15 | loops.<key> that matches a phase: must reference an existing phase |
| 16 | Loops are not allowed on skill phases |
| 17 | Loops are not allowed on raw phases |
| 18 | Loops are not allowed on questions phases (the follow-up loop is built-in) |
| 19 | until_dry requires the phase to set fanout |
| 19a | until: approved on a phase loop requires the gate PHASE to declare a schema.verdict |
| 20 | inputs.require names must resolve to a prior out/publish or a built-in (input/flow) — else unresolved |
| 21 | worktree: finalize requires a preceding worktree: prepare phase |
| 22 | artifact template refs must resolve (@flow/… or an existing path) |
| 23 | publish names must be valid identifiers; / require outputs.slug/outputs.dir; a bare value must be the phase out or a required input (else it would emit an undefined ref) |
| 24 | protocol: canonical-delta requires a group loop, until: approved, and the gate PHASE's findings schema |
Caveat (rule 19a): the guard checks
schema.verdictpresence only. An agent that declares a verdict schema but has no finding-capable tools (e.g. read-only with no analysis prompt) will alwaysAPPROVE— this is not structurally detectable.
Fail-closed gate (D4). A gate result approves ONLY with a string
verdict === "APPROVED"AND no blocking finding.null/{}/aREVISEwith no findings/ anAPPROVEDwith a blocking finding all reject — group and single-phase gates share one_gateApprovedpredicate.
Defining a new flow
Two paths to the same result (a .pi/sf/flow/workflows/<name>.yaml runnable via sf_flow_auto):
- Wizard —
/sf-flow-create-workflow(adaptive: suggests building blocks from local examples, validates sections incrementally, writes YAML + agent stubs, registers/<name>). - By hand — create
.pi/sf/flow/workflows/<name>.yaml(project) or~/.pi/sf/flow/workflows/<name>.yaml(global) following the schema above. Runsf_flow_create_workflowonce to validate + register/<name>, or just runsf_flow_auto <name> <input>directly (it validates + generates eagerly).
Upgrading from the old format (pre-0.12)
Workflow YAMLs written before the agents-as-definitions change fail validation at registration with a warning explaining the new shape. Migrate by hand:
agents:is now a list of names.agents: { scanner: { tools: [...], model: haiku } }→agents: [scanner]. Delete every per-agent field (tools,model,thinking,isolated,schema) from the YAML.- Move each agent's
schemaonto the phase(s) that need it — a gate phase'sverdict/findingscontract is a property of the phase, not the agent. An elicitor phase carries{ questions: array }. - Move
tools/model/thinking/isolatedinto the agent's.mdfrontmatter (project.pi/agents/<name>.mdoverrides global~/.pi/agent/agents/<name>.md). To pin a model, setmodel: provider/modelIdin the.md. - Config model groups are gone — a
config.jsoncarrying<role>.modelgroups still loads (stripped with a one-time warning); move those models into the.mdfiles. SF_FLOW_<ROLE>_MODELenv vars and the*_modeltool params are gone — set the model in the agent's.mdinstead.raw:phases: inlinemodel:/tools:/thinking:/isolated:inagent()calls still work but duplicate the.mddefinitions — bind calls with{ label, phase, agentType }(+schemawhen the result is consumed) so the.mddrives everything.
Run /sf-flow-seed to copy the new-format bundled examples beside your existing files (as <name>.new) for side-by-side migration.
Notifications in custom workflows (Tier-2, opt-in)
Flow ships an opt-in notifier agent that sends a one-line completion summary to Telegram via the bundled notify-telegram.sh script. It is a normal Tier-2 agent — name it and run it from a final phase in any custom workflow (its tools/thinking/isolation live in notifier.md):
agents: [notifier]
phases:
- id: notify
agent: notifier
prompt: "ship-feature complete"
out: notify_resultEnv-var contract — the agent is a no-op (returns skipped) unless both are set:
TELEGRAM_BOT_TOKEN— the Telegram bot token.TELEGRAM_CHAT_ID— the target chat id.TELEGRAM_API_BASE_URL(optional) — defaults tohttps://api.telegram.org(set it to a mock host for tests).
This is Tier-2 only: the Tier-1 skills (sf_flow_plan / sf_flow_implement / sf_flow_audit) each send their own completion notification (unchanged), but a YAML flow controls notification declaratively — add the phase, omit it, or repoint the prompt. The agent never blocks or retries; a skipped result is a normal outcome.
Code audit triad
sf_flow_audit runs four modules sharing a P0–P3 + verdict contract. VERDICT: APPROVED only when no P0/P1/P2 remain.
| Module | What it does |
|---|---|
| codereview | Wraps pi-dw /code-review: 7 finder angles (A/B/C correctness medium-tier, D/E/F cleanup small-tier, G altitude big-tier). Each finding is verified 3-way (CONFIRMED / PLAUSIBLE / REFUTED — REFUTED dropped), deduped by file:line:summary, ranked correctness > cleanup > altitude. Diffs cap at MAX_DIFF_CHARS (200000). |
| auditcode | A 10-section self-checklist (Supply Chain & Security, Provenance & Metadata, Law of Demeter, …). gateExitCode returns 1 on any failure; qualityScore = 100*(total − must − should)/total. |
| requestreview | Santa-method dual-blind AND-gate: two independent reviewers (neither sees the other) must both pass (mustFix == 0 && score >= threshold). Bounded by MAX_REVIEW_ITERATIONS (5); a 6th iteration is forbidden. |
| respondreview | categorize (P0/P1 must-fix, P2 should-fix, P3 consider) + applyOrder (severity rank). If apply_fixes, applies in order then re-runs test/typecheck/lint. Hard gate: every finding is addressed (fix, disagree+document, or clarify). |
Output is rendered via renderReport in pair's ### P0…P3 + ## Verdict format.
/sf-flow-audit vs the code-review flow
Both run the same audit triad, so they look interchangeable — but the wrapper matters:
/sf-flow-audit | sf_flow_auto code-review | |
|---|---|---|
| Tier | 1 (built-in skill) | 2 (YAML flow) |
| What runs | the skill inline, in your current session | a generated pi-dw script that runs the skill phase INLINE — the orchestrator reads + executes the skill file (no nested agent) |
| Model source | agent .md frontmatter | agent .md frontmatter |
| Result | findings + verdict into your chat | a flow result — the skill phase's out is opaque (a placeholder string) |
| Gated loop | no (one-shot; apply_fixes applies once) | yes — audit↔fix group loop (auditor gates, developer fixes, re-verify until APPROVED) |
| Extensible | fixed skill steps | edit the YAML: add phases, chain it, version & share it |
| Input | target (git ref / file / workdir) | prompt · md-file · prd · jira |
Today code-review.yaml is an audit↔fix group loop: the auditor agent gates (finds P0-P3 + verdict), the developer agent fixes, and the auditor re-verifies until APPROVED or max_rounds. This gives it a structural advantage over the one-shot skill: findings are addressed and re-verified in a loop. Use the skill for a quick, zero-overhead audit in your current task. Use the flow when you want a reusable, shareable, composable artifact with a gated fix loop — e.g. chain it after plan + implement (that's ship-feature.yaml). Remember: every phase's agents get their model from their .md files (both tiers).
Group loops are the fix mechanism. The gate phase finds issues → the fix phase modifies code → the gate re-verifies → until APPROVED. Without the fix phase, the gate would see the same artifact each round and the loop could never close.
Want a gated audit loop in your own flow? Use a
groupsentry with an auditor gate phase + developer fix phase, and a matchingloopsentry withuntil: approved. Thecode-reviewflow demonstrates this pattern. Askillphase can't loop (it returns no structured verdict to gate on) — always useagentphases in groups.
Agent resolution
When a skill or phase needs to spawn an agent, the type is resolved deterministically:
- If an agent definition
<name>.mdexists → spawn that named agent (name). - Else
planner→ built-inPlan;reviewer→ built-inReviewer. - Anything else with no
.md→general-purpose.
A missing researcher.md does not fall back to the built-in Explore (which forces Haiku) — it yields general-purpose, inheriting the orchestrator model. This rule is encoded in code (resolveAgentType) + stated verbatim in every tier-1 skill, so the direct (tool) path and the workflow (skill: phase) path spawn the same agent type.
The orchestrator is orchestrator-only: in /sf-flow-implement it writes no code — it delegates each milestone to the developer agent and runs the per-milestone reviewer gate.
Agent isolation in flow workflows
Agents spawn either isolated (isolated: true: fresh context, extensions/skills/ext:* tools stripped, built-ins + bash + env + network preserved) or un-isolated (isolated: false: inherits parent, extensions/skills loaded per frontmatter). Among the built-in agents, only researcher is un-isolated (isolated: false + extensions: [web, atlassian]); explorer and analyst stay isolated.
| Aspect | isolated: true | isolated: false |
|---|---|---|
| Context | fresh (no parent) | inherits parent |
Extension tools (sf_web_*, confluence_*, …) | stripped | loaded per extensions: |
Built-in tools + bash + env + network | preserved | preserved |
| Skills | off | per skills: |
The agent .md frontmatter is authoritative ("sticky"): extensions: declared there load whenever the agent spawns — including from a workflow's inline agent() call. A flow YAML only NAMES agents — it carries no extensions:/isolated:/tools: fields at all; to change an agent's extensions, isolation, or tools, edit its .md. See the Agent Isolation & Auth guide for the full model and recipes.
Authenticated source access for flow agents
An un-isolated researcher can reach private sources:
- Private GitHub —
ghis pre-authenticated and works even when isolated:gh pr view <url> --json …,gh pr diff <url>. - Confluence / Jira — the
@pi-stef/atlassiantools (confluence_page,jira_issue, …) needATLASSIAN_BASE_URL,ATLASSIAN_EMAIL,ATLASSIAN_API_TOKEN. - Confluence SSO fallback —
sf_web_login(once) thensf_web_fetch { url, profile, mode: "browser" }.
Full recipes + an env-var checklist live in the Agent Isolation & Auth guide.
Plan standard (exhaustive milestone plans)
Plans are consumed by an implementer that may be a weaker model, so /sf-flow-plan enforces an exhaustive standard: every story must specify exact files + lines, a precise change (no vague verbs like "refactor"/"improve"), rationale, acceptance criteria, edge cases, test expectations, and dependencies — enough that a less-intelligent model can implement it with zero remaining design decisions. A completeness self-check runs before finalizing, and the reviewer gate REVISEs under-detailed stories independent of correctness. (This applies to both the plan tool and a workflow's plan phase — both execute the same skill.)
Configuration
Layered: project .pi/sf/flow/config.json over global ~/.pi/sf/flow/config.json over defaults. Partial configs are fine.
{
"audit": { "threshold": 0.94, "max_rounds": 5 },
"worktree": { "branch_prefix": "flow/" }
}| Key | Type | Default | Description |
|---|---|---|---|
audit.threshold | number | 0.94 | Dual-blind AND-gate pass score |
audit.max_rounds | integer | 5 | Max audit fix-loop iterations |
worktree.branch_prefix | string | flow/ | Branch prefix for implement worktrees |
freshReviewResetThreshold | number | 0.5 | Reset the delta-review to a fresh comprehensive review when the changed-lines ratio meets/exceeds this |
Models are NOT configured here — each agent's model lives in its .md frontmatter (see Model resolution). Config files still carrying legacy <role>.model groups are stripped with a one-time warning.
Model resolution
Agents' models are defined in ONE place: each agent's .md frontmatter (model:). Discovery: project .pi/agents/<name>.md overrides global getAgentDir()/agents/<name>.md (default ~/.pi/agent/agents/); a .md with no model: inherits the orchestrator. Flow passes no model at dispatch — pi-subagents applies the .md model natively (frontmatter is authoritative), in tier-1 skills and tier-2 workflow agents alike.
The old model channels —
config.jsonmodel groups,SF_FLOW_<ROLE>_MODELenv vars, the*_modeltool params, and the workflow YAML's per-agentmodel:field — were removed. Config carrying model groups is stripped with a one-time warning; set the model in the agent's.mdinstead.
Architecture
Skill-driven design
The tools are thin: each ensures agents exist + reports each agent's .md (path + carried model, read-only), then hands off to a SKILL.md containing the actual step sequence. The extension provides only config loading (runtime settings), write-once agent templates, agent-type resolution, and worktree helpers.
Model resolution
Each agent's model lives in its .md frontmatter (project .pi/agents overrides global); flow passes no model at dispatch. Agent types resolve by .md filename match (see Agent resolution).
Orchestrator-only implement
/sf-flow-implement writes no code: it delegates each milestone to the developer agent (TDD), runs the per-milestone reviewer gate, then the audit gate. The orchestrator only reads, spawns, parses, and aggregates.
Worktree lifecycle (implement)
- Create one git worktree with branch
flow/<slug>(git-only; non-git targets skip it). - Per milestone: delegate to the
developeragent (TDD) → reviewer loop → commit to the worktree branch → update the tracker. - Audit gate on the accumulated diff; on
REVISE, loop back to the failing story (bounded byaudit.max_rounds). sf_flow_finalizeremoves the worktree directory while preserving theflow/<slug>branch for a PR.
Plan-folder layout
ai_plan/YYYY-MM-DD-<slug>/
├── original-plan.md # Raw approved plan
├── final-transcript.md # Conversation log
├── milestone-plan.md # Full specification
├── story-tracker.md # Status tracking
└── continuation-runbook.md # Resume contextai_plan/ is gitignored.
Migration from team
flow replaces team's dynamic dispatch + audit on the pi-subagents / pi-dynamic-workflows foundation — without subprocess orchestration or milestone/story parallel lanes (deliberately dropped).
team | flow |
|---|---|
| plan / implement | sf_flow_plan / sf_flow_implement |
| audit | sf_flow_audit |
| user-defined workflows | Tier 2 YAML flows via sf_flow_create_workflow + sf_flow_auto |
| subprocess orchestration | dropped (pi-subagents instead) |
| parallel lanes | dropped |
flow is self-contained — it imports neither @pi-stef/agent-workflows nor any deprecated package — so it cannot be broken by their removal.
Key differences from pair
| Feature | pair | flow |
|---|---|---|
| Plan research | Single researcher | Fleet of parallel researchers |
| Implement gate | Reviewer loop | Reviewer loop + audit triad |
| Custom workflows | — | Tier 2 YAML (agents/phases/loops) |
| Code audit | — | CodeRabbit-style triad (sf_flow_audit) |
| Foundation | pi-subagents | pi-subagents + pi-dynamic-workflows |