| name | anvil-workflow-builder |
| description | Use when the user wants to create or edit an automated multi-step workflow with gated checks. |
Anvil Workflow Builder
Help the user author a declarative anvil workflow. Interview them first, then write a workflow file and validate it.
Storage policy
Anvil workflows must be stored only in Anvil's workflow directories:
- User scope:
~/.pi/agent/anvil/workflows/<name>.ts
- Project scope:
.pi/anvil/workflows/<name>.ts
Do not write Anvil workflows to Pi's generic workflow locations such as .pi/workflows/saved/ or ~/.pi/workflows/saved/; those belong to other workflow implementations and may conflict.
Interview flow
Guide the user with pickers when Anvil-specific choices are missing. The user may know the desired workflow but not Anvil's required structure, so do not silently guess for ambiguous scope or gate choices.
- Choose a workflow name (
[a-z0-9-]+) and a one-sentence goal.
- Determine workflow scope.
- If the user explicitly says user/global/personal scope, use user scope (
~/.pi/agent/anvil/workflows/<name>.ts).
- If the user explicitly says project/repo/local scope, use project scope (
.pi/anvil/workflows/<name>.ts).
- If they do not specifically name the user or project scope, ask with a picker containing exactly these two choices: user scope or project scope.
- Decide workflow delegation defaults:
- Default to
delegation: "auto" unless the user specifically chooses otherwise; auto detects HERDR_ENV=1 as herdr first, then CMUX_SHELL_INTEGRATION=1 as cmux, and otherwise lets the main agent proceed.
delegation: { subagent: "cmux" } — Anvil itself spawns each step in a dedicated pi subagent session inside a cmux surface (declarative; requires running pi inside cmux). Per-step model/thinkingLevel are passed to the subagent.
delegation: { subagent: "herdr" } — Anvil itself spawns each step in a dedicated pi subagent session inside a herdr pane/tab (declarative; requires running pi inside herdr). Per-step model/thinkingLevel are passed to the subagent.
delegation: { skill: "<skill-name>" } — prompt hint to prefer a specific skill (the main agent decides).
delegation: "auto" — auto-detect herdr/cmux subagent support from the environment.
delegation: "none" — avoid subagents.
- For each step, capture:
id (stable kebab-case identifier)
- purpose / prompt
- optional per-step
model and/or thinkingLevel. Pi's thinking shorthand is colon-based (provider/model:high, not provider/model/high). Supported thinkingLevel values are off, minimal, low, medium, high, and xhigh. Omitted model/thinking values use the workflow-start defaults.
- optional
retryModelSelections when the user wants retry-aware model or thinking changes. retry: 0 is the first attempt; retry: 1 is the first retry. The highest retry less than or equal to the current retry count wins, and omitted fields fall back to the step's regular model/thinking. These selections affect main-session steps and declarative subagent launches.
- optional
subagentTimeoutMs on workflow defaults or individual steps that use declarative subagent delegation (defaults to 1,800,000ms).
- optional per-step
delegation override or runInMain: true
- whether the step should pass text to later steps. Later templates can reference prior outputs as
{outputs.<step-id>} or ctx.outputs["<step-id>"]; missing outputs are empty, and retry loops overwrite with the latest attempt. Resume matches the historical target by step id for its suggestion, while an explicit /anvil resume <step> is positional against the current definition. Completed surviving steps before the target are skipped and their bounded snapshots are restored by id; newly inserted or otherwise pending pre-target steps execute first, while the retry seed stays attached to the selected target. Target and downstream outputs are cleared. Session checkpoints are locally editable input, structurally validated and capped at 8 KiB UTF-8 bytes but not authenticated, so hostile checkpoint modification can inject restored prompt text. Raw snapshots never belong in maps, history, reports, summaries, or diagnostics. Declarative subagent steps capture their final summary automatically. Main-session steps only capture output if the agent calls anvil_output. For deterministic capture, set outputFrom: "<check-id>" on the step so that check's command output becomes the step output.
- For each step, determine whether it has gating checks.
- If the user explicitly provides one or more checks for the step, capture them.
- If the user explicitly says the step has no check / no gate, record no
checks for that step without asking again.
- If they are not explicit about whether there is no gating check for the step, ask with a picker: add a gating check, or no gating check.
- For each gating check, determine its type and details.
- Explain that checks may be deterministic or non-deterministic.
- Deterministic checks: a repeatable command or script, optional timeout/cwd. String commands render
{input}, {loop}, and {outputs.<step-id>} as quoted shell-variable expansions before running; function commands must quote any context values they interpolate. If the user says that a bash command must run successfully, treat it as implicitly deterministic.
- Non-deterministic checks: natural-language criteria judged by an agent, optional evaluation subagent, and optional
timeoutMs. Main-session grading defaults to 300,000ms. Main-session agent checks are self-graded by the same main agent, so they are not independent reviews.
- When independence matters, add
review: { subagent: "auto" | "cmux" | "herdr" }. This launches a fresh review-only session with bounded, sanitized rendered criteria, bounded identity fields (unsafe or over-256-byte identities use deterministic SHA-256 aliases across prompts and launcher requests/names, review path components over 255 bytes are aliased, and complete generated task/session, sidecar, and same-directory atomic temporary basenames are capped at 255 bytes), workspace guidance, and a bounded observable step result—not the executor transcript, hidden reasoning, raw terminal/provider output, retry feedback, or prior-step output. Only the current attempt's explicit main/chat anvil_output text or successful delegated final summary is eligible; missing output is explicit, and review criteria cannot interpolate {outputs.<step-id>}. Observable results are limited to 8 KiB including the marker, measured in UTF-8 bytes, with deterministic UTF-8-safe tail truncation. Unsupported controls are sanitized and common secret shapes—including Slack, GitLab, GitHub, OpenAI, AWS, NPM tokens, credential-bearing database URLs (including quoted .env and JSON forms), JWT, cookie, Basic/Bearer authorization, and private-key forms—are conservatively redacted; capture preprocessing is bounded to the final 64 Ki UTF-16 code units and discards a scan-boundary partial line (or reports missing output when no complete line remains) to prevent split credential labels from bypassing redaction. Ambiguous clipped or unmatched private-key markers fail closed as missing output. Redaction is not complete, so only intentionally disclosed text should be reported. The result remains prompt-only and is not persisted in workflow diagnostics. Reviewers can inspect workspace artifacts only through Anvil's bounded, read-only read, grep, find, and ls tools; reads are realpath-confined to the workflow cwd, symlink escapes and secret-like paths are denied, and shell/mutation tools stay disabled. This is constrained reviewer tool access, not a general-purpose OS sandbox. Reviewer-written reason prose is replaced with a fixed pass/fail reason before it can propagate through workflow state, because it may quote secrets. reviewFallback defaults to "fail" when no backend is available, producing a failed gate that follows the check's normal onFail policy; use reviewFallback: "main" only when the user explicitly accepts degraded self-grading. That fallback applies only when the backend is unavailable. A valid pass: false verdict remains a failed gate governed by onFail; review launch, timeout, non-zero exit, or verdict-transport failures are hard infrastructure errors and bypass fallback, onFail, retry feedback, and checkpoints. Delegated-step launch, timeout, transport, and non-zero failures likewise stop the workflow, including inside forEach regardless of onItemExhausted: "continue"; persisted diagnostics omit raw child/provider output, item text, secrets, and paths. Independent review has extra process cost, so its full launch-and-review timeout defaults to 1,800,000ms; explicit timeoutMs overrides that default.
- If the user is not explicit about whether a check is deterministic and it is not completely obvious, ask with a picker: deterministic command/script check, or non-deterministic agent-judged check.
- If the deterministic check does not name a script or command that already exists somewhere, offer to write the script/command for them as part of creating the workflow.
- For failures, choose
stop, continue, or { goto, maxLoops, onExhausted, feedback }. Make clear that onFail: "continue" advances to the next workflow step immediately and skips any remaining checks on the current step.
- Confirm a concise summary before writing the file.
Subagent pane command portability
Declarative subagent launches run inside user terminal panes, so generated launch commands must be portable across the user shells that Herdr or cmux may open.
- Do not emit raw
$? or other bash-only syntax directly into pane commands.
- Wrap generated subagent launch commands in
bash -lc when relying on POSIX/bash syntax for setup, exit-status capture, or sentinel output.
- Include fish-shell parse compatibility in subagent command regression tests when command-generation behavior changes.
- When adding terminal-screen fallback detection for subagent startup/errors, match the full prompt shape instead of a single phrase that an agent might quote while inspecting issues or logs.
- Add regression tests for false positives when subagent output contains sentinel-like strings or documented startup error text.
Per-item fan-out (forEach)
When a step's work is really "do this same thing to each of N items" — especially for small or local models that cannot hold a whole feature at once — propose a forEach step instead of asking the model to spawn its own subagents. The engine loops over the items deterministically; a small model choking on self-orchestration is the failure mode this avoids.
- Source the items deterministically when you can. The strongest pattern is a prior plan step gated by a deterministic check that its emitted list parses, then
items: { command: "cat <list-file>", parse: "lines" }. A function source (items: (ctx) => JSON.parse(ctx.outputs["plan"]).files) pairs with step outputs; it must return an array of strings.
- Keep each per-item prompt single-outcome. Write the prompt for one item ("write test stubs for
{item}"), not for the batch. Use {item}, {itemIndex} (zero-based), and {itemCount}; these are empty outside a forEach step.
- Prefer subagent delegation on fan-out steps so each item runs in a fresh session. Main-session/skill delegation runs items sequentially in the shared main session and defeats the point.
- Scope checks to the item (
command: "npx vitest run {item}"). A check's onFail: { goto } must target the step itself — that retries just that item with its own feedback. Do not goto another step from inside a forEach; validation rejects it.
- Use
onItemExhausted: "continue" when the user wants a best-effort sweep that reports failures rather than stopping at the first bad item; the step still fails if every item fails. maxItems caps enumeration. outputFrom is not available on a forEach step (its output is an automatic per-item digest).
- Concurrency is sequential today; do not promise parallel fan-out.
See examples/workflows/fan-out.ts for the full plan → gate → fan-out shape.
Template
Prefer the import form when possible:
import { defineWorkflow } from "anvil";
export default defineWorkflow({
name: "example-workflow",
description: "Short description.",
defaults: {
delegation: "auto",
onFail: "stop",
maxLoops: 3,
},
steps: [
{
id: "implement",
title: "Implement the change",
model: "openai-codex/gpt-5.5:high",
retryModelSelections: [
{ retry: 0, model: "openai-codex/gpt-5.5:minimal" },
{ retry: 1, model: "openai-codex/gpt-5.5", thinkingLevel: "high" },
],
prompt: "Implement this request: {input}",
outputFrom: "tests",
checks: [
{
type: "deterministic",
id: "tests",
name: "Test suite",
command: "npm test",
onFail: { goto: "implement", maxLoops: 2, feedback: true },
},
],
},
],
});
If TypeScript module resolution is awkward, a plain object default export is also valid:
export default {
name: "example-workflow",
steps: [{ id: "step-one", runInMain: true, prompt: "Do this: {input}" }],
};
Finish
Write the file to the chosen Anvil workflow path (~/.pi/agent/anvil/workflows/<name>.ts or .pi/anvil/workflows/<name>.ts), run /anvil validate <name>, fix any errors, and tell the user the invocation:
/anvil run <name> <task input>
For local development only, the user may explicitly choose /anvil run --watch <name> <task input>. Explain that watch mode is nondeterministic and checks a filesystem signature at outer step boundaries—never during a step or forEach item—then fresh-imports only the originally selected, canonical-path-pinned trusted workflow module when its workflow-root inputs changed. Broken or invalid candidates (including dangling onFail.goto) retain the last valid definition and emit a bounded warning. Valid edits reconcile completed/skipped state and outputs by stable step id, preserve an explicit pending goto by target id, discard removed outputs, and run newly inserted pending steps in current-definition order; active-definition titles, totals, routing, models, status, and summaries then apply. Checkpoint provenance is a revision counter only and contains no definition-derived fingerprint. Watch is not a workflow schema field, is never enabled for ordinary run/resume, and does not alter independent-review isolation. Do not recommend it for untrusted projects or deterministic unattended runs.