edit-flow · git:20260629.cf4ad70 · 2026-06-29 · sha256 f62a1e06a6eeb0a3

edit-flow git:20260629.cf4ad70A

Immutable. This exact content is served forever at /api/v1/blob/f62a1e06a6eeb0a3.

---
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

1. Clarify what the user wants the flow to do. Ask if it is unclear.
2. 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.
3. 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.
4. Author the flow with `flow_write`. Wire every declared input. Fix any validation diagnostics and retry.
5. 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).

```markdown
---
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`)

```yaml
name: my-flow              # REQUIRED
description: What it does   # REQUIRED
max_concurrent: 3          # optional (default 4)
task_required: true        # optional — prompt for task if invoked with no args
task_prompt: "Task:"       # optional

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

1. **agent** — dispatch an agent.
   ```yaml
   - id: impl
     agent: implementer
     task: Implement ${{task}}
     blockedBy: [research]
     inputs:
       ctx: ${{result.research.summary}}
     on_complete: verify     # optional cross-segment jump
     on_error: handler        # optional
   ```
2. **fork** — user (or `agent:` in autonomous mode) picks a branch.
   ```yaml
   - id: choose
     type: fork
     question: Which strategy?
     options: [Fast, Full]
     branches: { Fast: fast-impl, Full: full-impl }
     agent: flow-decision     # autonomous-mode decider
     allowCustom: false
     multiSelect: false
   ```
   Branch keys must match `options` exactly.
3. **conditional** — presence/absence of a result field.
   ```yaml
   - id: has-gaps
     type: conditional
     check: research.artifacts   # stepId.field
     present: gap-filler
     absent: finalize
   ```
4. **agent-decision** — agent calls `finish({ branch })` to choose.
   ```yaml
   - id: complexity
     type: agent-decision
     agent: analyzer
     task: Simple or complex?
     branches: { simple: quick, complex: thorough }
   ```
5. **agent-loop-decision** — loop back or exit.
   ```yaml
   - 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       # jump back
     exit_target: done        # continue forward
     max_iterations: 3
   ```
6. **flow-ref** — delegate to another flow file.
   ```yaml
   - id: sub
     type: flow-ref
     path: "project/changes/*/exec.yaml"   # glob ok
     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.