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.
- package/.pi/extensions/pi-workflow-engine/index.ts +199 -120
- package/.pi/extensions/pi-workflow-engine/src/advisory-schema.ts +11 -0
- package/.pi/extensions/pi-workflow-engine/src/agent-attempt.ts +145 -0
- package/.pi/extensions/pi-workflow-engine/src/agent-replay.ts +376 -0
- package/.pi/extensions/pi-workflow-engine/src/agent-runner-types.ts +87 -0
- package/.pi/extensions/pi-workflow-engine/src/agent-runner.ts +84 -529
- package/.pi/extensions/pi-workflow-engine/src/agent-session-identity.ts +256 -0
- package/.pi/extensions/pi-workflow-engine/src/agent-session.ts +413 -0
- package/.pi/extensions/pi-workflow-engine/src/agent-skills.ts +43 -1
- package/.pi/extensions/pi-workflow-engine/src/agent-workspace.ts +81 -0
- package/.pi/extensions/pi-workflow-engine/src/budget.ts +7 -0
- package/.pi/extensions/pi-workflow-engine/src/cancellation.ts +17 -3
- package/.pi/extensions/pi-workflow-engine/src/concurrency.ts +164 -61
- package/.pi/extensions/pi-workflow-engine/src/diff-capture.ts +117 -20
- package/.pi/extensions/pi-workflow-engine/src/discovery.ts +64 -15
- package/.pi/extensions/pi-workflow-engine/src/dynamax-shortcuts.ts +59 -13
- package/.pi/extensions/pi-workflow-engine/src/dynamax.ts +14 -3
- package/.pi/extensions/pi-workflow-engine/src/engine.ts +208 -74
- package/.pi/extensions/pi-workflow-engine/src/finalizers.ts +66 -0
- package/.pi/extensions/pi-workflow-engine/src/identity-canonicalization.ts +138 -0
- package/.pi/extensions/pi-workflow-engine/src/identity-fingerprint.ts +15 -0
- package/.pi/extensions/pi-workflow-engine/src/inline-workflow.ts +8 -3
- package/.pi/extensions/pi-workflow-engine/src/journal.ts +148 -64
- package/.pi/extensions/pi-workflow-engine/src/options.ts +9 -6
- package/.pi/extensions/pi-workflow-engine/src/process-runner.ts +203 -39
- package/.pi/extensions/pi-workflow-engine/src/progress.ts +2 -5
- package/.pi/extensions/pi-workflow-engine/src/replay-path-identity.ts +48 -0
- package/.pi/extensions/pi-workflow-engine/src/resume-context.ts +665 -0
- package/.pi/extensions/pi-workflow-engine/src/review/github-pr-comments.ts +75 -16
- package/.pi/extensions/pi-workflow-engine/src/review/review-actions.ts +83 -17
- package/.pi/extensions/pi-workflow-engine/src/review/review-budget.ts +45 -0
- package/.pi/extensions/pi-workflow-engine/src/review/review-fix-workflow.ts +117 -0
- package/.pi/extensions/pi-workflow-engine/src/review/review-handoff.ts +14 -66
- package/.pi/extensions/pi-workflow-engine/src/review/review-issues.ts +38 -12
- package/.pi/extensions/pi-workflow-engine/src/review/review-report.ts +42 -0
- package/.pi/extensions/pi-workflow-engine/src/review/review-results-flow.ts +17 -22
- package/.pi/extensions/pi-workflow-engine/src/review/review-results-viewer.ts +143 -52
- package/.pi/extensions/pi-workflow-engine/src/review/review-session-coordinator.ts +153 -0
- package/.pi/extensions/pi-workflow-engine/src/review/review-snapshot.ts +295 -0
- package/.pi/extensions/pi-workflow-engine/src/tool-source-identity.ts +153 -0
- package/.pi/extensions/pi-workflow-engine/src/tree-fingerprint.ts +404 -0
- package/.pi/extensions/pi-workflow-engine/src/types.ts +38 -10
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-result-renderer.ts +5 -69
- package/.pi/extensions/pi-workflow-engine/src/unknown-error.ts +8 -0
- package/.pi/extensions/pi-workflow-engine/src/workflow-execution.ts +121 -0
- package/.pi/extensions/pi-workflow-engine/src/workflow-module.ts +19 -1
- package/.pi/extensions/pi-workflow-engine/src/workflows.ts +28 -3
- package/.pi/extensions/pi-workflow-engine/src/worktree.ts +282 -41
- package/.pi/extensions/pi-workflow-engine/workflows/code-review.ts +48 -26
- package/.pi/extensions/pi-workflow-engine/workflows/diagnose.ts +8 -1
- package/.pi/extensions/pi-workflow-engine/workflows/perf-review.ts +8 -2
- package/.pi/extensions/pi-workflow-engine/workflows/refactor-scout.ts +8 -1
- package/README.md +170 -226
- package/USAGE.md +92 -12
- 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 {
|
|
22
|
-
import type { ReviewContext } from "../src/review/review-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 \`${
|
|
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${
|
|
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
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-

|
|
2
|
-
|
|
3
1
|
# pi-workflow-engine
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
+
[](https://github.com/timbrinded/pi-workflow-engine/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/pi-workflow-engine)
|
|
5
|
+
[](LICENSE)
|
|
18
6
|
|
|
19
|
-
|
|
7
|
+
**A multi-agent workflow engine for [pi](https://pi.dev).**
|
|
20
8
|
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
|
|
28
|
-
pi list
|
|
29
|
-
```
|
|
18
|
+

|
|
30
19
|
|
|
31
|
-
|
|
20
|
+
## Quick start
|
|
32
21
|
|
|
33
22
|
```bash
|
|
34
|
-
pi install npm:pi-workflow-engine
|
|
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
|
-
|
|
26
|
+
Restart pi or run `/reload`, then add `dynamax` to a prompt:
|
|
104
27
|
|
|
105
28
|
```text
|
|
106
|
-
dynamax
|
|
29
|
+
dynamax investigate why typecheck started failing on this branch
|
|
107
30
|
```
|
|
108
31
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
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 "
|
|
41
|
+
/workflow diagnose "the schema migration broke typecheck"
|
|
120
42
|
/workflow perf-review "workflow startup latency"
|
|
121
43
|
```
|
|
122
44
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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.
|