| name | edit-flow |
| description | Create and edit pi-flows flows and agents from the main session. Use when the user wants to create a new flow, add or change an agent, or edit an existing flow/agent. Covers agent frontmatter, flow YAML, step types, model references, the flow_agents and flow_write tools, write locations, and how to fix validation errors. |
| disable-model-invocation | false |
Edit Flow
You are creating and editing pi-flows flows and agents directly in this session. Two tools do the writing (both validate before writing and return diagnostics on failure):
flow_agents — op: "list" returns the agent catalog; op: "write" validates and writes an agent .md to .pi/flows/agents/<name>.md (filename derived from the agent's frontmatter name).
flow_write — namespace (default custom), name, content. Validates and writes a flow to .pi/flows/flows/<namespace>/<name>.yaml, which auto-registers as the /<namespace>:<name> command.
These tools derive their write locations from the discovery convention — there is no raw path. Writing to a name that already exists overwrites it (that is how you edit).
The edit-flow tools are off by default. They are active only when flows.editFlow: true is set in .pi/settings.json (project, when trusted) or ~/.pi/agent/settings.json (global). If flow_agents/flow_write are not available, tell the user to enable that setting and restart the session.
Workflow
- Clarify what the user wants the flow to do. Ask if it is unclear.
- Call
flow_agents with op: "list" to see existing agents and their inputs, outputs, and source_type. Reuse built-in/local agents where they fit.
- For each role not already covered, author a purpose-built agent with
flow_agents op: "write". Do not repurpose infrastructure agents (flow-decision, project-context-reader) for unrelated tasks.
- Author the flow with
flow_write. Wire every declared input. Fix any validation diagnostics and retry.
- Tell the user the resulting command name (
/<namespace>:<name>).
Editing an existing flow/agent = read the current file, change it, then write it back with the same tool and the same name/namespace.
Agent files (.md)
YAML frontmatter + Markdown body (the system prompt).
---
name: code-reviewer
description: Reviews source code for quality and correctness
model: @coding
thinking: medium
tools: read, grep, find
inputs:
- research_context
outputs:
- name: findings
description: Categorized issues found
- name: verdict
description: "pass" or "fail"
access:
read:
- "src/**"
write:
- "src/**"
bash:
deny:
- "rm -rf *"
card:
label: "Reviewer"
metric: "default"
architect:
use_when: "User wants code reviewed for quality"
produces: "A findings report and a pass/fail verdict"
depends_on: "An implementer must have produced code"
domain: "review"
---
# System Prompt
You are a code reviewer. Task: ${{task}}
Context: ${{input.research_context}}
| Field | Required | Notes |
|---|
name | Yes | Unique. Filename = <name>.md. Referenced as agent: <name> in steps. |
description | Yes | One line. Shown in catalog. |
model | Yes | See Model references. |
tools | Yes | Comma-separated. Guard blocks anything not listed. Standard: read, write, edit, grep, find, ls, bash, ask_user, skill_read. |
thinking | No | off/minimal/low/medium/high/xhigh. Overrides any :level suffix in model. |
skills | No | Comma-separated skill names injected into the prompt. |
inputs | No | Names → ${{input.NAME}} in the prompt. Flow step must wire each one. |
outputs | No | Names (or {name, description} objects). Become finish parameters and ${{result.STEP.NAME}} downstream. |
interactive | No | true allows mid-task UI prompts. Default false. |
output | No | Output file hint (display only). |
access | No | read/write glob allowlists; bash.deny command patterns. * = segment, ** = any depth. |
card | No | label, metric (default/files/tests/custom), role. |
architect | No | use_when/produces/depends_on/domain metadata surfaced in flow_agents op: list. |
Model references
The model: field accepts three forms. Prefer @role. Use the other two when a specific model is required regardless of role config, or when the user explicitly asks for a non-role model.
| Form | Example | When |
|---|
@role (preferred) | model: @coding | Default. Resolves via the active role→model map (/roles). Built-in roles: @planning, @coding, @fast, @architect. |
provider/model[:thinking] | model: anthropic/claude-haiku-4-5:high | A specific provider+model is required; optional :thinking suffix sets the thinking level. |
bare model-id | model: claude-haiku-4-5 | A specific model id with no provider qualifier; thinking comes from the thinking: field or none. |
A thinking: field always overrides any :thinking suffix in model.
Flow files (.yaml)
name: my-flow
description: What it does
max_concurrent: 3
task_required: true
task_prompt: "Task:"
steps:
- id: research
agent: code-reviewer
task: Review ${{task}}
name is the frontmatter name; the command name comes from the on-disk location (namespace/name you pass to flow_write). Every step needs a unique id. Step type is usually inferred from which fields are present; set type: explicitly when ambiguous.
Step types
- agent — dispatch an agent.
- id: impl
agent: implementer
task: Implement ${{task}}
blockedBy: [research]
inputs:
ctx: ${{result.research.summary}}
on_complete: verify
on_error: handler
- fork — user (or
agent: in autonomous mode) picks a branch.
- id: choose
type: fork
question: Which strategy?
options: [Fast, Full]
branches: { Fast: fast-impl, Full: full-impl }
agent: flow-decision
allowCustom: false
multiSelect: false
Branch keys must match options exactly.
- conditional — presence/absence of a result field.
- id: has-gaps
type: conditional
check: research.artifacts
present: gap-filler
absent: finalize
- agent-decision — agent calls
finish({ branch }) to choose.
- id: complexity
type: agent-decision
agent: analyzer
task: Simple or complex?
branches: { simple: quick, complex: thorough }
- agent-loop-decision — loop back or exit.
- id: should-fix
type: agent-loop-decision
agent: flow-decision
task: Iteration ${{loop.should-fix.iteration}}/${{loop.should-fix.max}}. ${{result.verify.summary}}
loop_target: fixer
exit_target: done
max_iterations: 3
- flow-ref — delegate to another flow file.
- id: sub
type: flow-ref
path: "project/changes/*/exec.yaml"
on_complete: verify
Template variables
Expanded in task, inputs values, and question. Not validated — a typo silently becomes empty string.
${{task}} — the user task.
${{input.NAME}} — input wired into this step.
${{result.STEP_ID.status|summary|artifacts|files|fullOutput|OUTPUTNAME}} — STEP_ID is the step id, not the agent name.
${{loop.STEP_ID.iteration|max}} — loop counters.
Wire data between steps via inputs: (agent declares names in frontmatter, step supplies values). Prefix an input value with file:// to inject file content verbatim; that file's producer step must be in blockedBy.
Write locations (discovery)
| Content | Tool | Lands at |
|---|
| Agent | flow_agents op: write | .pi/flows/agents/<name>.md |
| Flow | flow_write | .pi/flows/flows/<namespace>/<name>.yaml → /<namespace>:<name> |
Project-local definitions (.pi/flows/) override package and built-in ones.
Fixing validation errors
Both tools validate before writing. On failure they return { written: false, diagnostics: [...] } and write nothing. Read each diagnostic's message and suggestion, fix the content, and call the tool again. Common cases:
- Missing required field (
name, description, model, tools for agents; name, description for flows) → add it.
- "Agent not in catalog" → the flow references an agent that does not exist. Create it with
flow_agents op: write, then retry flow_write.
- Unwired declared input → add the missing key to the step's
inputs: block.
- Unknown tool in
tools: → use a valid tool name (see the standard list above) or an extension-registered tool name.
- Invalid YAML → fix indentation/quoting; re-validate.