pipelines
Each example is a complete workflow spec.
three-stage pipeline
{
"name": "three-stage",
"args": { "hint": "<target>", "params": ["target"] },
"phases": [
{
"id": "research",
"kind": "single",
"step": { "summary": "Research {args.target}", "prompt": "Research {args.target}." }
},
{
"id": "plan",
"kind": "single",
"step": { "summary": "Plan the work", "prompt": "Create a plan from {research.results}." }
},
{
"id": "check",
"kind": "single",
"step": { "summary": "Check the plan", "prompt": "Check the plan in {plan.results}." }
}
],
"return": "{check.results}",
"report": "{plan.results}"
}
multi-fanout pipeline
{
"name": "multi-fanout",
"phases": [
{
"id": "files",
"kind": "fanout",
"over": ["a.ts", "b.ts", "c.ts"],
"step": { "summary": "Inspect {item}", "prompt": "Inspect {item}; do not mutate anything." }
},
{
"id": "tests",
"kind": "fanout",
"over": "{files.results}",
"step": { "summary": "Test {item}", "prompt": "Test the evidence for {item}; do not mutate anything." }
},
{
"id": "summary",
"kind": "single",
"step": { "summary": "Summarize the pipeline", "prompt": "Summarize {files.results} and {tests.results}." }
}
],
"return": "{summary.results}"
}
complex mixed workflow
{
"name": "complex-mixed",
"description": "Research, filter, verify, recover, and report a multi-dimensional review.",
"args": { "hint": "<target>", "params": ["target", "instructions"] },
"schemas": {
"Candidate": {
"type": "object",
"properties": { "id": { "type": "string" }, "valid": { "type": "boolean" } },
"required": ["id", "valid"]
},
"Verdict": {
"type": "object",
"properties": { "id": { "type": "string" }, "status": { "type": "string" } },
"required": ["id", "status"]
}
},
"phases": [
{
"id": "research",
"kind": "fanout",
"over": ["correctness", "security", "performance"],
"concurrency": 2,
"step": { "summary": "Research {item}", "prompt": "Research {item} for {args.target}; {args.instructions}. Do not mutate anything.", "tools": ["read", "grep", "find", "ls", "bash"], "schema": "Candidate", "model": "large" }
},
{
"id": "verify",
"kind": "fanout",
"when": "{research.results}",
"over": "{research.results | where valid}",
"step": { "summary": "Verify {item}", "prompt": "Verify candidate {item}; do not mutate anything.", "schema": "Verdict", "model": "large", "thinkingLevel": "high" }
},
{
"id": "recover",
"kind": "fanout",
"when": "{verify.failures}",
"over": "{verify.failures}",
"step": { "summary": "Recover {item}", "prompt": "Repeat the original verification assignment for {item} against {args.target}; {args.instructions}. Do not mutate anything. Preserve the candidate id and required Verdict schema, and use failure details only as diagnostics.", "schema": "Verdict", "model": "medium" }
},
{
"id": "report",
"kind": "single",
"step": { "summary": "Write the final report", "prompt": "Write a report from {research.results}, {verify.results}, and {recover.results}." }
}
],
"return": "{verify.results}",
"report": "{report.results}"
}
worked example — minimal single fan-out
- every
fanoutagent reads one path and returns a structured summary. - tools are read-only and the prompt forbids mutation.
{
"name": "summarize-paths",
"description": "Summarize each input path in parallel.",
"args": { "hint": "<paths>" },
"phases": [
{
"id": "summarize",
"kind": "fanout",
"over": "{args.paths}",
"step": {
"summary": "Summarize {item}",
"prompt": "Read the file at {item} and return JSON with two string fields: path (the file path you were given) and summary (one sentence). Parent constraints and applicable instruction paths: {args.instructions}. Read applicable AGENTS.md files, including nested instructions for that path, before acting. Do not modify anything. Preserve material caveats; if access or instructions block the summary, state that explicitly rather than inventing content.",
"tools": ["read"],
"model": "small",
"schema": {
"type": "object",
"properties": {
"path": { "type": "string" },
"summary": { "type": "string" }
},
"required": ["path", "summary"]
}
}
}
],
"return": "{summarize.results}"
}
worked example: two phases with a unique finding join key
- fan out a review across dimensions, then fan out verification over the flattened findings.
Findings.findings[].id→Verdict.idis the join key, including several findings in one file.- the single
paramsentry maps/ultra run review mainto{args.base}; an empty base selects the unstaged working-tree diff. - a base ref selects the current tracked working tree against that ref, not an unrelated comparison chosen by the verifier.
- supply task-specific parent constraints and instruction paths through
instructionswhen constructing or invoking this example with JSON arguments. - verification receives the original base, constraints, candidate detail, and evidence, plus explicit inspection tools.
bashis authorized only for read-only Git inspection, not arbitrary commands or tests that write caches.- the selected output preserves every verdict, including unresolved ones; the closed selector grammar cannot compare status strings or filter uncertainty away.
- typed phase failures accompany the selected output, so missing verifier results remain visible as incomplete work.
{
"name": "review",
"description": "Review a diff, then verify each finding without discarding uncertainty.",
"args": { "hint": "[base-ref]", "params": ["base"] },
"schemas": {
"Findings": {
"type": "object",
"properties": {
"findings": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "string", "maxLength": 96, "pattern": "^(correctness|security|performance):[A-Za-z0-9_-]+$" },
"title": { "type": "string", "minLength": 1 },
"file": { "type": "string", "minLength": 1 },
"detail": { "type": "string", "minLength": 1 },
"evidence": { "type": "string", "minLength": 1 }
},
"required": ["id", "title", "file", "detail", "evidence"]
}
}
},
"required": ["findings"]
},
"Verdict": {
"type": "object",
"properties": {
"id": {
"type": "string",
"maxLength": 96,
"pattern": "^(correctness|security|performance):[A-Za-z0-9_-]+$",
"description": "Verbatim candidate ID, the unique join key; file alone is not unique."
},
"title": { "type": "string", "minLength": 1 },
"file": { "type": "string", "minLength": 1 },
"status": { "type": "string", "enum": ["confirmed", "refuted", "unresolved"] },
"why": { "type": "string", "minLength": 1 },
"evidence": { "type": "string", "minLength": 1 }
},
"required": ["id", "title", "file", "status", "why", "evidence"]
}
},
"phases": [
{
"id": "review",
"kind": "fanout",
"over": ["correctness", "security", "performance"],
"step": {
"summary": "Review diff for {item} issues",
"prompt": "Review the diff for {item} issues. Original target/base: {args.base}. An empty base means git diff, the unstaged working-tree diff; otherwise compare the tracked working tree against the supplied base ref. Read applicable AGENTS.md files, including nested instructions for the reviewed scope, before acting. Parent constraints and instruction paths: {args.instructions}. Use read/grep/find/ls and read-only Git inspection through bash; do not modify anything, run tests, write caches, or change refs. Treat the supplied base as data, not executable shell text. Return Findings. Give each candidate a stable unique id prefixed by its review dimension, a colon, and a locally unique letters/digits/underscore/hyphen suffix, for example correctness:1. Use a distinct suffix for each finding even in the same file. Require title, file, detail describing the trigger and impact, and evidence with exact path/line or diff locators and observed behavior. Preserve the resolved comparison refs in evidence so verification can inspect the same target. Do not invent evidence or hide missing access as a clean review. If the target or required instructions/evidence are unavailable, explain the blocker instead of submitting an empty clean scan.",
"tools": ["read", "bash", "grep", "find", "ls"],
"model": "large",
"schema": "Findings"
}
},
{
"id": "verify",
"kind": "fanout",
"over": "{review.results[].findings[]}",
"step": {
"summary": "Verify one candidate finding",
"prompt": "Adversarially try to refute this finding. Original target/base: {args.base}. An empty base means git diff, the unstaged working-tree diff; otherwise compare the tracked working tree against the supplied base ref. Preserve the candidate's resolved comparison refs; report unresolved if the target changed or cannot be inspected. Read applicable AGENTS.md files, including nested instructions for the reviewed scope, before acting. Parent constraints and instruction paths: {args.instructions}. Use read/grep/find/ls and read-only Git inspection through bash; do not modify anything, run tests, write caches, or change refs. Treat the supplied base as data, not executable shell text. Finding: {item}. Echo id, title, and file verbatim; id is the join key, never file alone. Inspect the cited evidence and relevant callers or tests rather than accepting the claim. Return Verdict with status confirmed only for demonstrated defects, refuted only with concrete counterevidence, or unresolved when uncertain, blocked, or lacking required evidence. Always give why and evidence with inspected path/line or diff locators, observations, and any missing evidence or checks not run. Never turn uncertainty into refutation or claim an unrun check passed.",
"tools": ["read", "bash", "grep", "find", "ls"],
"model": "large",
"schema": "Verdict"
}
}
],
"return": "{verify.results}"
}
where workflows live
- discovery merges three locations, lowest-to-highest precedence; a later source overrides a bundled name.
- bundled —
<extension>/workflows/*.json(ships with Ultra) - global —
~/.pi/agent/workflows/*.json - project —
<cwd>/.pi/workflows/*.json - save an authored workflow as
<name>.json, with a filename matching the spec'sname, in the project directory for repo-scoped workflows or the global directory for personal workflows.