pi-workflow-engine 0.11.0 → 0.13.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 (79) hide show
  1. package/.pi/extensions/pi-workflow-engine/index.ts +255 -97
  2. package/.pi/extensions/pi-workflow-engine/src/advisory-challenge.ts +75 -0
  3. package/.pi/extensions/pi-workflow-engine/src/advisory-evidence.ts +90 -0
  4. package/.pi/extensions/pi-workflow-engine/src/advisory-schema.ts +13 -5
  5. package/.pi/extensions/pi-workflow-engine/src/agent-attempt.ts +3 -1
  6. package/.pi/extensions/pi-workflow-engine/src/agent-limits.ts +35 -0
  7. package/.pi/extensions/pi-workflow-engine/src/agent-replay.ts +27 -4
  8. package/.pi/extensions/pi-workflow-engine/src/agent-retry.ts +116 -0
  9. package/.pi/extensions/pi-workflow-engine/src/agent-runner-types.ts +49 -45
  10. package/.pi/extensions/pi-workflow-engine/src/agent-runner.ts +95 -26
  11. package/.pi/extensions/pi-workflow-engine/src/agent-session-providers.ts +50 -0
  12. package/.pi/extensions/pi-workflow-engine/src/agent-session.ts +158 -203
  13. package/.pi/extensions/pi-workflow-engine/src/agent-skills.ts +48 -42
  14. package/.pi/extensions/pi-workflow-engine/src/agent-workspace.ts +26 -4
  15. package/.pi/extensions/pi-workflow-engine/src/background-workflow-tool.ts +75 -0
  16. package/.pi/extensions/pi-workflow-engine/src/background-workflows.ts +492 -0
  17. package/.pi/extensions/pi-workflow-engine/src/budget.ts +3 -3
  18. package/.pi/extensions/pi-workflow-engine/src/cancellation.ts +38 -0
  19. package/.pi/extensions/pi-workflow-engine/src/command-completions.ts +36 -0
  20. package/.pi/extensions/pi-workflow-engine/src/concurrency.ts +82 -10
  21. package/.pi/extensions/pi-workflow-engine/src/diff-capture.ts +16 -130
  22. package/.pi/extensions/pi-workflow-engine/src/discovery.ts +9 -5
  23. package/.pi/extensions/pi-workflow-engine/src/dynamax.ts +109 -60
  24. package/.pi/extensions/pi-workflow-engine/src/engine.ts +166 -18
  25. package/.pi/extensions/pi-workflow-engine/src/filesystem-error.ts +4 -0
  26. package/.pi/extensions/pi-workflow-engine/src/inline-workflow.ts +2 -2
  27. package/.pi/extensions/pi-workflow-engine/src/journal.ts +13 -6
  28. package/.pi/extensions/pi-workflow-engine/src/model-profile-command.ts +167 -0
  29. package/.pi/extensions/pi-workflow-engine/src/model-profiles.ts +310 -0
  30. package/.pi/extensions/pi-workflow-engine/src/options.ts +88 -1
  31. package/.pi/extensions/pi-workflow-engine/src/pi-compat.ts +32 -0
  32. package/.pi/extensions/pi-workflow-engine/src/progress-types.ts +50 -0
  33. package/.pi/extensions/pi-workflow-engine/src/progress.ts +72 -117
  34. package/.pi/extensions/pi-workflow-engine/src/provider-usage-limit.ts +189 -0
  35. package/.pi/extensions/pi-workflow-engine/src/research-contract.ts +89 -0
  36. package/.pi/extensions/pi-workflow-engine/src/research-evidence.ts +289 -0
  37. package/.pi/extensions/pi-workflow-engine/src/resume-context.ts +127 -41
  38. package/.pi/extensions/pi-workflow-engine/src/review/code-review-orchestration.ts +24 -0
  39. package/.pi/extensions/pi-workflow-engine/src/review/patch-validation.ts +120 -0
  40. package/.pi/extensions/pi-workflow-engine/src/review/review-fix-workflow.ts +43 -7
  41. package/.pi/extensions/pi-workflow-engine/src/review/review-format.ts +2 -1
  42. package/.pi/extensions/pi-workflow-engine/src/review/review-issues.ts +1 -1
  43. package/.pi/extensions/pi-workflow-engine/src/review/review-report.ts +1 -1
  44. package/.pi/extensions/pi-workflow-engine/src/review/review-results-flow.ts +2 -1
  45. package/.pi/extensions/pi-workflow-engine/src/review/review-results-viewer.ts +11 -30
  46. package/.pi/extensions/pi-workflow-engine/src/review/review-session-coordinator.ts +5 -5
  47. package/.pi/extensions/pi-workflow-engine/src/review/review-snapshot.ts +3 -4
  48. package/.pi/extensions/pi-workflow-engine/src/review-diff-target.ts +130 -0
  49. package/.pi/extensions/pi-workflow-engine/src/session-identity.ts +6 -0
  50. package/.pi/extensions/pi-workflow-engine/src/structured-output.ts +32 -0
  51. package/.pi/extensions/pi-workflow-engine/src/tool-capabilities.ts +44 -0
  52. package/.pi/extensions/pi-workflow-engine/src/tool-source-identity.ts +1 -4
  53. package/.pi/extensions/pi-workflow-engine/src/tree-fingerprint.ts +1 -4
  54. package/.pi/extensions/pi-workflow-engine/src/types.ts +52 -15
  55. package/.pi/extensions/pi-workflow-engine/src/ui/dynamax-editor-decoration.ts +220 -0
  56. package/.pi/extensions/pi-workflow-engine/src/ui/workflow-format.ts +26 -6
  57. package/.pi/extensions/pi-workflow-engine/src/ui/workflow-inspector.ts +88 -46
  58. package/.pi/extensions/pi-workflow-engine/src/ui/workflow-result-renderer.ts +7 -3
  59. package/.pi/extensions/pi-workflow-engine/src/ui/workflow-viewer-layout.ts +51 -0
  60. package/.pi/extensions/pi-workflow-engine/src/ui/workflow-widget.ts +12 -67
  61. package/.pi/extensions/pi-workflow-engine/src/usage.ts +124 -16
  62. package/.pi/extensions/pi-workflow-engine/src/workflow-advisory-utils.ts +151 -178
  63. package/.pi/extensions/pi-workflow-engine/src/workflow-execution.ts +1 -1
  64. package/.pi/extensions/pi-workflow-engine/src/workflow-run-background.ts +72 -0
  65. package/.pi/extensions/pi-workflow-engine/src/workflow-run-controller.ts +354 -0
  66. package/.pi/extensions/pi-workflow-engine/src/workflow-run-history.ts +162 -0
  67. package/.pi/extensions/pi-workflow-engine/src/workflow-run-record.ts +632 -0
  68. package/.pi/extensions/pi-workflow-engine/src/workflow-run-store.ts +172 -0
  69. package/.pi/extensions/pi-workflow-engine/src/workflow-usage-limit-scheduler.ts +80 -0
  70. package/.pi/extensions/pi-workflow-engine/src/workflows.ts +2 -0
  71. package/.pi/extensions/pi-workflow-engine/src/worktree.ts +47 -3
  72. package/.pi/extensions/pi-workflow-engine/workflows/code-review.ts +65 -162
  73. package/.pi/extensions/pi-workflow-engine/workflows/diagnose.ts +48 -149
  74. package/.pi/extensions/pi-workflow-engine/workflows/perf-review.ts +28 -77
  75. package/.pi/extensions/pi-workflow-engine/workflows/refactor-scout.ts +28 -77
  76. package/.pi/extensions/pi-workflow-engine/workflows/research.ts +170 -0
  77. package/README.md +72 -141
  78. package/USAGE.md +270 -17
  79. package/package.json +1 -1
@@ -1,14 +1,14 @@
1
+ import { challengeFindings, parseChallengeArgs } from "../src/advisory-challenge.ts";
1
2
  import { Type } from "typebox";
2
3
  import {
3
- AdvisoryReportSchema,
4
- type AdvisoryCandidate,
5
- type AdvisoryReport,
6
- type AdvisoryVerdict,
7
- } from "../src/advisory-schema.ts";
8
- import {
9
- backfillAdvisoryFindings,
4
+ type AdvisoryVerified,
5
+ type AdvisoryLens,
6
+ synthesizeAdvisoryReport,
7
+ finishAdvisoryReport,
8
+ emptyAdvisoryReport,
10
9
  formatEvidence,
11
10
  formatLocation,
11
+ publishVerifiedKeptProgress,
12
12
  runLensVerificationPipeline,
13
13
  DEFAULT_ADVISORY_TOOL_HINTS,
14
14
  DEFAULT_ADVISORY_TOOLS,
@@ -18,7 +18,7 @@ import type { WorkflowApi, WorkflowMeta, WorkflowRunStats } from "../src/types.t
18
18
  export const meta: WorkflowMeta = {
19
19
  name: "refactor-scout",
20
20
  description: "Advisory-only refactor scout: scope → per-lens find → independent verify → synthesize safe refactor opportunities.",
21
- phases: [{ title: "Scope" }, { title: "Find" }, { title: "Verify" }, { title: "Synthesize" }],
21
+ phases: [{ title: "Scope" }, { title: "Find" }, { title: "Verify" }, { title: "Challenge" }, { title: "Synthesize" }],
22
22
  };
23
23
 
24
24
  const ScopeSchema = Type.Object({
@@ -28,22 +28,7 @@ const ScopeSchema = Type.Object({
28
28
  conventions: Type.Optional(Type.String({ description: "Relevant project conventions from AGENTS.md / docs." })),
29
29
  });
30
30
 
31
- interface RefactorLens {
32
- label: string;
33
- category: string;
34
- text: string;
35
- }
36
-
37
- type Candidate = AdvisoryCandidate;
38
-
39
- interface Verified extends Candidate {
40
- verdict: AdvisoryVerdict["verdict"];
41
- evidence: string[];
42
- confidence?: AdvisoryVerdict["confidence"];
43
- lens: RefactorLens;
44
- }
45
-
46
- const REFACTOR_LENSES: RefactorLens[] = [
31
+ const REFACTOR_LENSES: AdvisoryLens[] = [
47
32
  { label: "duplication", category: "duplication", text: "Repeated logic, copy-pasted structures, or near-duplicate flows that could share one clearer implementation." },
48
33
  { label: "complexity", category: "complexity", text: "Oversized functions, tangled control flow, or abstractions that make local reasoning harder than necessary." },
49
34
  { label: "type-safety", category: "type-safety", text: "Weak typing, avoidable casts, unchecked shapes, or places stronger types would prevent mistakes." },
@@ -52,13 +37,12 @@ const REFACTOR_LENSES: RefactorLens[] = [
52
37
  { label: "conventions", category: "conventions", text: "Departures from project conventions, naming, dependency rules, or local idioms." },
53
38
  ];
54
39
 
55
- const TOOLS = DEFAULT_ADVISORY_TOOLS;
56
- const TOOL_HINTS = DEFAULT_ADVISORY_TOOL_HINTS;
57
40
  const PER_LENS = 5;
58
41
 
59
42
  export default async function run(api: WorkflowApi): Promise<unknown> {
60
- const { agent, parallel, pipeline, phase, log, progress, args } = api;
61
- const target = args.trim() || ".";
43
+ const { agent, phase, log, progress, args } = api;
44
+ const challengeConfig = parseChallengeArgs(args);
45
+ const target = challengeConfig.args.trim() || ".";
62
46
  let fileCount = 0;
63
47
  let rawCandidateCount = 0;
64
48
  let droppedCandidateCount = 0;
@@ -79,15 +63,15 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
79
63
  "Inspect repository structure, the target path or module, and relevant AGENTS.md / project docs conventions. " +
80
64
  "Return the concrete files that should be considered, a short summary, and any conventions that affect refactor advice. " +
81
65
  `This workflow will fan out across ${REFACTOR_LENSES.length} lenses with up to ${PER_LENS} candidates per lens. Structured output only.`,
82
- { phase: "Scope", label: "scope", tools: TOOLS, toolHints: TOOL_HINTS, thinkingLevel: "medium", schema: ScopeSchema },
66
+ { phase: "Scope", label: "scope", tools: DEFAULT_ADVISORY_TOOLS, toolHints: DEFAULT_ADVISORY_TOOL_HINTS, profile: "medium", schema: ScopeSchema },
83
67
  );
84
68
 
85
69
  if (!scope || scope.files.length === 0) {
86
- return emptyReport(
70
+ return finishAdvisoryReport(emptyAdvisoryReport(
87
71
  "No files were identified for refactor scouting.",
88
72
  ["Provide a target path, module, or subsystem to scout for refactor opportunities."],
89
73
  makeStats(0, 0),
90
- );
74
+ ), []);
91
75
  }
92
76
 
93
77
  fileCount = scope.files.length;
@@ -100,14 +84,10 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
100
84
  `## Summary\n${scope.summary}\n\n## Conventions\n${scope.conventions ?? "(none noted)"}\n` +
101
85
  (args.trim() ? `\n## User instructions (verbatim)\n${args.trim()}\n` : "");
102
86
 
103
- phase("Find");
104
87
  const pipelineResult = await runLensVerificationPipeline({
105
- api: { agent, parallel, pipeline, progress, log },
88
+ api,
106
89
  lenses: REFACTOR_LENSES,
107
- schedulingMode: "finder-barrier",
108
90
  perLens: PER_LENS,
109
- tools: TOOLS,
110
- toolHints: TOOL_HINTS,
111
91
  finderPrompt: (lens) =>
112
92
  `## Refactor-scout finder — ${lens.label}\n\n${scopeBlock}\n` +
113
93
  "This workflow is advisory-only: do not edit files and do not propose broad rewrites.\n" +
@@ -119,73 +99,44 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
119
99
  `## Refactor-scout verifier\n\n${scopeBlock}\n## Candidate\n` +
120
100
  `Location: ${formatLocation(candidate)}\nCategory: ${candidate.category}\nSummary: ${candidate.summary}\nImpact: ${candidate.impact}\n` +
121
101
  `Recommendation: ${candidate.recommendation ?? "(none supplied)"}\n\n` +
122
- "Read the relevant files and return CONFIRMED, PLAUSIBLE, or REFUTED. " +
102
+ "Read the relevant files and return CONFIRMED, PLAUSIBLE, NOT_SUBSTANTIATED, or REFUTED. " +
123
103
  "Default toward REFUTED if the opportunity is generic, too broad, not evidenced by code, or lacks a safe first step. " +
124
104
  "Evidence must quote or cite code. Structured output only.",
125
- makeVerified: (candidate, lens, judged): Verified => ({
126
- ...candidate,
127
- verdict: judged.verdict,
128
- evidence: judged.evidence,
129
- confidence: judged.confidence,
130
- lens,
131
- }),
132
105
  });
133
106
  rawCandidateCount += pipelineResult.rawCandidates;
134
107
  droppedCandidateCount += pipelineResult.dropped;
135
108
  refutedCandidateCount += pipelineResult.refuted;
136
- const verified = pipelineResult.verified;
109
+ const { coverage } = pipelineResult;
110
+ const verified = await challengeFindings(api, pipelineResult.verified, scopeBlock, challengeConfig.options, coverage);
137
111
  const surviving = verified.filter((finding) => finding.verdict !== "REFUTED");
138
112
  const stats = makeStats(verified.length, surviving.length);
139
- progress({ type: "counter", key: "verified", label: "verified", value: verified.length });
140
- progress({ type: "counter", key: "kept", label: "kept", value: surviving.length });
141
- progress({ type: "summary", key: "verified", value: verified.length });
142
- progress({ type: "summary", key: "kept", value: surviving.length });
143
- log(`${verified.length} verified → ${surviving.length} kept`);
113
+ publishVerifiedKeptProgress({ progress, log }, verified.length, surviving.length);
144
114
 
145
115
  if (surviving.length === 0) {
146
- return emptyReport("No refactor opportunities survived verification.", ["Leave the scoped code unchanged unless a human reviewer has additional context."], stats);
116
+ return finishAdvisoryReport(emptyAdvisoryReport("No refactor opportunities survived verification.", ["Leave the scoped code unchanged unless a human reviewer has additional context."], stats), coverage, verified);
147
117
  }
148
118
 
149
- phase("Synthesize");
150
119
  const ranked = [...surviving].sort((a, b) => rank(a) - rank(b));
151
120
  const block = ranked
152
121
  .map(
153
122
  (finding, index) =>
154
- `### [${index}] ${formatLocation(finding)} (${finding.verdict}, ${finding.category})\n` +
123
+ `### [${index}] IDs: ${finding.sourceCandidateIds.join(", ")} ${formatLocation(finding)} (${finding.verdict}, ${finding.category})\n` +
155
124
  `${finding.summary}\nImpact: ${finding.impact}\nEvidence: ${formatEvidence(finding.evidence)}\nSafe first step: ${finding.recommendation ?? "(none supplied)"}`,
156
125
  )
157
126
  .join("\n\n");
158
127
 
159
- const report = await agent(
128
+ const resolved = await synthesizeAdvisoryReport(api,
160
129
  `## Synthesis: final refactor-scout report\n\n${ranked.length} opportunities survived independent verification.\n\n${block}\n\n` +
161
130
  "Merge findings with the same root cause and rank highest leverage / lowest risk first. " +
162
- "Return the shared advisory report shape. Categories should come from the verified candidates. " +
163
- "Severity is maintenance or future-correctness impact. Confidence is high for CONFIRMED, medium for PLAUSIBLE unless verifier confidence says otherwise. " +
131
+ "Select findings by ID. " +
132
+ "Severity is maintenance or future-correctness impact. " +
164
133
  "Recommendations must be safe first refactor steps, not rewrites. Include concrete nextSteps for the host developer. Structured output only.",
165
- {
166
- phase: "Synthesize",
167
- label: "synthesize",
168
- tools: [],
169
- thinkingLevel: "medium",
170
- resume: "read-only",
171
- schema: AdvisoryReportSchema,
172
- },
134
+ ranked, coverage,
173
135
  );
174
-
175
- if (!report) return emptyReport("Synthesis produced no output.", ["Inspect verifier evidence manually or rerun the workflow with a narrower target."], stats);
176
-
177
- const findings = backfillAdvisoryFindings(report.findings, ranked, {
178
- impact: "Impact not restated by synthesis.",
179
- recommendation: "Choose a small, behavior-preserving refactor first step.",
180
- });
181
- return { ...report, findings, stats };
182
- }
183
-
184
- function emptyReport(summary: string, nextSteps: string[], stats: WorkflowRunStats): AdvisoryReport & { stats: WorkflowRunStats } {
185
- return { summary, findings: [], nextSteps, stats };
136
+ return finishAdvisoryReport({ ...resolved, stats: { ...stats, kept: resolved.findings.length } }, coverage, verified);
186
137
  }
187
138
 
188
- function rank(finding: Verified): number {
139
+ function rank(finding: AdvisoryVerified): number {
189
140
  const verdictRank = finding.verdict === "CONFIRMED" ? 0 : 1;
190
141
  const categoryRank = finding.category === "dead-code" || finding.category === "conventions" ? 2 : 0;
191
142
  return verdictRank + categoryRank;
@@ -0,0 +1,170 @@
1
+ import { compactResults } from "../src/concurrency.ts";
2
+ import {
3
+ MAX_RESEARCH_LANES,
4
+ ResearchLaneResultSchema,
5
+ ResearchPlanSchema,
6
+ ResearchReportSchema,
7
+ ResearchVerificationSchema,
8
+ type ResearchClaimCandidate,
9
+ type ResearchReport,
10
+ type ResearchVerification,
11
+ } from "../src/research-contract.ts";
12
+ import {
13
+ buildClaimCandidates,
14
+ fallbackResearchReport,
15
+ normalizeResearchLanes,
16
+ sanitizeLaneResults,
17
+ sanitizeResearchReport,
18
+ sanitizeVerification,
19
+ unavailableResearchReport,
20
+ unavailableVerification,
21
+ } from "../src/research-evidence.ts";
22
+ import { WorkflowToolHintUnavailableError } from "../src/tool-capabilities.ts";
23
+ import type { WorkflowApi, WorkflowMeta } from "../src/types.ts";
24
+
25
+ export const meta: WorkflowMeta = {
26
+ name: "research",
27
+ description: "Source-grounded external research: decompose → gather direct-page evidence → independently verify claims → cited synthesis.",
28
+ phases: [{ title: "Plan" }, { title: "Gather" }, { title: "Verify" }, { title: "Synthesize" }],
29
+ };
30
+
31
+ const EXTERNAL_TOOLS: string[] = [];
32
+ const EXTERNAL_TOOL_HINTS = ["external-search"] as const;
33
+
34
+ export default async function run(api: WorkflowApi): Promise<ResearchReport> {
35
+ const { agent, parallel, phase, log, progress, args } = api;
36
+ const question = args.trim();
37
+ if (!question) return unavailableResearchReport("empty-question");
38
+
39
+ phase("Plan");
40
+ let plan;
41
+ try {
42
+ plan = await agent(
43
+ `Plan bounded, source-grounded research for the user's question and any scope constraints embedded in it.\n\n` +
44
+ `Question and constraints (verbatim):\n${question}\n\n` +
45
+ `Create at most ${MAX_RESEARCH_LANES} non-overlapping lanes. Each lane needs a stable short id, title, objective, and 1-4 concrete search queries. ` +
46
+ "Separate primary-source discovery, current status, counterevidence, or jurisdiction/timeframe only when relevant. " +
47
+ "Do not claim exhaustive coverage. Structured output only.",
48
+ {
49
+ phase: "Plan",
50
+ label: "plan",
51
+ tools: EXTERNAL_TOOLS,
52
+ toolHints: EXTERNAL_TOOL_HINTS,
53
+ requireToolHints: true,
54
+ profile: "medium",
55
+ resume: "off",
56
+ schema: ResearchPlanSchema,
57
+ },
58
+ );
59
+ } catch (error) {
60
+ if (error instanceof WorkflowToolHintUnavailableError) return unavailableResearchReport("missing-capability");
61
+ throw error;
62
+ }
63
+
64
+ if (!plan) return unavailableResearchReport("no-evidence");
65
+ const lanes = normalizeResearchLanes(plan);
66
+ if (lanes.length === 0) return unavailableResearchReport("no-evidence");
67
+ progress({ type: "counter", key: "research.lanes", label: "research lanes", value: lanes.length });
68
+ progress({ type: "summary", key: "research.question", value: question });
69
+
70
+ phase("Gather");
71
+ const gathered = compactResults(
72
+ await parallel(
73
+ lanes.map((lane) => async () => {
74
+ const result = await agent(
75
+ `Research one bounded lane using only installed external web-search, browsing, or URL-extraction tools.\n\n` +
76
+ `Question: ${question}\n` +
77
+ `Scope constraints: ${plan.scopeConstraints.join("; ") || "(none)"}\n` +
78
+ `Lane id: ${lane.id}\nLane: ${lane.title}\nObjective: ${lane.objective}\n` +
79
+ `Queries:\n${lane.queries.map((query) => `- ${query}`).join("\n")}\n\n` +
80
+ "Open the supporting pages instead of citing a search-results page. Prefer primary and authoritative sources; use independent sources when useful. " +
81
+ "Return concrete claims with importance, whether each page supports or conflicts with the claim, a short passage or precise paraphrase, and the exact page title and URL. " +
82
+ "State evidence gaps. Never invent a URL or claim that the opened page does not support. Structured output only.",
83
+ {
84
+ phase: "Gather",
85
+ label: `gather:${lane.id}`,
86
+ tools: EXTERNAL_TOOLS,
87
+ toolHints: EXTERNAL_TOOL_HINTS,
88
+ requireToolHints: true,
89
+ profile: "small",
90
+ resume: "off",
91
+ schema: ResearchLaneResultSchema,
92
+ },
93
+ );
94
+ if (!result) return null;
95
+ progress({ type: "counter_delta", key: "research.evidence", label: "evidence items", delta: result.evidence.length });
96
+ progress({
97
+ type: "lane_item",
98
+ lane: "Research lanes",
99
+ title: lane.title,
100
+ subtitle: `${result.evidence.length} evidence item(s)`,
101
+ status: result.evidence.length > 0 ? "success" : "warning",
102
+ details: result.gaps.join("; ") || lane.objective,
103
+ });
104
+ return result;
105
+ }),
106
+ ),
107
+ );
108
+
109
+ const laneResults = sanitizeLaneResults(gathered);
110
+ const candidates = buildClaimCandidates(laneResults);
111
+ if (candidates.length === 0) return unavailableResearchReport("no-evidence");
112
+ progress({ type: "counter", key: "research.claims", label: "claims to verify", value: candidates.length });
113
+ log(`${candidates.length} bounded claim(s) selected for independent verification`);
114
+
115
+ phase("Verify");
116
+ const verificationResults = await parallel(
117
+ candidates.map((candidate, index) => async () => {
118
+ const result = await agent(verificationPrompt(question, candidate), {
119
+ phase: "Verify",
120
+ label: `verify:${index + 1}`,
121
+ tools: EXTERNAL_TOOLS,
122
+ toolHints: EXTERNAL_TOOL_HINTS,
123
+ requireToolHints: true,
124
+ profile: "medium",
125
+ resume: "off",
126
+ schema: ResearchVerificationSchema,
127
+ });
128
+ return result ? sanitizeVerification(result, candidate) : null;
129
+ }),
130
+ );
131
+ const verifications = verificationResults.map((result, index) => result ?? unavailableVerification(candidates[index]!));
132
+ for (const verification of verifications) {
133
+ progress({ type: "counter_delta", key: `research.${verification.verdict.toLowerCase()}`, label: verification.verdict.toLowerCase(), delta: 1 });
134
+ }
135
+ const synthesisInputs = verifications.filter((verification) => verification.verdict !== "REJECTED");
136
+
137
+ phase("Synthesize");
138
+ const synthesis = await agent(
139
+ `Answer the research question using only the independently verified handoff below.\n\n` +
140
+ `Question: ${question}\nScope constraints: ${plan.scopeConstraints.join("; ") || "(none)"}\n\n` +
141
+ `Verified claims JSON:\n${JSON.stringify(synthesisInputs)}\n\n` +
142
+ "Keep SUPPORTED claims, CONFLICTED evidence, UNCERTAIN claims, and model INFERENCE in their separate fields. Exclude REJECTED claims. " +
143
+ "Copy each verified claim string exactly so its citations remain bound to that claim during validation. " +
144
+ "Every supported or conflicting claim must cite exact title/URL objects from its verification; do not add URLs, cite search-results pages, or turn inference into fact. " +
145
+ "Answer concisely, disclose limited coverage, and provide useful next steps. Structured output only.",
146
+ {
147
+ phase: "Synthesize",
148
+ label: "synthesize",
149
+ tools: [],
150
+ profile: "medium",
151
+ resume: "off",
152
+ schema: ResearchReportSchema,
153
+ },
154
+ );
155
+
156
+ return sanitizeResearchReport(
157
+ synthesis ?? fallbackResearchReport(synthesisInputs, "The model did not return a structured synthesis."),
158
+ synthesisInputs,
159
+ );
160
+ }
161
+
162
+ function verificationPrompt(question: string, candidate: ResearchClaimCandidate): string {
163
+ return `Independently verify one important research claim using installed external web-search, browsing, or URL-extraction tools.\n\n` +
164
+ `Question: ${question}\nClaim: ${candidate.claim}\nImportance: ${candidate.importance}\n\n` +
165
+ `Gather-stage evidence (context only; do not treat it as verified):\n${JSON.stringify(candidate.evidence)}\n\n` +
166
+ "Search independently and open direct supporting pages. Prefer primary/authoritative sources and actively look for credible counterevidence. " +
167
+ "Return SUPPORTED only when direct pages substantiate the claim, CONFLICTED when credible sources disagree, UNCERTAIN when evidence is insufficient, " +
168
+ "INFERENCE when the conclusion is reasoned rather than directly stated, or REJECTED when evidence refutes it. " +
169
+ "Include exact page titles and direct HTTP(S) URLs, never search-results URLs. Structured output only.";
170
+ }
package/README.md CHANGED
@@ -4,63 +4,48 @@
4
4
  [![npm](https://img.shields.io/npm/v/pi-workflow-engine)](https://www.npmjs.com/package/pi-workflow-engine)
5
5
  [![MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
6
 
7
- **A multi-agent workflow engine for [pi](https://pi.dev).**
7
+ **Multi-agent workflows for [pi](https://pi.dev).**
8
8
 
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.
12
-
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.
9
+ When a task benefits from independent review, competing hypotheses, or separate
10
+ verification, pi-workflow-engine fans it out to subagents and synthesizes one
11
+ result. Run a built-in workflow or add `dynamax` to a prompt so pi can compose a
12
+ one-off workflow for the task.
17
13
 
18
14
  ![A lead agent coordinating subagents](assets/preview.png)
19
15
 
20
16
  ## Quick start
21
17
 
18
+ Requires **pi 0.80.10 or newer**.
19
+
22
20
  ```bash
23
21
  pi install npm:pi-workflow-engine
24
22
  ```
25
23
 
26
- Restart pi or run `/reload`, then add `dynamax` to a prompt:
24
+ Restart pi or run `/reload`, then try either style:
27
25
 
28
26
  ```text
29
27
  dynamax investigate why typecheck started failing on this branch
30
- ```
31
-
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.
35
-
36
- Saved workflows are available directly:
37
28
 
38
- ```text
39
29
  /workflow code-review
40
30
  /workflow refactor-scout src/
41
31
  /workflow diagnose "the schema migration broke typecheck"
42
- /workflow perf-review "workflow startup latency"
43
32
  ```
44
33
 
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.
34
+ Built-in workflows inspect and report by default; they do not edit your working
35
+ tree.
36
+
37
+ ## What you get
38
+
39
+ - **Independent analysis.** Give correctness, testing, performance, or other
40
+ concerns to separate agents instead of asking one agent to cover everything.
41
+ - **Verified results.** Workflows can check and rank findings before presenting
42
+ a single synthesis.
43
+ - **Live visibility.** Follow phases and agents in the TUI, with usage and cost
44
+ totals when the provider reports them.
45
+ - **Controlled execution.** Concurrency, timeouts, agent counts, and token
46
+ budgets keep fan-out bounded.
47
+ - **Safe follow-up.** Advisory runs leave the checkout alone, while selected
48
+ review fixes can return isolated patch previews.
64
49
 
65
50
  ## Built-in workflows
66
51
 
@@ -69,115 +54,63 @@ bundle a second agent runtime.
69
54
  | `code-review` | PRs, branch diffs, ref ranges, or focused targets |
70
55
  | `refactor-scout` | Small, defensible refactors without broad rewrites |
71
56
  | `diagnose` | Competing explanations for bugs or failing commands |
72
- | `perf-review` | Measured bottlenecks versus performance guesses |
57
+ | `perf-review` | Measured bottlenecks rather than performance guesses |
58
+ | `research` | Cited external evidence with independent claim verification |
73
59
 
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.
60
+ The `research` workflow needs an installed pi tool that can search or browse the
61
+ web. See the [built-in workflow guide](USAGE.md#built-in-workflows) for behavior
62
+ and requirements.
76
63
 
77
- ## The review experience
64
+ ## Use Dynamax for one-off workflows
78
65
 
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.
66
+ Use `dynamax` when no saved workflow quite fits and independent passes are
67
+ likely to change the answer:
82
68
 
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.
69
+ ```text
70
+ dynamax review this branch for correctness, tests, and maintainability; verify every finding
71
+ dynamax investigate this flaky test using separate timing, fixture, diff, and external-state hypotheses
72
+ dynamax argue both implementation options, then have another agent challenge the recommendation
73
+ ```
85
74
 
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.
75
+ The token enables Dynamax for one turn. Sticky mode remains active for the pi
76
+ session:
90
77
 
91
- ## Prompts that benefit from Dynamax
78
+ ```text
79
+ /workflow:dynamax on
80
+ /workflow:dynamax off
81
+ /workflow:dynamax status
82
+ ```
92
83
 
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.
84
+ In the TUI, a standalone `dynamax` token receives a short moving-shine sweep and
85
+ then stays highlighted. Use `PI_DYNAMAX_EFFECT=static` or
86
+ `PI_DYNAMAX_EFFECT=off` to reduce or disable the cue; the
87
+ [prompt editor guide](USAGE.md#prompt-editor-cue) has the full details.
101
88
 
102
89
  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
- }
90
+ Dynamax is useful when independence or breadth materially improves the result.
91
+
92
+ ## Review and inspect results
93
+
94
+ `code-review` verifies, deduplicates, and ranks candidate findings. Open the
95
+ interactive findings view for follow-up, or bring back the latest result without
96
+ rerunning the workflow:
97
+
98
+ ```text
99
+ /workflow code-review --result-viewer
100
+ /workflow:results
101
+ /workflow:inspector
152
102
  ```
153
103
 
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.
104
+ The viewer can produce isolated patch previews for selected findings without
105
+ changing the active checkout. The [usage guide](USAGE.md#workflow-results)
106
+ covers review actions and recent run history.
107
+
108
+ ## Create your own workflows
109
+
110
+ Saved workflows are TypeScript modules for repeatable orchestration; inline
111
+ workflows are useful for one-off Dynamax tasks. The
112
+ [workflow authoring guide](USAGE.md#author-a-saved-workflow) covers the API,
113
+ typed handoffs, model profiles, tool access, composition, and registration.
181
114
 
182
115
  ## Develop locally
183
116
 
@@ -192,11 +125,9 @@ pi -ne -e .
192
125
  `pi -ne -e .` loads the working copy without also loading an installed global
193
126
  copy. There is no build step; pi loads the TypeScript extension directly.
194
127
 
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.
128
+ For the complete command reference, model configuration, workflow authoring,
129
+ runtime controls, resume behavior, and troubleshooting, see
130
+ [USAGE.md](USAGE.md).
200
131
 
201
132
  Questions and ideas are welcome in
202
133
  [GitHub Issues](https://github.com/timbrinded/pi-workflow-engine/issues).