pi-agent-flow 0.1.0-alpha.7 → 0.1.0-alpha.8

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 CHANGED
@@ -1,28 +1,27 @@
1
- # pi-agent-flow
1
+ <p align="center"><code>pi install /path/to/pi-agent-flow</code></p>
2
+ <p align="center"><strong>Pi Agent Flow</strong> is a flow-state delegation extension for the <a href="https://pi.dev">Pi coding agent</a> that runs locally in your terminal.</p>
3
+ <p align="center">
4
+ <img src="https://github.com/user-attachments/assets/pi-agent-flow-demo.png" alt="Pi Agent Flow demo" width="80%" />
5
+ </p>
6
+ <br/>
2
7
 
3
- Flow-state delegation extension for [Pi coding agent](https://pi.dev).
4
-
5
- Delegates tasks to specialized flow states running as isolated pi processes. Each flow receives a snapshot of the current session context and returns a structured report.
8
+ ---
6
9
 
7
- ## Features
10
+ ## Quickstart
8
11
 
9
- - Isolated context flows always receive your current session snapshot
10
- - Parallel execution — batch independent flows into one call
11
- - Structured reports — every flow returns [Summary], [Done], [Not Done], [Next Steps]
12
- - Depth guards — configurable max delegation depth (default: 3)
13
- - Cycle prevention — blocks recursive delegation chains
14
- - Flow discovery — reads definitions from ~/.pi/agent/agents/ and .pi/agents/
15
- - TUI rendering — rich collapsed/expanded display in interactive mode
12
+ ### Installing Pi Agent Flow
16
13
 
17
- ## Installation
14
+ Install via the Pi CLI with your local path:
18
15
 
19
- ```bash
16
+ ```shell
17
+ # Install from a local path
20
18
  pi install /path/to/pi-agent-flow
21
19
  ```
22
20
 
23
- Or add to ~/.pi/agent/settings.json:
21
+ Or add it to your Pi settings:
24
22
 
25
- ```json
23
+ ```shell
24
+ # ~/.pi/agent/settings.json
26
25
  {
27
26
  "packages": [
28
27
  "./path/to/pi-agent-flow"
@@ -30,9 +29,36 @@ Or add to ~/.pi/agent/settings.json:
30
29
  }
31
30
  ```
32
31
 
32
+ Then start Pi and delegate tasks using flow states.
33
+
34
+ <details>
35
+ <summary>You can also clone this repository and use it directly.</summary>
36
+
37
+ ```shell
38
+ git clone https://github.com/your-org/pi-agent-flow.git
39
+ cd pi-agent-flow
40
+ pi install .
41
+ ```
42
+
43
+ </details>
44
+
45
+ ---
46
+
47
+ ## Features
48
+
49
+ - **Isolated context** — flows always receive your current session snapshot (or start clean when configured)
50
+ - **Parallel execution** — batch independent flows into one call
51
+ - **Structured reports** — every flow returns `[Summary]`, `[Done]`, `[Not Done]`, `[Next Steps]`
52
+ - **Depth guards** — configurable max delegation depth (default: `3`)
53
+ - **Cycle prevention** — blocks recursive delegation chains
54
+ - **Flow discovery** — reads definitions from `~/.pi/agent/agents/` and `.pi/agents/`
55
+ - **TUI rendering** — rich collapsed/expanded display in interactive mode
56
+
57
+ ---
58
+
33
59
  ## Flow Definitions
34
60
 
35
- Create .md files in ~/.pi/agent/agents/ or .pi/agents/:
61
+ Create `.md` files in `~/.pi/agent/agents/` or `.pi/agents/`:
36
62
 
37
63
  ```markdown
38
64
  ---
@@ -59,21 +85,32 @@ flow [explore] accomplished
59
85
  - recommended follow-up
60
86
  ```
61
87
 
88
+ ---
89
+
62
90
  ## Bundled Flows
63
91
 
64
- - [explore] discover files, trace code paths, map architecture
65
- - [debug] — investigate logs, errors, stack traces, root causes
66
- - [code] implement features, fix bugs, write tests
67
- - [architect] plan structure, break down requirements, design solutions
68
- - [review] audit security, quality, correctness
92
+ | Flow | Purpose |
93
+ |------|---------|
94
+ | `[explore]` | Discover files, trace code paths, map architecture |
95
+ | `[debug]` | Investigate logs, errors, stack traces, root causes |
96
+ | `[code]` | Implement features, fix bugs, write tests |
97
+ | `[architect]` | Plan structure, break down requirements, design solutions |
98
+ | `[review]` | Audit security, quality, correctness |
99
+ | `[brainstorm]` | Generate ideas and explore possibilities with a clean slate |
100
+
101
+ > **Note:** Some flows — like `[brainstorm]` — start with a **clean slate** and do not inherit the current session context. They receive only the intent, making them ideal for unbiased, creative thinking.
102
+
103
+ ---
69
104
 
70
105
  ## Usage
71
106
 
107
+ ### Single flow
108
+
72
109
  ```json
73
110
  { "flow": [{ "type": "explore", "intent": "Find all authentication-related code" }] }
74
111
  ```
75
112
 
76
- Batch multiple flows:
113
+ ### Batch multiple flows
77
114
 
78
115
  ```json
79
116
  {
@@ -84,21 +121,33 @@ Batch multiple flows:
84
121
  }
85
122
  ```
86
123
 
124
+ ---
125
+
87
126
  ## Configuration
88
127
 
89
- Flags (passed to parent pi process):
128
+ ### Flags (passed to parent pi process)
90
129
 
91
- - --flow-max-depth [n] Maximum delegation depth (default: 3)
92
- - --flow-prevent-cycles — Block cyclic delegation (default: true)
93
- - --no-flow-prevent-cycles Disable cycle prevention
130
+ | Flag | Description | Default |
131
+ |------|-------------|---------|
132
+ | `--flow-max-depth [n]` | Maximum delegation depth | `3` |
133
+ | `--flow-prevent-cycles` | Block cyclic delegation | `true` |
134
+ | `--no-flow-prevent-cycles` | Disable cycle prevention | — |
94
135
 
95
- Environment variables (propagated to child processes):
136
+ ### Environment variables (propagated to child processes)
137
+
138
+ | Variable | Description |
139
+ |----------|-------------|
140
+ | `PI_FLOW_DEPTH` | Current depth |
141
+ | `PI_FLOW_MAX_DEPTH` | Max allowed depth |
142
+ | `PI_FLOW_STACK` | JSON array of ancestor flow names |
143
+ | `PI_FLOW_PREVENT_CYCLES` | `"1"` or `"0"` |
144
+
145
+ ---
96
146
 
97
- - PI_FLOW_DEPTH — Current depth
98
- - PI_FLOW_MAX_DEPTH — Max allowed depth
99
- - PI_FLOW_STACK — JSON array of ancestor flow names
100
- - PI_FLOW_PREVENT_CYCLES — "1" or "0"
147
+ ## Docs
101
148
 
102
- ## License
149
+ - [**Pi Documentation**](https://pi.dev)
150
+ - [**Contributing**](./docs/contributing.md) *(if available)*
151
+ - [**License**](./LICENSE)
103
152
 
104
- MIT
153
+ This repository is licensed under the [MIT License](LICENSE).
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: brainstorm
3
+ description: Generate ideas, explore possibilities, and think creatively without context bias
4
+ tools: read, bash
5
+ inheritContext: false
6
+ maxDepth: 0
7
+ ---
8
+
9
+ You are the brainstorm flow — your mission is to generate ideas and think creatively. You start with a clean slate and only have the intent to guide you. Stay focused on your intent at all times.
10
+
11
+ When your mission is accomplished, end your response with:
12
+
13
+ flow [brainstorm] accomplished
14
+
15
+ [Summary] what was brainstormed
16
+
17
+ [Done]
18
+ - ideas generated
19
+
20
+ [Not Done]
21
+ - incomplete items and reasons
22
+
23
+ [Next Steps]
24
+ - recommended follow-up
package/agents.ts CHANGED
@@ -24,6 +24,7 @@ export interface FlowConfig {
24
24
  model?: string;
25
25
  thinking?: string;
26
26
  maxDepth?: number;
27
+ inheritContext?: boolean;
27
28
  systemPrompt: string;
28
29
  source: "user" | "project" | "bundled";
29
30
  filePath: string;
@@ -140,6 +141,26 @@ function parseFlowFile(filePath: string, source: "user" | "project" | "bundled")
140
141
  if (Number.isFinite(parsed) && parsed >= 0) maxDepth = parsed;
141
142
  }
142
143
 
144
+ let inheritContext: boolean | undefined;
145
+ if (typeof frontmatter.inheritContext === "boolean") {
146
+ inheritContext = frontmatter.inheritContext;
147
+ } else if (typeof frontmatter.inheritContext === "string") {
148
+ const normalized = frontmatter.inheritContext.trim().toLowerCase();
149
+ if (normalized === "true" || normalized === "yes" || normalized === "1") {
150
+ inheritContext = true;
151
+ } else if (normalized === "false" || normalized === "no" || normalized === "0") {
152
+ inheritContext = false;
153
+ } else {
154
+ console.warn(
155
+ `[pi-agent-flow] Ignoring invalid inheritContext value "${frontmatter.inheritContext}" in "${filePath}". Expected true/false.`,
156
+ );
157
+ }
158
+ } else if (frontmatter.inheritContext !== undefined) {
159
+ console.warn(
160
+ `[pi-agent-flow] Ignoring invalid inheritContext field in "${filePath}". Expected boolean or string.`,
161
+ );
162
+ }
163
+
143
164
  return {
144
165
  name,
145
166
  description,
@@ -147,6 +168,7 @@ function parseFlowFile(filePath: string, source: "user" | "project" | "bundled")
147
168
  model: typeof frontmatter.model === "string" ? frontmatter.model : undefined,
148
169
  thinking: typeof frontmatter.thinking === "string" ? frontmatter.thinking : undefined,
149
170
  maxDepth,
171
+ inheritContext,
150
172
  systemPrompt: body,
151
173
  source,
152
174
  filePath,
package/flow.ts CHANGED
@@ -143,8 +143,8 @@ export interface RunFlowOptions {
143
143
  intent: string;
144
144
  /** Optional override working directory. */
145
145
  taskCwd?: string;
146
- /** Serialized parent session snapshot for fork mode. */
147
- forkSessionSnapshotJsonl: string;
146
+ /** Serialized parent session snapshot for fork mode. Null when the flow starts with a clean slate. */
147
+ forkSessionSnapshotJsonl: string | null;
148
148
  /** Current delegation depth of the caller process. */
149
149
  parentDepth: number;
150
150
  /** Delegation stack from the caller process (ancestor flow names). */
@@ -197,21 +197,6 @@ export async function runFlow(opts: RunFlowOptions): Promise<SingleResult> {
197
197
  };
198
198
  }
199
199
 
200
- if (!forkSessionSnapshotJsonl || !forkSessionSnapshotJsonl.trim()) {
201
- return {
202
- type: flowName,
203
- agentSource: flow.source,
204
- intent,
205
- exitCode: 1,
206
- messages: [],
207
- stderr: "Cannot run in fork mode: missing parent session snapshot context.",
208
- usage: emptyFlowUsage(),
209
- model: flow.model,
210
- stopReason: "error",
211
- errorMessage: "Cannot run in fork mode: missing parent session snapshot context.",
212
- };
213
- }
214
-
215
200
  const result: SingleResult = {
216
201
  type: flowName,
217
202
  agentSource: flow.source,
@@ -236,10 +221,14 @@ export async function runFlow(opts: RunFlowOptions): Promise<SingleResult> {
236
221
  });
237
222
  };
238
223
 
239
- // Write forked session snapshot to temp file
240
- const forkTmp = writeFlowSessionToTempFile(flow.name, forkSessionSnapshotJsonl);
241
- const forkSessionTmpDir = forkTmp.dir;
242
- const forkSessionTmpPath = forkTmp.filePath;
224
+ // Write forked session snapshot to temp file only when provided
225
+ let forkSessionTmpDir: string | null = null;
226
+ let forkSessionTmpPath: string | null = null;
227
+ if (forkSessionSnapshotJsonl) {
228
+ const forkTmp = writeFlowSessionToTempFile(flow.name, forkSessionSnapshotJsonl);
229
+ forkSessionTmpDir = forkTmp.dir;
230
+ forkSessionTmpPath = forkTmp.filePath;
231
+ }
243
232
 
244
233
  try {
245
234
  const piArgs = buildFlowArgs(
package/index.ts CHANGED
@@ -36,7 +36,7 @@ const FLOW_PREVENT_CYCLES_ENV = "PI_FLOW_PREVENT_CYCLES";
36
36
 
37
37
  const FlowItem = Type.Object({
38
38
  type: Type.String({
39
- description: "Flow type. Must match an available flow name exactly: explore, debug, code, architect, review.",
39
+ description: "Flow type. Must match an available flow name exactly: explore, debug, code, architect, review, brainstorm.",
40
40
  }),
41
41
  intent: Type.String({
42
42
  description: "Clear, specific mission for this flow.",
@@ -371,6 +371,7 @@ Before acting, reason about whether to dive into a flow:
371
371
  - [code] — when you are ready to build. Implement features, fix bugs, write tests.
372
372
  - [architect] — when you need a plan. Design structure, break down requirements before building.
373
373
  - [review] — when you need to verify. Audit security, quality, correctness.
374
+ - [brainstorm] — when you need fresh ideas. Start from a clean slate with only the intent.
374
375
 
375
376
  Multiple independent flows? Batch them into one call:
376
377
 
@@ -416,17 +417,10 @@ flow [type] accomplished
416
417
  const { flows } = discovery;
417
418
  const makeDetails = makeFlowDetailsFactory(discovery.projectFlowsDir);
418
419
 
419
- // Build fork session snapshot (shared across all flows)
420
+ // Build fork session snapshot (shared across all flows that inherit context)
420
421
  const forkSessionSnapshotJsonl = buildForkSessionSnapshotJsonl(
421
422
  ctx.sessionManager,
422
423
  );
423
- if (!forkSessionSnapshotJsonl) {
424
- return {
425
- content: [{ type: "text", text: "Cannot use fork mode: failed to snapshot current session context." }],
426
- details: makeDetails([]),
427
- isError: true,
428
- };
429
- }
430
424
 
431
425
  // Collect all requested flow names
432
426
  const requested = new Set(params.flow.map((f) => f.type));
@@ -513,13 +507,14 @@ flow [type] accomplished
513
507
  const effectiveMaxDepth =
514
508
  targetFlow?.maxDepth !== undefined ? targetFlow.maxDepth : maxDepth;
515
509
 
510
+ const shouldInheritContext = targetFlow?.inheritContext !== false;
516
511
  const result = await runFlow({
517
512
  cwd: ctx.cwd,
518
513
  flows,
519
514
  flowName: item.type,
520
515
  intent: item.intent,
521
516
  taskCwd: item.cwd,
522
- forkSessionSnapshotJsonl,
517
+ forkSessionSnapshotJsonl: shouldInheritContext ? forkSessionSnapshotJsonl : null,
523
518
  parentDepth: currentDepth,
524
519
  parentFlowStack: ancestorFlowStack,
525
520
  maxDepth: effectiveMaxDepth,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-agent-flow",
3
- "version": "0.1.0-alpha.7",
3
+ "version": "0.1.0-alpha.8",
4
4
  "description": "Flow-state delegation extension for Pi coding agent.",
5
5
  "type": "module",
6
6
  "main": "index.ts",
package/render-utils.ts CHANGED
@@ -21,7 +21,7 @@ export function formatFlowUsage(usage: Partial<UsageStats>, model?: string): str
21
21
  if (usage.cacheWrite) parts.push(`CW:${formatTokens(usage.cacheWrite)}`);
22
22
  if (usage.cost) parts.push(`$${usage.cost.toFixed(4)}`);
23
23
  if (usage.contextTokens && usage.contextTokens > 0) parts.push(`ctx:${formatTokens(usage.contextTokens)}`);
24
- if (model) parts.push(model);
24
+ if (model) parts.push(`model:${model}`);
25
25
  return parts.join(" ");
26
26
  }
27
27
 
@@ -31,7 +31,7 @@ export function formatCompactStats(usage: Partial<UsageStats>, model?: string):
31
31
  parts.push(`↓${formatTokens(usage.output || 0)}`);
32
32
  if (usage.cacheRead) parts.push(`cr:${formatTokens(usage.cacheRead)}`);
33
33
  if (usage.contextTokens && usage.contextTokens > 0) parts.push(`ctx:${formatTokens(usage.contextTokens)}`);
34
- if (model) parts.push(model);
34
+ if (model) parts.push(`model:${model}`);
35
35
  return parts.join(" ");
36
36
  }
37
37
 
package/render.ts CHANGED
@@ -109,19 +109,11 @@ function flowStatusIcon(r: SingleResult, theme: { fg: ThemeFg }): string {
109
109
  // renderFlowCall — shown while the flow is being invoked
110
110
  // ---------------------------------------------------------------------------
111
111
 
112
- function truncateText(text: string): string {
113
- const words = text.split(/\s+/);
114
- if (words.length <= 12) return text;
115
- return `${words.slice(0, 3).join(" ")} ... ${words.slice(-8).join(" ")}`;
116
- }
117
-
118
112
  export function renderFlowCall(args: Record<string, any>, theme: FlowTheme): Text {
119
113
  const flows = args.flow as Array<{ type: string; intent: string }> | undefined;
120
114
 
121
115
  // Minimal — renderFlowResult owns the full display
122
116
  return new Text("", 0, 0);
123
-
124
- return new Text(theme.fg("muted", "(empty flow call)"), 0, 0);
125
117
  }
126
118
 
127
119
  // ---------------------------------------------------------------------------
@@ -352,7 +344,7 @@ function renderMultiFlowCollapsed(
352
344
 
353
345
  // Intent line
354
346
  if (r.intent) {
355
- const intentPrefix = hasMsg ? "├─" : "└─";
347
+ const intentPrefix = "├─";
356
348
  text += `\n${theme.fg("dim", indent + intentPrefix + " int:")} ${theme.fg("dim", truncateChars(r.intent, 40))}`;
357
349
  }
358
350
 
package/types.ts CHANGED
@@ -140,7 +140,9 @@ export function getFlowDisplayItems(messages: Message[]): DisplayItem[] {
140
140
  if (part.type === "text") {
141
141
  items.push({ type: "text", text: part.text });
142
142
  } else if (part.type === "toolCall") {
143
- items.push({ type: "toolCall", name: part.name, args: part.arguments });
143
+ const name = part.name ?? part.toolName ?? "unknown";
144
+ const args = (part.arguments ?? part.input ?? {}) as Record<string, unknown>;
145
+ items.push({ type: "toolCall", name, args });
144
146
  }
145
147
  }
146
148
  }