pi-taskflow 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +237 -53
- package/extensions/runtime.ts +5 -2
- package/package.json +1 -1
- package/skills/taskflow/SKILL.md +14 -0
- package/skills/taskflow/configuration.md +275 -0
package/README.md
CHANGED
|
@@ -1,94 +1,238 @@
|
|
|
1
1
|
# pi-taskflow
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/pi-taskflow)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
[](https://pi.dev)
|
|
6
|
+
|
|
3
7
|
> Lightweight workflow orchestration for the [Pi coding agent](https://pi.dev).
|
|
4
8
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
**Orchestrate your Pi subagents. Not by prompting — by declaring.**
|
|
10
|
+
|
|
11
|
+
If you've used the built-in subagent tool's `task` / `tasks` / `chain`, you
|
|
12
|
+
already know the shorthand — your runs just get tracked, resumable, and
|
|
13
|
+
saveable as a one-word `/tf:<name>` command.
|
|
9
14
|
|
|
10
15
|
```bash
|
|
11
16
|
pi install npm:pi-taskflow
|
|
12
17
|
```
|
|
13
18
|
|
|
19
|
+
Fan out one subagent per item, gate the results with an adversarial review, and
|
|
20
|
+
get back only the final report — none of the intermediate transcripts ever touch
|
|
21
|
+
your conversation.
|
|
22
|
+
|
|
14
23
|
## Why
|
|
15
24
|
|
|
16
|
-
|
|
17
|
-
coordinated steps
|
|
18
|
-
or a
|
|
19
|
-
|
|
25
|
+
The built-in subagent tool is great for a single delegated task. But when a job
|
|
26
|
+
needs many coordinated steps, fan-out over dozens of items, cross-checked review,
|
|
27
|
+
or a repeatable pipeline, you want orchestration — without the intermediate
|
|
28
|
+
transcripts eating your context window.
|
|
20
29
|
|
|
21
30
|
`pi-taskflow` moves the plan into a small declarative definition. The runtime
|
|
22
31
|
holds the DAG, the loops, and the intermediate results; your context receives
|
|
23
32
|
only the final phase's output.
|
|
24
33
|
|
|
25
|
-
| | `subagent` | `pi-taskflow` |
|
|
34
|
+
| | `subagent` tool | `pi-taskflow` |
|
|
26
35
|
|---|---|---|
|
|
27
36
|
| Who drives | the model, turn by turn | the runtime, from a definition |
|
|
28
37
|
| Intermediate results | in your context window | in the runtime (not your context) |
|
|
29
38
|
| Reusable | re-described each time | saved as `/tf:<name>` |
|
|
30
39
|
| Scale | a few tasks | dynamic `map` fan-out |
|
|
31
|
-
| Resumable | no | yes (cross-session) |
|
|
40
|
+
| Resumable | no | yes (cross-session, cached phases skip) |
|
|
41
|
+
| Quality gates | no | `gate` phases with `VERDICT: BLOCK / PASS` |
|
|
42
|
+
| Progress visibility | opaque while running | live DAG render with timing + cost |
|
|
43
|
+
| Ergonomics | inline JSON each time | shorthand (`task`/`tasks`/`chain`) or DSL |
|
|
44
|
+
|
|
45
|
+
## Show me
|
|
46
|
+
|
|
47
|
+
Describe a pipeline once, then run it from a pi session by name:
|
|
48
|
+
|
|
49
|
+
> `/tf:summarize-files dir=src`
|
|
50
|
+
|
|
51
|
+
The runtime fans out one subagent per file, merges the summaries in a `reduce`
|
|
52
|
+
phase, and returns only the final overview. Every intermediate transcript stays
|
|
53
|
+
in the runtime — never in your context window. (Full definition in
|
|
54
|
+
[Quickstart](#then-go-declarative) below.)
|
|
55
|
+
|
|
56
|
+
## Quickstart
|
|
57
|
+
|
|
58
|
+
### Shorthand: same effort as `subagent`, but tracked & resumable
|
|
59
|
+
|
|
60
|
+
**Single task** — one agent, one job:
|
|
61
|
+
|
|
62
|
+
```jsonc
|
|
63
|
+
{ "task": "Summarize the architecture of src/", "agent": "explorer" }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Parallel tasks** — fire several at once, outputs merge:
|
|
67
|
+
|
|
68
|
+
```jsonc
|
|
69
|
+
{ "tasks": [
|
|
70
|
+
{ "task": "Audit auth in src/api", "agent": "analyst" },
|
|
71
|
+
{ "task": "Audit input validation in src/api", "agent": "analyst" }
|
|
72
|
+
] }
|
|
73
|
+
```
|
|
32
74
|
|
|
33
|
-
|
|
75
|
+
**Chain** — sequential, each step sees the previous one's output:
|
|
76
|
+
|
|
77
|
+
```jsonc
|
|
78
|
+
{ "chain": [
|
|
79
|
+
{ "task": "List the public API of src/lib", "agent": "scout" },
|
|
80
|
+
{ "task": "Write docs for:\n{previous.output}", "agent": "writer" }
|
|
81
|
+
] }
|
|
82
|
+
```
|
|
34
83
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
run concurrently (bounded by `concurrency`).
|
|
84
|
+
`agent` is optional (defaults to the first available agent). Add `name` to label
|
|
85
|
+
the run and enable saving it as a reusable command.
|
|
38
86
|
|
|
39
|
-
|
|
87
|
+
Try it inline — tell the model something like:
|
|
40
88
|
|
|
41
|
-
|
|
42
|
-
|------|---------|
|
|
43
|
-
| `agent` | one subagent runs `task` |
|
|
44
|
-
| `parallel` | run `branches[]` concurrently |
|
|
45
|
-
| `map` | fan out over an array — one subagent per item, `{item}` bound |
|
|
46
|
-
| `gate` | quality/adversarial-review step |
|
|
47
|
-
| `reduce` | aggregate several phases' outputs into one |
|
|
89
|
+
> Run a chain: first explore the auth flow, then summarize findings.
|
|
48
90
|
|
|
49
|
-
|
|
91
|
+
The model calls the `taskflow` tool; you get live progress, per-step timing,
|
|
92
|
+
token cost, and a run record. Ask to `save` it and you get `/tf:<name>`.
|
|
50
93
|
|
|
51
|
-
|
|
52
|
-
- `{steps.ID.output}` — a prior phase's text output
|
|
53
|
-
- `{steps.ID.json}` / `{steps.ID.json.field}` — prior output parsed as JSON
|
|
54
|
-
- `{item}` / `{item.field}` — current item inside a `map` phase
|
|
55
|
-
- `{previous.output}` — the immediately-upstream phase output
|
|
94
|
+
### Then go declarative
|
|
56
95
|
|
|
57
|
-
|
|
96
|
+
When your pipeline outgrows the shorthand — when you need dynamic fan-out,
|
|
97
|
+
intermediate JSON routing, or quality gates — graduate to the full DSL:
|
|
58
98
|
|
|
59
99
|
```jsonc
|
|
60
100
|
{
|
|
61
101
|
"name": "summarize-files",
|
|
102
|
+
"description": "Discover files, summarize each, produce a report",
|
|
62
103
|
"args": { "dir": { "default": "." } },
|
|
63
|
-
"concurrency":
|
|
104
|
+
"concurrency": 8,
|
|
64
105
|
"phases": [
|
|
65
106
|
{ "id": "discover", "type": "agent", "agent": "scout",
|
|
66
|
-
"task": "List source files under {args.dir}
|
|
107
|
+
"task": "List source files under {args.dir} (non-recursive).\nOutput ONLY a JSON array [{\"file\":\"\"}]. No prose.",
|
|
67
108
|
"output": "json" },
|
|
68
|
-
{ "id": "summarize", "type": "map",
|
|
69
|
-
"
|
|
109
|
+
{ "id": "summarize", "type": "map",
|
|
110
|
+
"over": "{steps.discover.json}", "as": "item",
|
|
111
|
+
"agent": "scout",
|
|
112
|
+
"task": "Read {item.file} and give a one-sentence summary.",
|
|
70
113
|
"dependsOn": ["discover"] },
|
|
71
|
-
{ "id": "report", "type": "reduce", "from": ["summarize"],
|
|
114
|
+
{ "id": "report", "type": "reduce", "from": ["summarize"],
|
|
115
|
+
"agent": "writer",
|
|
72
116
|
"task": "Combine into a short overview:\n{steps.summarize.output}",
|
|
73
117
|
"dependsOn": ["summarize"], "final": true }
|
|
74
118
|
]
|
|
75
119
|
}
|
|
76
120
|
```
|
|
77
121
|
|
|
78
|
-
|
|
122
|
+
What this does:
|
|
123
|
+
|
|
124
|
+
1. **`discover`** — an agent lists every file in the directory and outputs a JSON array.
|
|
125
|
+
2. **`summarize`** — a `map` fans out, spawning one subagent per file in parallel
|
|
126
|
+
(throttled to 8 concurrent). Each gets `{item.file}` bound to its file path.
|
|
127
|
+
3. **`report`** — a `reduce` merges all summaries into one clean overview.
|
|
128
|
+
|
|
129
|
+
Intermediate outputs never enter your context. The runtime owns them. You get
|
|
130
|
+
only the final report back.
|
|
131
|
+
|
|
132
|
+
Save it once → `/tf:summarize-files` forever.
|
|
79
133
|
|
|
80
|
-
|
|
134
|
+
## Watch it run
|
|
135
|
+
|
|
136
|
+
This is the live progress render for a real run — the `self-improve` flow that
|
|
137
|
+
writes and verifies its own test suites, caught here mid-block by a quality gate:
|
|
81
138
|
|
|
82
139
|
```
|
|
83
|
-
/
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
140
|
+
⊗ taskflow self-improve 6/7 · blocked · $0.095
|
|
141
|
+
✓ discover agent deepseek-v4-flash 10t ↑38k ↓6.7k $0.011
|
|
142
|
+
┌ ✓ write-runner-tests agent claude-sonnet-4-6 10t ↑13 ↓6.6k $0.020
|
|
143
|
+
├ ✓ write-store-tests agent claude-sonnet-4-6 10t ↑11 ↓10k $0.018
|
|
144
|
+
├ ✓ write-agents-tests agent claude-sonnet-4-6 10t ↑28 ↓13k $0.030
|
|
145
|
+
└ ✓ fix-stability agent claude-sonnet-4-6 10t ↑13 ↓3.9k $0.012
|
|
146
|
+
✓ verify gate BLOCK 3 type errors in test files deepseek-v4-flash
|
|
147
|
+
⊘ report reduce skipped · Gate blocked ↳ fix-stability
|
|
89
148
|
```
|
|
90
149
|
|
|
91
|
-
|
|
150
|
+
**How to read it — the layout *is* the DAG:**
|
|
151
|
+
|
|
152
|
+
- **Header** — `⊗` means the flow is blocked (a gate halted it); `6/7` phases
|
|
153
|
+
processed, aggregate cost `$0.095`.
|
|
154
|
+
- **Status icons** — `✓` done, `◐` running, `✗` failed, `⊘` skipped, `○` pending.
|
|
155
|
+
- **Rail `┌ ├ └`** — phases in the same DAG layer, running concurrently. The four
|
|
156
|
+
`write-*`/`fix-stability` tasks all fan out from `discover`. A blank gutter is
|
|
157
|
+
a single-phase layer.
|
|
158
|
+
- **`↳`** — a long (layer-skipping) dependency. `report` depends on `verify` (the
|
|
159
|
+
adjacent layer, implied by position) *and* `fix-stability` two layers back, so
|
|
160
|
+
only that skip edge is annotated.
|
|
161
|
+
- **Gate** — `verify` emitted `VERDICT: BLOCK`, so the runtime skipped `report`
|
|
162
|
+
and ended the run as `blocked`, surfacing the reason.
|
|
163
|
+
- **Detail** — per phase: model, token counts (`↑`in `↓`out), cost, and timing.
|
|
164
|
+
Fan-out phases also show sub-task progress.
|
|
165
|
+
|
|
166
|
+
## Phase types
|
|
167
|
+
|
|
168
|
+
| type | meaning | required fields |
|
|
169
|
+
|------|---------|-----------------|
|
|
170
|
+
| `agent` | one subagent runs a single task | `task` |
|
|
171
|
+
| `parallel` | run `branches[]` concurrently | `branches` (array of `{task, agent?}`) |
|
|
172
|
+
| `map` | fan out over an array — one subagent per item, `{item}` bound | `over`, `task` |
|
|
173
|
+
| `gate` | quality/review step that can **halt the flow** | `task` |
|
|
174
|
+
| `reduce` | aggregate `from[]` phase outputs into one | `from`, `task` |
|
|
175
|
+
|
|
176
|
+
Every phase needs `id`. Optional fields: `agent`, `dependsOn`, `output`,
|
|
177
|
+
`model`, `thinking`, `tools`, `cwd`, `concurrency`, `final`, `optional`.
|
|
178
|
+
|
|
179
|
+
### `output` format
|
|
180
|
+
|
|
181
|
+
- `output: "text"` (default) — the raw subagent output.
|
|
182
|
+
- `output: "json"` — the subagent output is parsed as JSON and exposed via
|
|
183
|
+
`{steps.ID.json}` / `{steps.ID.json.field}`. Set this on phases whose output
|
|
184
|
+
a downstream `map` or `reduce` needs to consume as structured data.
|
|
185
|
+
|
|
186
|
+
There is no `output: "file"`. For file-based output, have the agent write to
|
|
187
|
+
disk with a `write` tool call.
|
|
188
|
+
|
|
189
|
+
### Gate phases (quality control)
|
|
190
|
+
|
|
191
|
+
A `gate` runs an agent to review upstream output and can **block the rest
|
|
192
|
+
of the workflow**. End the gate task's instructions by asking the agent to
|
|
193
|
+
emit a verdict the runtime can read:
|
|
194
|
+
|
|
195
|
+
- a final line `VERDICT: PASS` or `VERDICT: BLOCK` (also accepts `OK`, `FAIL`,
|
|
196
|
+
`STOP`, `REJECT`, `HALT` — last occurrence wins), or
|
|
197
|
+
- JSON like `{"continue": false, "reason": "missing auth checks"}` /
|
|
198
|
+
`{"verdict": "block", "reason": "..."}`.
|
|
199
|
+
|
|
200
|
+
On **BLOCK**, downstream phases are skipped and the run ends as `blocked` with
|
|
201
|
+
the reason surfaced. **Ambiguous output fails open** (treated as PASS) — a gate
|
|
202
|
+
never halts the flow by accident.
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
Review the audit results below. If any endpoint is missing auth, end with
|
|
206
|
+
"VERDICT: BLOCK" and a one-line reason; otherwise end with "VERDICT: PASS".
|
|
207
|
+
|
|
208
|
+
{steps.audit.output}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Interpolation
|
|
212
|
+
|
|
213
|
+
| placeholder | resolves to |
|
|
214
|
+
|---|---|
|
|
215
|
+
| `{args.X}` | invocation argument |
|
|
216
|
+
| `{steps.ID.output}` | a prior phase's text output |
|
|
217
|
+
| `{steps.ID.json}` | prior output parsed as JSON (or `{steps.ID.json.field}`) |
|
|
218
|
+
| `{item}` / `{item.field}` | current item inside a `map` phase |
|
|
219
|
+
| `{previous.output}` | the immediately-upstream phase output |
|
|
220
|
+
|
|
221
|
+
## Commands
|
|
222
|
+
|
|
223
|
+
Saved flows become CLI shortcuts. All commands work in the pi session:
|
|
224
|
+
|
|
225
|
+
| Command | What it does |
|
|
226
|
+
|---|---|
|
|
227
|
+
| `/tf list` | List all saved flows |
|
|
228
|
+
| `/tf run <name> [args]` | Run a saved flow (e.g. `/tf run summarize-files dir=src`) |
|
|
229
|
+
| `/tf show <name>` | Print a flow's definition |
|
|
230
|
+
| `/tf runs` | Browse recent run history (interactive TUI) |
|
|
231
|
+
| `/tf resume <runId>` | Continue a paused/failed run — cached phases skip automatically |
|
|
232
|
+
| `/tf:<name> [args]` | Shortcut — runs the flow in one tap |
|
|
233
|
+
|
|
234
|
+
Tool actions (used by the model): `run` (inline `define` or saved `name`),
|
|
235
|
+
`save`, `resume`, `list`.
|
|
92
236
|
|
|
93
237
|
## Storage
|
|
94
238
|
|
|
@@ -98,30 +242,70 @@ Tool actions: `run` (inline `define` or saved `name`), `save`, `resume`, `list`.
|
|
|
98
242
|
.pi/taskflows/runs/<runId>.json # run state (resume); gitignore this
|
|
99
243
|
```
|
|
100
244
|
|
|
245
|
+
Agent discovery scope (set via `agentScope` in the flow definition):
|
|
246
|
+
|
|
247
|
+
| value | discovers agents from |
|
|
248
|
+
|---|---|
|
|
249
|
+
| `"user"` (default) | `~/.pi/agent/agents/*.md` |
|
|
250
|
+
| `"project"` | `.pi/agents/*.md` (walks up the tree) |
|
|
251
|
+
| `"both"` | user + project; project wins on name collision |
|
|
252
|
+
|
|
101
253
|
## Agents
|
|
102
254
|
|
|
103
|
-
Taskflow reuses your existing pi
|
|
104
|
-
`.pi/agents/*.md`)
|
|
105
|
-
|
|
255
|
+
Taskflow reuses your existing pi agent files (`~/.pi/agent/agents/*.md`,
|
|
256
|
+
`.pi/agents/*.md`). Reference agents by `name` in a phase or shorthand.
|
|
257
|
+
|
|
258
|
+
When running a phase, the runtime extracts the agent's `systemPrompt` from its
|
|
259
|
+
`.md` frontmatter and passes it via `--append-system-prompt` (written to a temp
|
|
260
|
+
file). Phase-level overrides for `model`, `thinking`, and `tools` are passed as
|
|
261
|
+
`--model` / `--thinking` / `--tools` flags to the subagent invocation.
|
|
262
|
+
|
|
263
|
+
Settings from `~/.pi/agent/settings.json` (the `subagents.agentOverrides` map)
|
|
264
|
+
are honored, letting you tweak model, thinking, or tools per agent across all flows.
|
|
265
|
+
|
|
266
|
+
## Status & limits
|
|
267
|
+
|
|
268
|
+
- **v0.0.4** — DSL + DAG runtime (`agent`/`parallel`/`map`/`gate`/`reduce`),
|
|
269
|
+
inline + saved flows, cross-session resume, live progress, isolated context.
|
|
270
|
+
Default `concurrency` is 8 (set on the flow; per-phase `concurrency` overrides
|
|
271
|
+
for that phase).
|
|
272
|
+
- A run executes as one streaming tool call (live progress while it runs).
|
|
273
|
+
- `map` requires the upstream phase to emit a JSON array (`output: "json"`).
|
|
274
|
+
- Gate verdicts are **fail-open**: if the agent output contains no recognizable
|
|
275
|
+
verdict marker (`VERDICT: BLOCK/PASS/OK/FAIL/STOP/REJECT/HALT` or
|
|
276
|
+
`{continue: false}` / `{verdict: "block"}`), the gate passes. This prevents
|
|
277
|
+
an accidental missing verdict from blocking your workflow.
|
|
278
|
+
|
|
279
|
+
### What it doesn't do (yet)
|
|
280
|
+
|
|
281
|
+
- **No detached background execution.** A run needs the pi session to stay open.
|
|
282
|
+
True background execution is on the roadmap.
|
|
283
|
+
- **No `output: "file"`.** Outputs are text/JSON only. Write files via agent
|
|
284
|
+
tool calls if needed.
|
|
285
|
+
- **`map` requires a JSON array.** The `over` field must resolve to
|
|
286
|
+
`{steps.ID.json}` where the upstream phase emitted `output: "json"`. If the
|
|
287
|
+
source is a plain text list, wrap it in a single-agent phase that outputs JSON.
|
|
288
|
+
- **Cycles are rejected at validation.** The DAG must be acyclic.
|
|
106
289
|
|
|
107
290
|
## Development
|
|
108
291
|
|
|
109
292
|
```bash
|
|
110
293
|
npm install
|
|
111
294
|
npm run typecheck
|
|
112
|
-
node --experimental-strip-types --test test/interpolate.test.ts
|
|
295
|
+
node --experimental-strip-types --test test/interpolate.test.ts \
|
|
296
|
+
test/schema.test.ts test/runtime.test.ts test/runner.test.ts \
|
|
297
|
+
test/store.test.ts test/agents.test.ts test/render.test.ts \
|
|
298
|
+
test/desugar.test.ts
|
|
113
299
|
|
|
114
300
|
# real end-to-end (spawns live subagents; needs model access)
|
|
115
301
|
PI_TASKFLOW_PI_BIN=pi node --experimental-strip-types test/e2e.mts
|
|
116
302
|
```
|
|
117
303
|
|
|
118
|
-
##
|
|
304
|
+
## Contributing
|
|
119
305
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
detached background execution is on the roadmap.
|
|
124
|
-
- `map` requires the upstream phase to emit a JSON array (`output: "json"`).
|
|
306
|
+
Contributions welcome! This is a young project — open an issue or PR on
|
|
307
|
+
[GitHub](https://github.com/heggria/pi-taskflow). Tests live in `test/`, the
|
|
308
|
+
runtime in `extensions/`.
|
|
125
309
|
|
|
126
310
|
## License
|
|
127
311
|
|
package/extensions/runtime.ts
CHANGED
|
@@ -366,16 +366,19 @@ export async function executeTaskflow(state: RunState, deps: RuntimeDeps): Promi
|
|
|
366
366
|
return;
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
+
const startedAt = Date.now();
|
|
369
370
|
state.phases[phase.id] = {
|
|
370
371
|
...(state.phases[phase.id] ?? { id: phase.id }),
|
|
371
372
|
id: phase.id,
|
|
372
373
|
status: "running",
|
|
373
|
-
startedAt
|
|
374
|
+
startedAt,
|
|
374
375
|
};
|
|
375
376
|
deps.onProgress?.(state);
|
|
376
377
|
|
|
377
378
|
const ps = await executePhase(phase, state, deps, prior, () => deps.onProgress?.(state));
|
|
378
|
-
|
|
379
|
+
// Preserve the phase start time: executePhase returns a fresh PhaseState
|
|
380
|
+
// that omits startedAt (cached/resumed results carry their own).
|
|
381
|
+
state.phases[phase.id] = ps.startedAt ? ps : { ...ps, startedAt };
|
|
379
382
|
if ((phase.type ?? "agent") === "gate" && ps.gate?.verdict === "block") {
|
|
380
383
|
gateBlocked = true;
|
|
381
384
|
gateReason = ps.gate.reason ?? "";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-taskflow",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "Lightweight workflow orchestration for the Pi coding agent — declarative multi-phase taskflows with dynamic fan-out, isolated subagent context, resumable runs, and saveable commands.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
package/skills/taskflow/SKILL.md
CHANGED
|
@@ -123,6 +123,20 @@ Review the audit results below. If any endpoint is missing auth, end with
|
|
|
123
123
|
3. Reference upstream results explicitly with `{steps.ID...}` and set `dependsOn`.
|
|
124
124
|
4. Mark the result-bearing phase with `"final": true` (else the last phase wins).
|
|
125
125
|
|
|
126
|
+
## Configuration
|
|
127
|
+
|
|
128
|
+
For the full set of knobs — per-phase `model`/`thinking`/`tools`/`cwd`, the
|
|
129
|
+
two-level concurrency model, model/thinking/tools resolution precedence,
|
|
130
|
+
`agentScope` & agent discovery, `settings.json` overrides, environment
|
|
131
|
+
variables, and storage paths — read `configuration.md` (next to this file).
|
|
132
|
+
|
|
133
|
+
Quick reference:
|
|
134
|
+
|
|
135
|
+
- **Flow:** `name`, `description`, `concurrency` (default 8), `agentScope` (user|project|both), `args`.
|
|
136
|
+
- **Phase:** `model`, `thinking`, `tools` (whitelist), `cwd`, `output:"json"`, `concurrency` (map/parallel fan-out), `final`.
|
|
137
|
+
- **Precedence (model/thinking/tools):** phase value → `settings.subagents.agentOverrides[agent]` → agent frontmatter → global/default.
|
|
138
|
+
- **Concurrency:** same-layer phases use `flow.concurrency`; a `map`/`parallel` phase uses `phase.concurrency ?? flow.concurrency ?? 8`.
|
|
139
|
+
|
|
126
140
|
## Actions
|
|
127
141
|
|
|
128
142
|
- `action: "run"` — run inline `define` or a saved `name` (with optional `args`).
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# Taskflow Configuration Reference
|
|
2
|
+
|
|
3
|
+
Every knob you can set on a taskflow, where it lives, and how the values are
|
|
4
|
+
resolved. Read this when you need fine control over models, concurrency, agent
|
|
5
|
+
discovery, working directories, tool restrictions, or storage.
|
|
6
|
+
|
|
7
|
+
Configuration lives in **five layers**, from most local to most global:
|
|
8
|
+
|
|
9
|
+
| Layer | Where | Sets |
|
|
10
|
+
|-------|-------|------|
|
|
11
|
+
| Phase | a phase object in the DSL | per-step model/thinking/tools/cwd/output/concurrency |
|
|
12
|
+
| Flow | the top-level DSL object | name, args, default concurrency, agent scope |
|
|
13
|
+
| Agent | `~/.pi/agent/agents/*.md`, `.pi/agents/*.md` frontmatter | per-agent default model/thinking/tools + system prompt |
|
|
14
|
+
| Settings | `~/.pi/agent/settings.json` | `subagents.agentOverrides`, global thinking |
|
|
15
|
+
| Environment | shell env | `PI_TASKFLOW_PI_BIN` |
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 1. Flow-level options
|
|
20
|
+
|
|
21
|
+
Top-level keys of the taskflow definition object.
|
|
22
|
+
|
|
23
|
+
```jsonc
|
|
24
|
+
{
|
|
25
|
+
"name": "audit-endpoints", // required — also becomes /tf:<name> when saved
|
|
26
|
+
"description": "Audit API auth", // shown in /tf list and the command palette
|
|
27
|
+
"concurrency": 8, // default max concurrent subagents (default: 8)
|
|
28
|
+
"agentScope": "user", // user | project | both (default: user)
|
|
29
|
+
"args": { /* see §3 */ },
|
|
30
|
+
"phases": [ /* see §2 */ ] // required, at least one phase
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
| Key | Type | Default | Notes |
|
|
35
|
+
|-----|------|---------|-------|
|
|
36
|
+
| `name` | string | — | **Required.** Saved as `/tf:<name>`. |
|
|
37
|
+
| `description` | string | — | Surfaced in `/tf list` and the slash-command. |
|
|
38
|
+
| `concurrency` | number | `8` | Default fan-out / same-layer parallelism cap. See §4. |
|
|
39
|
+
| `agentScope` | `user`\|`project`\|`both` | `user` | Which agent dirs to load. See §6. |
|
|
40
|
+
| `args` | record | `{}` | Declared invocation arguments. See §3. |
|
|
41
|
+
| `phases` | array | — | **Required.** The phase DAG. See §2. |
|
|
42
|
+
| `version` | number | `1` | ⚠️ Declared in schema but **not yet used** by the runtime. |
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 2. Phase-level options
|
|
47
|
+
|
|
48
|
+
Keys of each object in `phases[]`. Some only apply to specific `type`s.
|
|
49
|
+
|
|
50
|
+
```jsonc
|
|
51
|
+
{
|
|
52
|
+
"id": "audit", // required, unique — referenced via {steps.audit.output}
|
|
53
|
+
"type": "map", // agent | parallel | map | gate | reduce (default: agent)
|
|
54
|
+
"agent": "analyst", // agent name to run this phase
|
|
55
|
+
"task": "Audit {item.route}…",
|
|
56
|
+
"dependsOn": ["discover"],// DAG edges
|
|
57
|
+
"over": "{steps.discover.json}", // [map] array to fan out over
|
|
58
|
+
"as": "item", // [map] loop var name (default: item)
|
|
59
|
+
"branches": [ /* … */ ], // [parallel] static task list
|
|
60
|
+
"from": ["audit"], // [reduce] phase ids to aggregate
|
|
61
|
+
"output": "json", // text | json (default: text)
|
|
62
|
+
"model": "claude-sonnet-4-5", // per-phase model override
|
|
63
|
+
"thinking": "high", // per-phase thinking override
|
|
64
|
+
"tools": ["read","bash"], // restrict tools for this phase's subagent
|
|
65
|
+
"cwd": "packages/api", // working directory for this phase's subagent
|
|
66
|
+
"concurrency": 4, // [map/parallel] fan-out cap for THIS phase
|
|
67
|
+
"final": true // mark this phase's output as the workflow result
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
| Key | Applies to | Default | Notes |
|
|
72
|
+
|-----|-----------|---------|-------|
|
|
73
|
+
| `id` | all | — | **Required, unique.** Used in `{steps.<id>…}`. |
|
|
74
|
+
| `type` | all | `agent` | One of the 5 phase types. |
|
|
75
|
+
| `agent` | all | first available | Agent name; resolved from the scoped pool. |
|
|
76
|
+
| `task` | agent, gate, map, reduce | — | Prompt; supports interpolation. Required for these types. |
|
|
77
|
+
| `over` | map | — | **Required for map.** Must resolve to an array. |
|
|
78
|
+
| `as` | map | `item` | Loop variable bound per item. |
|
|
79
|
+
| `branches` | parallel | — | **Required for parallel.** `[{task, agent?}]`. |
|
|
80
|
+
| `from` | reduce | — | **Required for reduce.** Phase ids whose outputs are aggregated. |
|
|
81
|
+
| `dependsOn` | all | `[]` | DAG edges. `from` also implies a dependency. |
|
|
82
|
+
| `output` | all | `text` | `json` parses output so `{steps.id.json}` / map `over` work. |
|
|
83
|
+
| `model` | all | agent/global | Per-phase model override. See §5. |
|
|
84
|
+
| `thinking` | all | agent/global | Per-phase thinking level. See §5. |
|
|
85
|
+
| `tools` | all | agent default | Whitelist of tools for the subagent. See §5. |
|
|
86
|
+
| `cwd` | all | flow cwd | Run this phase's subagent in a different directory. |
|
|
87
|
+
| `concurrency` | map, parallel | flow concurrency | Fan-out cap for this phase only. See §4. |
|
|
88
|
+
| `final` | all | last phase | Exactly one phase may be `final`; its output is returned. |
|
|
89
|
+
| `optional` | all | `false` | ⚠️ Declared in schema but **not yet enforced** — a failed phase still skips downstream. |
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## 3. Declaring & passing arguments
|
|
94
|
+
|
|
95
|
+
Declare arguments on the flow, then reference them with `{args.X}`.
|
|
96
|
+
|
|
97
|
+
```jsonc
|
|
98
|
+
"args": {
|
|
99
|
+
"dir": { "default": "src", "description": "Directory to scan" },
|
|
100
|
+
"depth": { "default": 2 },
|
|
101
|
+
"token": { "required": true, "description": "API token" }
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
| Field | Notes |
|
|
106
|
+
|-------|-------|
|
|
107
|
+
| `default` | Used when the caller omits the arg. |
|
|
108
|
+
| `description` | Documentation only. |
|
|
109
|
+
| `required` | ⚠️ Declared but **not enforced** at runtime — treat as documentation for now. |
|
|
110
|
+
|
|
111
|
+
**Resolution:** for each declared arg, the provided value wins, else its
|
|
112
|
+
`default`. Any extra provided keys are also passed through (so undeclared args
|
|
113
|
+
still reach `{args.X}`).
|
|
114
|
+
|
|
115
|
+
**Passing args:**
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
/tf run audit-endpoints {"dir":"packages/api"} # JSON
|
|
119
|
+
/tf run audit-endpoints dir=packages/api depth=3 # key=value pairs
|
|
120
|
+
/tf run audit-endpoints packages/api # single positional → first declared arg
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Via the tool: `{ "action": "run", "name": "audit-endpoints", "args": { "dir": "packages/api" } }`.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 4. Concurrency model
|
|
128
|
+
|
|
129
|
+
There are **two independent concurrency limits**:
|
|
130
|
+
|
|
131
|
+
1. **Same-layer parallelism** — phases with no dependency between them sit in the
|
|
132
|
+
same topological layer and run concurrently, bounded by **`flow.concurrency`**
|
|
133
|
+
(default `8`).
|
|
134
|
+
2. **Fan-out within a `map`/`parallel` phase** — bounded by
|
|
135
|
+
**`phase.concurrency ?? flow.concurrency ?? 8`**.
|
|
136
|
+
|
|
137
|
+
```jsonc
|
|
138
|
+
{
|
|
139
|
+
"concurrency": 6, // ≤6 sibling phases run at once
|
|
140
|
+
"phases": [
|
|
141
|
+
{ "id": "scan", "type": "map", "over": "{steps.list.json}",
|
|
142
|
+
"concurrency": 3, // …but this map only fans out 3 at a time
|
|
143
|
+
"task": "…", "dependsOn": ["list"] }
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Set a low `phase.concurrency` to protect rate-limited models or heavy bash work;
|
|
149
|
+
keep `flow.concurrency` higher to let independent phases overlap.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## 5. Model, thinking & tools resolution
|
|
154
|
+
|
|
155
|
+
For any phase, the effective value is resolved in this **precedence order**
|
|
156
|
+
(first defined wins):
|
|
157
|
+
|
|
158
|
+
| Setting | Precedence (high → low) |
|
|
159
|
+
|---------|-------------------------|
|
|
160
|
+
| **model** | `phase.model` → `settings.agentOverrides[agent].model` → agent frontmatter `model` → pi default |
|
|
161
|
+
| **thinking** | `phase.thinking` → `settings.agentOverrides[agent].thinking` → agent frontmatter `thinking` → `settings` global thinking → pi default |
|
|
162
|
+
| **tools** | `phase.tools` → `settings.agentOverrides[agent].tools` → agent frontmatter `tools` → all tools |
|
|
163
|
+
|
|
164
|
+
Notes:
|
|
165
|
+
- `tools` is a **whitelist** passed as `--tools a,b,c`. Omit it to allow all.
|
|
166
|
+
- Each phase runs as an isolated process:
|
|
167
|
+
`pi --mode json -p --no-session [--model …] [--thinking …] [--tools …] [--append-system-prompt <agent>] "Task: …"`.
|
|
168
|
+
- The agent's markdown body becomes the subagent's appended system prompt.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## 6. Agent discovery & scope
|
|
173
|
+
|
|
174
|
+
`flow.agentScope` controls which agent directories are loaded:
|
|
175
|
+
|
|
176
|
+
| Scope | Loads from |
|
|
177
|
+
|-------|-----------|
|
|
178
|
+
| `user` (default) | `~/.pi/agent/agents/*.md` |
|
|
179
|
+
| `project` | nearest `.pi/agents/*.md` found walking up from cwd |
|
|
180
|
+
| `both` | user **then** project (project overrides on name collision) |
|
|
181
|
+
|
|
182
|
+
- Agents are `.md` files with frontmatter `name` + `description` (required), plus
|
|
183
|
+
optional `model`, `thinking`, `tools`. The body is the system prompt.
|
|
184
|
+
- Reference agents in phases by their `name`. An unknown name fails that phase
|
|
185
|
+
with the list of available agents.
|
|
186
|
+
- If a phase omits `agent`, the **first discovered agent** is used.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## 7. settings.json
|
|
191
|
+
|
|
192
|
+
Taskflow shares the subagent settings file at `~/.pi/agent/settings.json`:
|
|
193
|
+
|
|
194
|
+
```jsonc
|
|
195
|
+
{
|
|
196
|
+
"subagents": {
|
|
197
|
+
"globalThinking": "medium", // fallback thinking for all subagents
|
|
198
|
+
"agentOverrides": {
|
|
199
|
+
"analyst": { "model": "claude-sonnet-4-5", "thinking": "high" },
|
|
200
|
+
"scout": { "tools": ["read", "bash", "grep"] }
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
"defaultThinkingLevel": "low" // used if subagents.globalThinking is absent
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
- `subagents.agentOverrides` — per-agent overrides applied at discovery; they beat
|
|
208
|
+
agent frontmatter but lose to a phase-level value (see §5).
|
|
209
|
+
- `subagents.globalThinking` (or top-level `defaultThinkingLevel`) — global
|
|
210
|
+
thinking fallback.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 8. Environment variables
|
|
215
|
+
|
|
216
|
+
| Variable | Effect |
|
|
217
|
+
|----------|--------|
|
|
218
|
+
| `PI_TASKFLOW_PI_BIN` | Override the `pi` binary used to spawn subagents. Used by tests and unusual launch setups (e.g. `PI_TASKFLOW_PI_BIN=pi`). Normally auto-detected. |
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## 9. Storage & file locations
|
|
223
|
+
|
|
224
|
+
| What | Path | Commit? |
|
|
225
|
+
|------|------|---------|
|
|
226
|
+
| User-scoped flow | `~/.pi/agent/taskflows/<name>.json` | personal |
|
|
227
|
+
| Project-scoped flow | `<nearest .pi>/taskflows/<name>.json` | ✅ commit to share |
|
|
228
|
+
| Run state (resume) | `<project .pi>/taskflows/runs/<runId>.json` | ❌ gitignore |
|
|
229
|
+
|
|
230
|
+
- `action: "save"` takes `scope: "project"` (default) or `"user"`.
|
|
231
|
+
- Saved flows auto-register as `/tf:<name>` (immediately for the current session,
|
|
232
|
+
and on future `session_start`).
|
|
233
|
+
- Project flows override user flows on a name collision.
|
|
234
|
+
- Add `.pi/taskflows/runs/` to `.gitignore`.
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## 10. Quick recipes
|
|
239
|
+
|
|
240
|
+
**Pin a strong model only for the review gate:**
|
|
241
|
+
```jsonc
|
|
242
|
+
{ "id": "review", "type": "gate", "agent": "reviewer",
|
|
243
|
+
"model": "claude-opus-4", "thinking": "high",
|
|
244
|
+
"task": "…\nVERDICT:", "dependsOn": ["audit"] }
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Sandbox a phase to read-only in a subdirectory:**
|
|
248
|
+
```jsonc
|
|
249
|
+
{ "id": "scan", "type": "agent", "agent": "scout",
|
|
250
|
+
"cwd": "packages/api", "tools": ["read", "grep", "ls"],
|
|
251
|
+
"task": "List route files. Output ONLY a JSON array.", "output": "json" }
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**Throttle a rate-limited fan-out:**
|
|
255
|
+
```jsonc
|
|
256
|
+
{ "id": "summarize", "type": "map", "over": "{steps.scan.json}",
|
|
257
|
+
"concurrency": 2, "agent": "writer",
|
|
258
|
+
"task": "Summarize {item.file}.", "dependsOn": ["scan"] }
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Project-only agents:**
|
|
262
|
+
```jsonc
|
|
263
|
+
{ "name": "ci-audit", "agentScope": "project", "phases": [ /* … */ ] }
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Caveats (declared but not yet enforced)
|
|
269
|
+
|
|
270
|
+
These keys validate but the runtime does **not** act on them yet — don't rely on
|
|
271
|
+
them for behavior:
|
|
272
|
+
|
|
273
|
+
- `phase.optional` — a failed phase still marks downstream phases as skipped.
|
|
274
|
+
- `arg.required` — missing required args are not rejected.
|
|
275
|
+
- `flow.version` — informational only.
|