pi-workflow-engine 0.10.2 → 0.11.0

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.
Files changed (55) hide show
  1. package/.pi/extensions/pi-workflow-engine/index.ts +199 -120
  2. package/.pi/extensions/pi-workflow-engine/src/advisory-schema.ts +11 -0
  3. package/.pi/extensions/pi-workflow-engine/src/agent-attempt.ts +145 -0
  4. package/.pi/extensions/pi-workflow-engine/src/agent-replay.ts +376 -0
  5. package/.pi/extensions/pi-workflow-engine/src/agent-runner-types.ts +87 -0
  6. package/.pi/extensions/pi-workflow-engine/src/agent-runner.ts +84 -529
  7. package/.pi/extensions/pi-workflow-engine/src/agent-session-identity.ts +256 -0
  8. package/.pi/extensions/pi-workflow-engine/src/agent-session.ts +413 -0
  9. package/.pi/extensions/pi-workflow-engine/src/agent-skills.ts +43 -1
  10. package/.pi/extensions/pi-workflow-engine/src/agent-workspace.ts +81 -0
  11. package/.pi/extensions/pi-workflow-engine/src/budget.ts +7 -0
  12. package/.pi/extensions/pi-workflow-engine/src/cancellation.ts +17 -3
  13. package/.pi/extensions/pi-workflow-engine/src/concurrency.ts +164 -61
  14. package/.pi/extensions/pi-workflow-engine/src/diff-capture.ts +117 -20
  15. package/.pi/extensions/pi-workflow-engine/src/discovery.ts +64 -15
  16. package/.pi/extensions/pi-workflow-engine/src/dynamax-shortcuts.ts +59 -13
  17. package/.pi/extensions/pi-workflow-engine/src/dynamax.ts +14 -3
  18. package/.pi/extensions/pi-workflow-engine/src/engine.ts +208 -74
  19. package/.pi/extensions/pi-workflow-engine/src/finalizers.ts +66 -0
  20. package/.pi/extensions/pi-workflow-engine/src/identity-canonicalization.ts +138 -0
  21. package/.pi/extensions/pi-workflow-engine/src/identity-fingerprint.ts +15 -0
  22. package/.pi/extensions/pi-workflow-engine/src/inline-workflow.ts +8 -3
  23. package/.pi/extensions/pi-workflow-engine/src/journal.ts +148 -64
  24. package/.pi/extensions/pi-workflow-engine/src/options.ts +9 -6
  25. package/.pi/extensions/pi-workflow-engine/src/process-runner.ts +203 -39
  26. package/.pi/extensions/pi-workflow-engine/src/progress.ts +2 -5
  27. package/.pi/extensions/pi-workflow-engine/src/replay-path-identity.ts +48 -0
  28. package/.pi/extensions/pi-workflow-engine/src/resume-context.ts +665 -0
  29. package/.pi/extensions/pi-workflow-engine/src/review/github-pr-comments.ts +75 -16
  30. package/.pi/extensions/pi-workflow-engine/src/review/review-actions.ts +83 -17
  31. package/.pi/extensions/pi-workflow-engine/src/review/review-budget.ts +45 -0
  32. package/.pi/extensions/pi-workflow-engine/src/review/review-fix-workflow.ts +117 -0
  33. package/.pi/extensions/pi-workflow-engine/src/review/review-handoff.ts +14 -66
  34. package/.pi/extensions/pi-workflow-engine/src/review/review-issues.ts +38 -12
  35. package/.pi/extensions/pi-workflow-engine/src/review/review-report.ts +42 -0
  36. package/.pi/extensions/pi-workflow-engine/src/review/review-results-flow.ts +17 -22
  37. package/.pi/extensions/pi-workflow-engine/src/review/review-results-viewer.ts +143 -52
  38. package/.pi/extensions/pi-workflow-engine/src/review/review-session-coordinator.ts +153 -0
  39. package/.pi/extensions/pi-workflow-engine/src/review/review-snapshot.ts +295 -0
  40. package/.pi/extensions/pi-workflow-engine/src/tool-source-identity.ts +153 -0
  41. package/.pi/extensions/pi-workflow-engine/src/tree-fingerprint.ts +404 -0
  42. package/.pi/extensions/pi-workflow-engine/src/types.ts +38 -10
  43. package/.pi/extensions/pi-workflow-engine/src/ui/workflow-result-renderer.ts +5 -69
  44. package/.pi/extensions/pi-workflow-engine/src/unknown-error.ts +8 -0
  45. package/.pi/extensions/pi-workflow-engine/src/workflow-execution.ts +121 -0
  46. package/.pi/extensions/pi-workflow-engine/src/workflow-module.ts +19 -1
  47. package/.pi/extensions/pi-workflow-engine/src/workflows.ts +28 -3
  48. package/.pi/extensions/pi-workflow-engine/src/worktree.ts +282 -41
  49. package/.pi/extensions/pi-workflow-engine/workflows/code-review.ts +48 -26
  50. package/.pi/extensions/pi-workflow-engine/workflows/diagnose.ts +8 -1
  51. package/.pi/extensions/pi-workflow-engine/workflows/perf-review.ts +8 -2
  52. package/.pi/extensions/pi-workflow-engine/workflows/refactor-scout.ts +8 -1
  53. package/README.md +170 -226
  54. package/USAGE.md +92 -12
  55. package/package.json +9 -9
@@ -18,8 +18,9 @@ import {
18
18
  DEFAULT_ADVISORY_TOOLS,
19
19
  } from "../src/workflow-advisory-utils.ts";
20
20
  import { compactResults } from "../src/concurrency.ts";
21
- import { captureDiff } from "../src/diff-capture.ts";
22
- import type { ReviewContext } from "../src/review/review-issues.ts";
21
+ import { formatReviewDiffTarget, parseAllowedDiffCommand } from "../src/diff-capture.ts";
22
+ import type { ReviewContext } from "../src/review/review-report.ts";
23
+ import { captureReviewMaterial, type ReviewMaterialCaptureResult } from "../src/review/review-snapshot.ts";
23
24
  import type { WorkflowApi, WorkflowMeta, WorkflowRunStats } from "../src/types.ts";
24
25
 
25
26
  export const meta: WorkflowMeta = {
@@ -95,7 +96,15 @@ export function inDiff(changed: Map<string, Set<number>>, file: string, line?: n
95
96
  return set.has(line) || set.has(line - 1) || set.has(line + 1);
96
97
  }
97
98
 
98
- export default async function run(api: WorkflowApi): Promise<unknown> {
99
+ export interface CodeReviewDependencies {
100
+ readonly captureReviewMaterial?: (
101
+ target: Parameters<typeof captureReviewMaterial>[0],
102
+ cwd: string,
103
+ signal?: AbortSignal,
104
+ ) => Promise<ReviewMaterialCaptureResult>;
105
+ }
106
+
107
+ export default async function run(api: WorkflowApi, dependencies: CodeReviewDependencies = {}): Promise<unknown> {
99
108
  const { agent, parallel, phase, log, progress, args, cwd, signal } = api;
100
109
  const target = args.trim();
101
110
  let fileCount = 0;
@@ -116,6 +125,7 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
116
125
  (target
117
126
  ? `Target / instructions (verbatim): "${target}". If it names a PR number, branch, ref range, or files, build the matching diff command (use 'gh pr diff <number>' for a PR). Otherwise use the default selection below.\n`
118
127
  : "No explicit target — select the diff to review using the default below.\n") +
128
+ "Canonical Git syntax: use `git diff -- <path> [<path>...]` for file paths; use one `A..B` or `A...B` range operand for two revisions. Never emit ambiguous two-operand forms such as `git diff A B`.\n" +
119
129
  "Default selection — run commands to decide, falling through until you get a NON-EMPTY diff:\n" +
120
130
  "1. Get the current branch: `git branch --show-current`.\n" +
121
131
  "2. Check for an OPEN GitHub PR for this branch: `gh pr list --head <branch> --state open --json number,title`. " +
@@ -136,45 +146,53 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
136
146
 
137
147
  fileCount = scope.files.length;
138
148
  progress({ type: "summary", key: "files", value: scope.files.join(", ") || "(none)" });
139
- progress({ type: "summary", key: "diffCommand", value: scope.diffCommand });
149
+ const diffTarget = parseAllowedDiffCommand(scope.diffCommand);
150
+ if ("error" in diffTarget) {
151
+ throw new Error(`Code-review target rejected: ${diffTarget.error}`);
152
+ }
153
+ const diffCommand = formatReviewDiffTarget(diffTarget);
154
+ progress({ type: "summary", key: "diffCommand", value: diffCommand });
140
155
  progress({ type: "counter", key: "files", label: "files", value: fileCount });
141
156
 
142
157
  if (scope.files.length === 0) {
143
158
  return { summary: "No changes found to review.", findings: [], nextSteps: ["Provide a PR, ref range, or changed files to review."], stats: makeStats(0, 0) };
144
159
  }
145
160
 
161
+ log(`${scope.files.length} changed files`);
162
+
163
+ // Capture the diff once, deterministically, so findings can be bounded to changed lines in code.
164
+ const reviewMaterial = await (dependencies.captureReviewMaterial ?? captureReviewMaterial)(diffTarget, cwd, signal);
165
+ if (!reviewMaterial.ok) {
166
+ throw new Error(`Code-review diff capture failed: ${reviewMaterial.error}`);
167
+ }
168
+ const diffText = reviewMaterial.diff;
169
+ const changed = changedLines(diffText);
170
+ progress({ type: "summary", key: "diffBytes", value: Buffer.byteLength(diffText) });
171
+ if (reviewMaterial.snapshot.status === "unavailable") {
172
+ log(`review snapshot unavailable (${reviewMaterial.snapshot.reason}) — patch previews will be unavailable`);
173
+ }
174
+
146
175
  const reviewContext: ReviewContext = {
147
176
  workflowName: "code-review",
148
177
  target,
149
- diffCommand: scope.diffCommand,
178
+ diffTarget,
150
179
  files: scope.files,
151
180
  summary: scope.summary,
181
+ ...(reviewMaterial.snapshot.status === "verified"
182
+ ? { snapshot: reviewMaterial.snapshot.identity }
183
+ : {}),
152
184
  };
153
185
 
154
- log(`${scope.files.length} changed files`);
155
-
156
- // Capture the diff once, deterministically, so findings can be bounded to changed lines in code.
157
- let changed: Map<string, Set<number>> | null = null;
158
- let diffText = "";
159
- const capturedDiff = await captureDiff(scope.diffCommand, { cwd, signal, timeoutMs: 30_000, maxBufferBytes: 16 << 20 });
160
- if (capturedDiff.ok) {
161
- diffText = capturedDiff.stdout;
162
- changed = changedLines(diffText);
163
- progress({ type: "summary", key: "diffBytes", value: capturedDiff.bytes });
164
- } else {
165
- log(`diff capture failed (${capturedDiff.error ?? "unknown error"}) — reviewing without the line gate`);
166
- }
167
-
168
186
  const diffBlock = diffText
169
187
  ? `\n## Diff (review is bounded to these changed lines)\n\`\`\`diff\n${
170
188
  diffText.length > DIFF_EMBED_CAP
171
- ? `${diffText.slice(0, DIFF_EMBED_CAP)}\n... (truncated — run \`${scope.diffCommand}\` for the full diff)`
189
+ ? `${diffText.slice(0, DIFF_EMBED_CAP)}\n... (truncated — run \`${diffCommand}\` for the full diff)`
172
190
  : diffText
173
191
  }\n\`\`\`\n`
174
192
  : "";
175
193
 
176
194
  const scopeBlock =
177
- `## Diff command\n${scope.diffCommand}\n\n## Changed files\n${scope.files.map((file) => `- ${file}`).join("\n")}\n\n` +
195
+ `## Diff command\n${diffCommand}\n\n## Changed files\n${scope.files.map((file) => `- ${file}`).join("\n")}\n\n` +
178
196
  `## Summary\n${scope.summary}\n\n## Conventions\n${scope.conventions ?? "(none noted)"}\n` +
179
197
  diffBlock +
180
198
  (target ? `\n## User instructions (verbatim)\n${target}\n` : "");
@@ -204,14 +222,11 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
204
222
  const raw = (found?.candidates ?? []).slice(0, PER_ANGLE);
205
223
  rawCandidateCount += raw.length;
206
224
  progress({ type: "counter_delta", key: "candidates", label: "candidates", delta: raw.length });
207
- const gate = changed;
208
- const bounded = gate ? raw.filter((candidate) => inDiff(gate, primaryLocation(candidate).file, primaryLocation(candidate).line)) : raw;
225
+ const bounded = raw.filter((candidate) => inDiff(changed, primaryLocation(candidate).file, primaryLocation(candidate).line));
209
226
  const dropped = raw.length - bounded.length;
210
227
  if (dropped > 0) {
211
228
  droppedCandidateCount += dropped;
212
229
  progress({ type: "counter_delta", key: "dropped", label: "dropped", delta: dropped });
213
- }
214
- if (gate && dropped > 0) {
215
230
  log(`find:${angle.label}: dropped ${dropped} out-of-diff candidate(s)`);
216
231
  }
217
232
  for (const candidate of bounded) {
@@ -295,7 +310,14 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
295
310
  "Merge findings with the same root cause, rank most-severe first (correctness bugs above cleanups), and produce the final advisory report. " +
296
311
  "Return summary, findings, and nextSteps. For each finding: category must be bug or cleanup; severity is impact level (low/medium/high), not category; " +
297
312
  "confidence must be high for CONFIRMED and medium for PLAUSIBLE; copy locations and evidence arrays from the verified finding; impact is the concrete failure or maintenance scenario; recommendation is an advisory fix direction, not an edit. Structured output only.",
298
- { phase: "Synthesize", label: "synthesize", thinkingLevel: "medium", schema: AdvisoryReportSchema },
313
+ {
314
+ phase: "Synthesize",
315
+ label: "synthesize",
316
+ tools: [],
317
+ thinkingLevel: "medium",
318
+ resume: "read-only",
319
+ schema: AdvisoryReportSchema,
320
+ },
299
321
  );
300
322
 
301
323
  if (!report) return { summary: "Synthesis produced no output.", findings: [], nextSteps: ["Re-run the workflow or inspect verifier evidence manually."], stats, reviewContext };
@@ -212,7 +212,14 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
212
212
  "Produce the shared advisory report shape. Only include confirmed or plausible root causes in findings. " +
213
213
  "Use categories such as root-cause, regression, configuration, dependency, or test-fixture. " +
214
214
  "Recommendation must be a validation/fix plan, not a patch. nextSteps must be the minimum commands or code inspections needed to confirm the top diagnosis. Structured output only.",
215
- { phase: "Synthesize", label: "synthesize", thinkingLevel: "medium", schema: AdvisoryReportSchema },
215
+ {
216
+ phase: "Synthesize",
217
+ label: "synthesize",
218
+ tools: [],
219
+ thinkingLevel: "medium",
220
+ resume: "read-only",
221
+ schema: AdvisoryReportSchema,
222
+ },
216
223
  );
217
224
 
218
225
  if (!report) return emptyReport("Synthesis produced no output.", ["Inspect verifier evidence manually or rerun diagnose with a narrower symptom."], stats);
@@ -168,7 +168,14 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
168
168
  "Produce the shared advisory report shape. Categories should be algorithmic, io, concurrency, startup, allocation, or measurement when applicable. " +
169
169
  "Severity is expected performance impact for the target workload. Prefer measurement recommendations before optimization recommendations when evidence is weak. " +
170
170
  "Recommendations must be safe advisory next actions, not patches. Include risky optimizations to avoid in recommendations or nextSteps when relevant. Structured output only.",
171
- { phase: "Synthesize", label: "synthesize", thinkingLevel: "medium", schema: AdvisoryReportSchema },
171
+ {
172
+ phase: "Synthesize",
173
+ label: "synthesize",
174
+ tools: [],
175
+ thinkingLevel: "medium",
176
+ resume: "read-only",
177
+ schema: AdvisoryReportSchema,
178
+ },
172
179
  );
173
180
 
174
181
  if (!report) return emptyReport("Synthesis produced no output.", ["Inspect verifier evidence manually or rerun perf-review with a narrower target."], stats);
@@ -189,4 +196,3 @@ function rank(finding: Verified): number {
189
196
  const measurementPenalty = finding.category === "measurement" ? 1 : 0;
190
197
  return verdictRank + measurementPenalty;
191
198
  }
192
-
@@ -162,7 +162,14 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
162
162
  "Return the shared advisory report shape. Categories should come from the verified candidates. " +
163
163
  "Severity is maintenance or future-correctness impact. Confidence is high for CONFIRMED, medium for PLAUSIBLE unless verifier confidence says otherwise. " +
164
164
  "Recommendations must be safe first refactor steps, not rewrites. Include concrete nextSteps for the host developer. Structured output only.",
165
- { phase: "Synthesize", label: "synthesize", thinkingLevel: "medium", schema: AdvisoryReportSchema },
165
+ {
166
+ phase: "Synthesize",
167
+ label: "synthesize",
168
+ tools: [],
169
+ thinkingLevel: "medium",
170
+ resume: "read-only",
171
+ schema: AdvisoryReportSchema,
172
+ },
166
173
  );
167
174
 
168
175
  if (!report) return emptyReport("Synthesis produced no output.", ["Inspect verifier evidence manually or rerun the workflow with a narrower target."], stats);
package/README.md CHANGED
@@ -1,259 +1,203 @@
1
- ![agents](assets/preview.png)
2
-
3
1
  # pi-workflow-engine
4
2
 
5
- Zero-dependency Dynamax workflows for [pi](https://pi.dev): opt into live, parallel subagents when one coding agent is not enough.
6
-
7
- Use `dynamax` when a task needs several independent passes: review from different angles, compare options, chase a bug through multiple suspects, or stress-test a plan. pi can spin up a temporary workflow, run focused subagents in parallel, show progress in the TUI, and return one synthesized result.
8
-
9
- The package ships no bundled runtime dependencies. It uses the pi SDK and pi's bundled core packages, so installing it does not pull in a second agent stack.
10
-
11
- The built-in workflows are advisory by default. They inspect, verify, and report; they do not edit files. Workflow authors can opt individual subagents into disposable git worktrees when they want reviewable patches instead of direct edits.
12
-
13
- ## Install
14
-
15
- ```bash
16
- pi install npm:pi-workflow-engine
17
- ```
3
+ [![CI](https://github.com/timbrinded/pi-workflow-engine/actions/workflows/ci.yml/badge.svg)](https://github.com/timbrinded/pi-workflow-engine/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/pi-workflow-engine)](https://www.npmjs.com/package/pi-workflow-engine)
5
+ [![MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
18
6
 
19
- Or install from GitHub:
7
+ **A multi-agent workflow engine for [pi](https://pi.dev).**
20
8
 
21
- ```bash
22
- pi install git:github.com/timbrinded/pi-workflow-engine
23
- ```
9
+ One coding agent is usually enough. When a task needs independent opinions,
10
+ competing hypotheses, or a verifier that did not write the first answer, this
11
+ extension lets pi fan the work out and bring the evidence back together.
24
12
 
25
- Restart pi, or run `/reload` in an open session, then confirm it loaded:
13
+ The process is simple: **scope → fan out → verify → synthesize**. You see the work
14
+ live in the TUI, concurrency stays bounded, and the final answer includes the
15
+ subagents' combined usage and estimated cost when the provider exposes enough
16
+ data to calculate them.
26
17
 
27
- ```bash
28
- pi list
29
- ```
18
+ ![A lead agent coordinating subagents](assets/preview.png)
30
19
 
31
- For project-local install:
20
+ ## Quick start
32
21
 
33
22
  ```bash
34
- pi install npm:pi-workflow-engine -l
35
- ```
36
-
37
- ## Use Dynamax
38
-
39
- Put the word `dynamax` in your prompt when you want pi to use a custom multi-agent workflow for the current task.
40
-
41
- ```text
42
- dynamax investigate why typecheck started failing after this branch
43
- ```
44
-
45
- What to expect:
46
-
47
- 1. pi decides whether a saved workflow fits or whether to write a one-off workflow.
48
- 2. The workflow runs focused subagents in parallel.
49
- 3. You can watch phases, agents, findings, and logs in the workflow inspector.
50
- 4. You get a summarized result with evidence, risks, next steps, and workflow-level token/cost totals gathered from subagent sessions.
51
- 5. You can then ask pi to make changes using that result.
52
-
53
- Workflow usage totals are reported on the workflow result itself. pi's built-in footer and `/session` stats may still show host-session usage only unless pi core adds a first-class extension usage API.
54
-
55
- Dynamax is one-shot by default. It applies to the next agent run and then clears. Keep it on for the session with:
56
-
57
- ```text
58
- /workflow:dynamax on
59
- /workflow:dynamax status
60
- /workflow:dynamax off
61
- ```
62
-
63
- The workflow inspector is available with:
64
-
65
- ```text
66
- /workflow:inspector
67
- ```
68
-
69
- In interactive mode, `/workflow` with no arguments also offers `Author temporary one-shot workflow...`. Choose it, type a brief, and pi will ask the host agent to write and run a temporary workflow.
70
-
71
- ## Example Dynamax Prompts
72
-
73
- Use Dynamax when you want breadth, independent judgment, or adversarial pressure before editing.
74
-
75
- ```text
76
- dynamax do an adversarial review of this branch. I want correctness bugs, hidden coupling, test gaps, and over-engineered parts called out separately.
77
- ```
78
-
79
- Expected result: several review angles run independently, likely findings are verified, duplicates are merged, and you get a ranked report with concrete evidence before deciding what to fix.
80
-
81
- ```text
82
- dynamax investigate this flaky test. Split the work between recent diffs, test setup, async timing, and external dependencies.
83
- ```
84
-
85
- Expected result: parallel hypotheses, evidence for or against each one, and the most likely root cause with a small validation step.
86
-
87
- ```text
88
- dynamax compare these two implementation approaches and argue both sides before recommending one.
89
- ```
90
-
91
- Expected result: separate agents inspect each approach, another pass looks for failure modes, and the final answer separates tradeoffs from the recommendation.
92
-
93
- ```text
94
- dynamax review this migration plan like a hostile reviewer. Focus on rollback, data loss, concurrency, and missing operational steps.
95
- ```
96
-
97
- Expected result: an adversarial checklist with concrete risks, what would break, and which items are blockers versus follow-ups.
98
-
99
- ```text
100
- dynamax find the simplest safe refactor path for this module. Avoid broad rewrites.
23
+ pi install npm:pi-workflow-engine
101
24
  ```
102
25
 
103
- Expected result: small refactor opportunities, why they are safe, and what not to touch.
26
+ Restart pi or run `/reload`, then add `dynamax` to a prompt:
104
27
 
105
28
  ```text
106
- dynamax inspect this performance issue. Separate measurement gaps from real bottleneck hypotheses.
29
+ dynamax investigate why typecheck started failing on this branch
107
30
  ```
108
31
 
109
- Expected result: a performance-focused report that distinguishes known evidence from guesses and suggests the next measurement command.
110
-
111
- ## Saved Workflows
32
+ `dynamax` is a one-shot opt-in. It lets the host agent choose a saved workflow
33
+ or author a temporary one for this task. Use `/workflow:dynamax on` when you
34
+ want that permission to remain active for the session.
112
35
 
113
- You can also run saved workflows directly:
36
+ Saved workflows are available directly:
114
37
 
115
38
  ```text
116
39
  /workflow code-review
117
- /workflow code-review HEAD~3
118
40
  /workflow refactor-scout src/
119
- /workflow diagnose "typecheck fails after the schema change"
41
+ /workflow diagnose "the schema migration broke typecheck"
120
42
  /workflow perf-review "workflow startup latency"
121
43
  ```
122
44
 
123
- Built-ins:
124
-
125
- - `code-review`: reviews a PR, branch diff, ref range, or target.
126
- - `refactor-scout`: finds small, safe refactor opportunities.
127
- - `diagnose`: investigates a bug, failing command, or regression.
128
- - `perf-review`: reviews a slow path or performance concern.
129
-
130
- Useful flags:
131
-
132
- ```text
133
- /workflow code-review --inspect
134
- /workflow code-review --result-viewer
135
- /workflow code-review --no-result-viewer
136
- /workflow code-review --concurrency=4
137
- /workflow code-review --budget=50000
138
- /workflow code-review --resume <run-id>
139
- /workflow code-review --refresh
140
- ```
141
-
142
- Every workflow result includes a run id. If a long run is interrupted, rerun the same workflow with `--resume <run-id>` to replay matching completed `agent()` calls from the journal and run only the first changed or missing call onward. Resume is deterministic-prefix based: keep prompts and agent options stable if you want cache hits.
143
-
144
- The code-review workflow can open an interactive findings viewer. Select findings and hand them back to the parent agent for minimal fixes, or ask it to raise GitHub PR comments when `gh` is authenticated and PR context is available.
145
-
146
- For the full command guide, see [USAGE.md](USAGE.md).
147
-
148
- ## What Dynamax Is Good For
149
-
150
- Use it for:
151
-
152
- - adversarial reviews before you trust a plan or diff;
153
- - debugging where several causes are plausible;
154
- - design comparisons where you want both sides argued;
155
- - refactor scouting where broad rewrites would be risky;
156
- - performance investigations where measurement gaps matter;
157
- - documentation reviews where consistency and reader expectations both matter.
158
-
159
- Do not use it for every tiny task. A single prompt is still better for simple edits, one-file explanations, and obvious mechanical changes.
160
-
161
- ## Local Development
162
-
163
- Only needed if you want to add workflows, customize workflows, or contribute. Using the package does not require a clone, install, or build step.
45
+ The package uses pi's bundled SDK and core packages; it does not install or
46
+ bundle a second agent runtime.
47
+
48
+ ## What it gives you
49
+
50
+ - **Independent passes.** Different agents can inspect correctness, tests,
51
+ coupling, performance, or any lenses you define.
52
+ - **Typed handoffs.** A TypeBox schema becomes the agent's terminating tool
53
+ contract—there is no JSON scraping.
54
+ - **Live progress.** Phases, agents, findings, and logs are visible in the TUI,
55
+ alongside usage and estimated cost when provider data is available.
56
+ - **Bounded execution.** One semaphore covers nested fan-out, pipelines, and
57
+ sub-workflows.
58
+ - **Evidence before action.** Built-in workflows inspect and report by default;
59
+ they do not edit your working tree.
60
+ - **Reviewable mutation.** An author can opt a mutating agent into a disposable
61
+ git worktree and receive its patch.
62
+ - **Safe replay.** Interrupted runs reuse a completed call only when every
63
+ captured observable surface and its effective execution identity still match.
64
+
65
+ ## Built-in workflows
66
+
67
+ | Workflow | Best used for |
68
+ | --- | --- |
69
+ | `code-review` | PRs, branch diffs, ref ranges, or focused targets |
70
+ | `refactor-scout` | Small, defensible refactors without broad rewrites |
71
+ | `diagnose` | Competing explanations for bugs or failing commands |
72
+ | `perf-review` | Measured bottlenecks versus performance guesses |
73
+
74
+ These are starting points, not a fixed menu. Dynamax can author a one-off
75
+ workflow when the question needs different lenses or an adaptive second pass.
76
+
77
+ ## The review experience
78
+
79
+ When reviewing a PR, `code-review` uses its cumulative diff, so superseded code
80
+ from earlier commits does not become a finding. Candidate issues are
81
+ independently verified, deduplicated, and ranked before synthesis.
82
+
83
+ Add `--result-viewer` to open the interactive findings view. It exposes a findings list for follow-up. Reopen the current session's latest review
84
+ with `/workflow:results` or `ctrl+shift+r`—it does not rerun the workflow.
85
+
86
+ The **Fix** action creates one disposable worktree per selected finding and
87
+ returns separate patch previews without touching the active checkout. GitHub
88
+ comments are available only for a verified PR target; the engine checks the
89
+ snapshot and PR head again and skips exact duplicates before posting.
90
+
91
+ ## Prompts that benefit from Dynamax
92
+
93
+ - **Review:** `dynamax` review this branch from correctness, test,
94
+ security-boundary, and maintainability angles; verify every finding.
95
+ - **Debug:** `dynamax` investigate this flaky test using separate hypotheses for
96
+ timing, fixtures, recent diffs, and external state.
97
+ - **Decide:** `dynamax` argue both implementation options, then have another
98
+ agent attack the recommendation.
99
+ - **Refactor:** `dynamax` find the smallest safe path through this module and
100
+ identify what should not change.
101
+
102
+ Tiny edits and straightforward questions are still better handled by one agent.
103
+ Dynamax earns its cost when independence or breadth changes the answer.
104
+
105
+ ## Write a workflow
106
+
107
+ A saved workflow is a TypeScript module with metadata and one async function.
108
+ The injected API supplies agents, concurrency primitives, phases, progress,
109
+ budgets, cancellation, and sub-workflow composition.
110
+
111
+ ```ts
112
+ import { Type } from "typebox";
113
+ import type { WorkflowApi, WorkflowMeta } from "../src/types.ts";
114
+
115
+ export const meta: WorkflowMeta = {
116
+ name: "two-angle-review",
117
+ description: "Find issues independently, then synthesize.",
118
+ };
119
+
120
+ const Finding = Type.Object({
121
+ summary: Type.String(),
122
+ evidence: Type.Array(Type.String()),
123
+ });
124
+ const reviewTools = ["read", "grep", "find", "ls"];
125
+
126
+ export default async function run(
127
+ { agent, parallel, phase, args }: WorkflowApi,
128
+ ) {
129
+ phase("Find");
130
+ const findings = await parallel([
131
+ () => agent(`Review correctness: ${args}`, {
132
+ schema: Finding,
133
+ tools: reviewTools,
134
+ thinkingLevel: "low",
135
+ resume: "read-only",
136
+ }),
137
+ () => agent(`Review edge cases: ${args}`, {
138
+ schema: Finding,
139
+ tools: reviewTools,
140
+ thinkingLevel: "low",
141
+ resume: "read-only",
142
+ }),
143
+ ]);
144
+
145
+ phase("Synthesize");
146
+ return agent(`Merge and rank these findings: ${JSON.stringify(findings)}`, {
147
+ tools: [],
148
+ thinkingLevel: "medium",
149
+ resume: "read-only",
150
+ });
151
+ }
152
+ ```
153
+
154
+ Set `thinkingLevel` on fan-out agents so they do not all inherit an expensive
155
+ global setting. The [usage guide](USAGE.md#author-a-saved-workflow) covers
156
+ schemas, tools, skills, pipelines, settled results, worktree isolation, inline
157
+ workflows, and registration.
158
+
159
+ ## Design choices
160
+
161
+ - **Agent sessions:** each call uses an in-process pi `AgentSession` with an
162
+ in-memory session manager.
163
+ - **Structured output:** the schema is a terminating tool definition validated
164
+ by pi.
165
+ - **Recoverable failures:** a failed parallel branch becomes `null`; surviving
166
+ work continues.
167
+ - **Fatal failures:** cancellation aborts siblings and drains submitted work
168
+ before rejection.
169
+ - **Mutating agents:** disposable worktrees isolate edits and return
170
+ baseline-relative patches.
171
+ - **Resume:** journals bind workspace-aware calls to repository state and all
172
+ calls to workflow, model, prompt, skill, and tool identity.
173
+ - **Dependencies:** pi and TypeBox remain host-provided peers; the package ships
174
+ no duplicate runtime stack.
175
+
176
+ The replay contract deliberately fails closed. For workspace-aware calls,
177
+ changed commits, dirty state, and declared ignored inputs invalidate a hit.
178
+ Workflow source, effective model or prompt, selected skills, and executable
179
+ tools remain part of the relevant identity; unsafe or unbounded surfaces run
180
+ live instead.
181
+
182
+ ## Develop locally
164
183
 
165
184
  ```bash
166
185
  git clone https://github.com/timbrinded/pi-workflow-engine
167
186
  cd pi-workflow-engine
168
187
  bun install
169
- bun run typecheck
170
- bun run test
171
- ```
172
-
173
- Load your working copy through the package manifest without installing it:
174
-
175
- ```bash
176
- pi -e .
177
- ```
178
-
179
- If you also have the global package installed, pi may report duplicate `/workflow` or `workflow` tool diagnostics. Remove one source or load only this working copy:
180
-
181
- ```bash
188
+ bun run typecheck && bun run test
182
189
  pi -ne -e .
183
190
  ```
184
191
 
185
- ### Add A Workflow
186
-
187
- A workflow is a TypeScript module that exports `meta` plus a default async function. The `api` object gives you `agent`, `parallel`, `pipeline`, `workflow`, `phase`, `log`, and `progress`.
188
-
189
- Guaranteed built-ins are statically registered:
190
-
191
- 1. Add `.pi/extensions/pi-workflow-engine/workflows/<name>.ts`.
192
- 2. Import it in `.pi/extensions/pi-workflow-engine/src/workflows.ts`.
193
- 3. Add it to `BUILTIN_WORKFLOWS`.
194
-
195
- Drop-in workflows are also discovered best-effort from:
196
-
197
- - `.pi/extensions/pi-workflow-engine/workflows/*.ts`
198
- - `~/.pi/agent/workflows/*.ts`
199
-
200
- Use `/workflow <name> --refresh` after adding a drop-in file.
201
-
202
- Inline workflows are passed to the `workflow` tool as a script string. They are useful for one-off Dynamax orchestration. Inline v1 rules: no imports, no dynamic `import()`, pure-literal `meta`, schemas must use the injected `Type` value, and scripts run in-process with the extension's permissions.
192
+ `pi -ne -e .` loads the working copy without also loading an installed global
193
+ copy. There is no build step; pi loads the TypeScript extension directly.
203
194
 
204
- ### Technical Notes
195
+ For the complete command reference, runtime controls, authoring rules, and
196
+ troubleshooting, see [USAGE.md](USAGE.md). The public API lives in
197
+ [types.ts](.pi/extensions/pi-workflow-engine/src/types.ts), and the built-in
198
+ [code-review workflow](.pi/extensions/pi-workflow-engine/workflows/code-review.ts)
199
+ is the most complete example.
205
200
 
206
- - Each `agent()` runs an in-process pi `AgentSession` with `SessionManager.inMemory()`.
207
- - Workflow results aggregate the finalized assistant usage from those subagent sessions before disposal and show token/cost totals separately from pi's host-session footer accounting.
208
- - Structured output uses a terminating tool whose `parameters` is your schema. pi validates the call; the engine captures the args. There is no JSON scraping.
209
- - A schema agent that finishes without calling `final_answer` is re-prompted once before returning `null`.
210
- - Runs write completed `agent()` results to `.pi/.workflow-runs/<run-id>.jsonl`; `--resume <run-id>` replays the longest unchanged prefix and writes a fresh journal for the resumed run.
211
- - `agent(..., { isolation: "worktree" })` runs in a disposable git worktree and returns `{ result, patch, changed }`; the user working tree is not mutated.
212
- - A single run-level semaphore caps concurrent agents, so nested `parallel`, `pipeline`, and `workflow()` calls stay bounded.
213
- - `parallel()` and `pipeline()` are fail-soft: recoverable branch failures, including budget backstops, become `null` slots while survivors continue. A genuine run abort still rejects.
214
- - Built-in workflows stay statically imported so they share pi's bundled `typebox` identity.
215
- - Set `thinkingLevel` per fan-out agent. Otherwise many subagents can inherit an expensive global reasoning level.
216
- - Subagents receive no skills by default. Opt in per agent with `skills: ["skill-name"]`; clear prompt text like `include skill name` also works when `skills` is omitted.
217
- - `tools` is a strict allowlist for subagents. Use `toolHints: ["search"]` to dynamically expose installed grep/find/search-like tools such as `ast-grep`, `mgrep`, `ffgrep`, or `fffind` without hard-coding a specific extension.
218
- - Set `model` per agent only when needed: bare ids are Anthropic shorthand, and `provider/id` targets other providers.
219
- Omitted models inherit the host/session default; malformed or unknown explicit refs fail fast.
220
-
221
- ### Performance Controls
222
-
223
- Runtime controls:
224
-
225
- - `PI_WORKFLOW_PERF=1` enables per-run performance timing. This is timing-only; workflow usage/cost totals are reported separately when subagent usage is available.
226
- - `PI_WORKFLOW_CONCURRENCY=N` sets the per-run agent cap. The default is `min(8, max(2, CPU count))`.
227
- - `PI_WORKFLOW_PARALLEL_SUBMISSION_LIMIT=N` limits how many `parallel()` thunks are submitted at once.
228
- - `PI_WORKFLOW_LANE_ITEM_LIMIT=N` caps retained progress lane items per lane.
229
-
230
- No-LLM benchmark scripts:
231
-
232
- ```bash
233
- bun run bench:concurrency -- --items 200 --concurrency 8 --json
234
- bun run bench:discovery -- --iterations 3 --json
235
- bun run bench:startup -- --json
236
- bun run bench:ui -- --agents 1000 --lane-items 1000 --json
237
- ```
238
-
239
- ### Layout
240
-
241
- ```text
242
- .pi/
243
- settings.json
244
- extensions/pi-workflow-engine/
245
- index.ts
246
- src/
247
- types.ts
248
- agent-runner.ts
249
- concurrency.ts
250
- engine.ts
251
- progress.ts
252
- discovery.ts
253
- workflows.ts
254
- workflows/
255
- code-review.ts
256
- refactor-scout.ts
257
- diagnose.ts
258
- perf-review.ts
259
- ```
201
+ Questions and ideas are welcome in
202
+ [GitHub Issues](https://github.com/timbrinded/pi-workflow-engine/issues).
203
+ MIT licensed.