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/. Workflows are scripts with implicit globals — no function wrapper needed:
// .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" },
],
};
log("Starting...");
// Spawn an agent with full tool access
phase("Discover");
const files = await agent("Find all TypeScript files in src/", {
label: "find-files",
schema: { type: "array", items: { type: "string" } },
});
// Process items through stages (concurrent within each stage)
phase("Process");
const results = await pipeline(
files,
(file) => agent(`Analyze ${file}`, { label: `analyze:${file}` }),
);
return { files, results };Workflows have these globals available:
| Global | 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. |
parallel(thunks) |
Run an array of zero-argument async functions concurrently. |
phase(name) |
Mark the current execution phase (for progress tracking/UI). |
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
}-
All declared phases must be used. If
meta.phaseslists a phase (e.g."Report"), callphase("Report")before the relevant code. -
Phase names must match exactly. The
phase()calls andagent(..., { phase: "X" })must match atitleinmeta.phasesexactly (case-sensitive). -
Use
returnfor the result. The workflow's return value is displayed to the user and persisted. -
One phase per logical unit. Don't mix multiple phases in a single
agent()call.
Create .pi/workflows/test-plan.js:
export const meta = {
name: "test-plan",
description: "Analyze source files and decide what to test, how, and priority",
};
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, meta extraction & source loading
├── executor.ts # Script execution engine (AsyncFunction with globals)
├── runtime.ts # Agent, pipeline, parallel, phase, log runtime creation
├── store.ts # Run persistence (JSON files in .pi-workflows/.runs/)
├── types.ts # TypeScript interfaces
├── runtime.test.ts # Unit tests for runtime + executor
├── loader.test.ts # Unit tests for meta/body extraction
├── store.test.ts # Unit tests for persistence layer
└── format.test.ts # Unit tests for run formatting
bun testWorkflows 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
