A pi extension that adds workflow orchestration — define multi-step agent pipelines as simple JavaScript scripts and run them from the TUI.
- Just prompt: “/skill:create-workflow Create a workflow to analyze each file and decide what kind of tests are needed for each feature (unit or integration). Group by severity.”
- It will create a workflow and then execute it via /workflows, or the AI can automatically run it.
Run /dashboard to start a local web dashboard showing live workflow runs with a tree visualization of steps and phases.
- Discover workflows from
.pi/workflows/,.agents/workflows/,.pi-workflows/in the project tree and~/.pi/agent/workflows/globally - Execute workflows with the
workflowtool or/workflowcommand - Pipeline processing with concurrent items and sequential stages
- Agent spawning with optional JSON schema for structured output
- Run tracking with persistent status, steps, and results
pi install https://github.com/umutbasal/pi-workflowsgit clone https://github.com/umutbasal/pi-workflows.git
cd pi-workflows
bun installThen install the extension:
pi extensions add ./src/index.tsOr copy manually:
cp -r src ~/.pi/agent/extensions/pi-workflowscp -r skills/create-workflow ~/.pi/agent/skills/This installs the create-workflow skill which gives the AI full context on how to write workflows when asked to create one.
Create a .js or .ts file in .pi/workflows/:
// .pi/workflows/my-workflow.js
export const meta = {
name: "my-workflow",
description: "Does something useful",
phases: [
{ title: "Discover", detail: "find files" },
{ title: "Process", detail: "process each file" },
],
};
export default async function ({ agent, pipeline, step, log, args }) {
log("Starting...");
// Spawn an agent with full tool access
const files = await agent("Find all TypeScript files in src/", {
label: "find-files",
phase: "Discover",
schema: { type: "array", items: { type: "string" } },
});
// Process items through stages (concurrent within each stage)
const results = await pipeline(
files,
(file) => agent(`Analyze ${file}`, { label: `analyze:${file}`, phase: "Process" }),
);
return { files, results };
}Workflows receive a runtime object with:
| Function | Description |
|---|---|
agent(prompt, opts?) |
Spawn a sub-agent with full tool access (read/write/bash/grep). Returns text or parsed JSON if schema is provided. |
pipeline(items, ...stages) |
Process items through stages. Items within a stage run concurrently; stages run sequentially. |
step(name, phase, fn) |
Wrap arbitrary (non-agent) logic as a tracked step. Reports running→completed/failed to the runtime. Returns the value from fn(). |
log(message) |
Show a notification in the TUI. |
args |
Parsed JSON arguments passed to the workflow. |
interface AgentOptions {
label?: string; // Display name for step tracking
phase?: string; // Phase grouping (matches meta.phases)
schema?: Record<string, unknown>; // JSON schema for structured output
}type StepFn = <T>(name: string, phase: string, fn: () => T | Promise<T>) => Promise<T>;Use step() to wrap non-agent logic (data transformation, compilation, local computation) so it appears as a tracked step in the run. This is essential for phases that don't involve agent calls.
const report = await step("compile-report", "Report", async () => {
// local computation, no agent needed
return { summary: computeSummary(data) };
});Every phase declared in
meta.phasesMUST be tracked in the workflow code.
When writing workflows, follow these rules:
-
All declared phases must be used. If
meta.phaseslists a phase (e.g."Report"), the workflow code MUST reference that phase — either viaagent(..., { phase: "Report" })orstep("name", "Report", fn). A phase declared but never tracked is a bug. -
Use
step()for non-agent phases. If a phase involves only local computation (aggregation, formatting, filtering), wrap it instep(name, phase, fn). Do NOT leave it as bare code outside any phase tracking. -
Phase names must match exactly. The
phasestring inagent()options orstep()calls must match atitleinmeta.phasesexactly (case-sensitive). -
Destructure
stepfrom the runtime. The function signature should be:export default async function ({ agent, pipeline, step, log, args }) {
-
Return values from
step(). If the final phase produces the workflow result, return it directly:return await step("compile", "Report", async () => { return { summary, details }; });
-
One phase per logical unit. Don't mix multiple phases in a single
agent()orstep()call. Each tracked call should belong to exactly one phase.
Create .pi/workflows/test-plan.js:
export const meta = {
name: "test-plan",
description: "Analyze source files and decide what to test, how, and priority",
};
export default async function ({ agent, pipeline, log }) {
const files = await agent("List all source files in src/. Return only the file paths.", {
schema: { type: "array", items: { type: "string" } },
});
log(`Found ${files.length} files to analyze`);
const plans = await pipeline(files, (file) =>
agent(
`Analyze ${file} and decide:
- What should be tested?
- Test type: unit or integration?
- Priority: high, medium, or low?
Return a plan for this file.`,
{
label: `plan:${file}`,
schema: {
type: "object",
properties: {
file: { type: "string" },
tests: {
type: "array",
items: {
type: "object",
properties: {
description: { type: "string" },
type: { type: "string", enum: ["unit", "integration"] },
priority: { type: "string", enum: ["high", "medium", "low"] },
},
},
},
},
},
}
)
);
return plans;
}Run it:
/workflow test-plan
Use the workflow tool to start "my-workflow" with args: {"dir": "./src"}
/workflow my-workflow {"dir": "./src"}
/workflow list
| Action | Description |
|---|---|
start |
Execute a workflow (default) |
list |
List available workflows and recent runs |
status |
Check a run's status by run_id |
cancel |
Cancel a running workflow by run_id |
src/
├── index.ts # Extension entry point, tool & command registration
├── loader.ts # Workflow discovery & module loading
├── runtime.ts # Agent, pipeline, and log runtime creation
├── store.ts # Run persistence (JSON files in .pi-workflows/.runs/)
├── types.ts # TypeScript interfaces
├── runtime.test.ts # Unit tests for runtime (agent, pipeline, log)
├── store.test.ts # Unit tests for persistence layer
└── format.test.ts # Unit tests for run formatting
bun test 39 pass, 0 fail
75 expect() calls
Ran 39 tests across 3 files
Workflows are searched in this order (first match wins):
.pi/workflows/— project-local (traverses up to git root).agents/workflows/— project-local (traverses up to git root).pi-workflows/— project-local (traverses up to git root)~/.pi/agent/workflows/— global~/.agents/workflows/— global
Project workflows override global ones with the same name.
- security-workflows — Standalone usecase of pi workflows for security analysis
MIT
