@signalridge/pi-workflows 1.3.2 → 1.5.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/src/builtins.ts CHANGED
@@ -55,7 +55,7 @@ const plan = await agent(
55
55
  'You are planning web research for this question:\\n' + question +
56
56
  '\\n\\nProduce ' + angles + ' diverse, specific search queries that together cover the question from different angles. ' +
57
57
  'Use your web search tools when helpful to refine them.',
58
- { label: 'plan queries', schema: { type: 'object', properties: { queries: { type: 'array', items: { type: 'string' } } }, required: ['queries'] } }
58
+ { label: 'plan queries', tier: 'medium', schema: { type: 'object', properties: { queries: { type: 'array', items: { type: 'string' } } }, required: ['queries'] } }
59
59
  )
60
60
  const planned = plan && Array.isArray(plan.queries) ? plan.queries.filter((q) => typeof q === 'string' && q.trim().length > 0) : []
61
61
  const queries = (planned.length > 0 ? planned : [question]).slice(0, angles)
@@ -67,7 +67,7 @@ const gathered = await parallel(queries.map((q, i) => () =>
67
67
  '\\n\\nSteps: (1) run a web search for the query; (2) fetch the most relevant result URLs; ' +
68
68
  '(3) extract concrete, verifiable factual claims, each tagged with the exact source URL it came from. ' +
69
69
  'Do NOT invent sources or claims — report only what the fetched pages actually say.',
70
- { label: 'research ' + (i + 1), schema: { type: 'object', properties: { sources: { type: 'array', items: { type: 'object', properties: { url: { type: 'string' }, claims: { type: 'array', items: { type: 'string' } } }, required: ['url', 'claims'] } } }, required: ['sources'] } }
70
+ { label: 'research ' + (i + 1), tier: 'low', schema: { type: 'object', properties: { sources: { type: 'array', items: { type: 'object', properties: { url: { type: 'string' }, claims: { type: 'array', items: { type: 'string' } } }, required: ['url', 'claims'] } } }, required: ['sources'] } }
71
71
  )
72
72
  ))
73
73
  const allSources = gathered.filter(Boolean).flatMap((g) => (g && g.sources) || [])
@@ -77,7 +77,7 @@ const verdict = await agent(
77
77
  'Cross-check these research sources. Group claims that assert the same fact across different source URLs. ' +
78
78
  'Keep a claim only if it is supported by at least ' + minSupport + ' distinct source URLs OR by one clearly authoritative source. ' +
79
79
  'Discard claims found in a single weak source or that conflict with others.\\n\\nSOURCES JSON:\\n' + JSON.stringify(allSources),
80
- { label: 'cross-check', schema: { type: 'object', properties: { supported: { type: 'array', items: { type: 'object', properties: { claim: { type: 'string' }, sources: { type: 'array', items: { type: 'string' } } }, required: ['claim', 'sources'] } }, discarded: { type: 'array', items: { type: 'string' } } }, required: ['supported'] } }
80
+ { label: 'cross-check', tier: 'medium', schema: { type: 'object', properties: { supported: { type: 'array', items: { type: 'object', properties: { claim: { type: 'string' }, sources: { type: 'array', items: { type: 'string' } } }, required: ['claim', 'sources'] } }, discarded: { type: 'array', items: { type: 'string' } } }, required: ['supported'] } }
81
81
  )
82
82
 
83
83
  phase('Report')
@@ -85,7 +85,7 @@ const report = await agent(
85
85
  'Write a concise, well-structured research report that answers the question using ONLY the supported claims below. ' +
86
86
  'Cite source URLs inline next to each claim. If the evidence is thin, say so explicitly.\\n\\n' +
87
87
  'QUESTION: ' + question + '\\n\\nSUPPORTED CLAIMS JSON:\\n' + JSON.stringify((verdict && verdict.supported) || []),
88
- { label: 'write report' }
88
+ { label: 'write report', tier: 'medium' }
89
89
  )
90
90
 
91
91
  return { question, queries, supported: (verdict && verdict.supported) || [], report }`;
@@ -107,7 +107,7 @@ const threshold = (args && args.threshold) || 0.5
107
107
  phase('Investigate')
108
108
  const investigation = await agent(
109
109
  'Investigate the following and list concrete, individually-checkable findings:\\n' + task,
110
- { label: 'investigate', schema: { type: 'object', properties: { findings: { type: 'array', items: { type: 'string' } } }, required: ['findings'] } }
110
+ { label: 'investigate', tier: 'medium', schema: { type: 'object', properties: { findings: { type: 'array', items: { type: 'string' } } }, required: ['findings'] } }
111
111
  )
112
112
  const findings = (investigation && Array.isArray(investigation.findings) ? investigation.findings : [])
113
113
 
@@ -118,7 +118,7 @@ const judged = await parallel(findings.map((f, i) => () =>
118
118
  'You are a skeptical reviewer. Try to REFUTE this finding for the task below. ' +
119
119
  'Default to real=false when uncertain. Investigate with the available tools if needed.\\n\\n' +
120
120
  'TASK: ' + task + '\\nFINDING: ' + f,
121
- { label: 'refute ' + (i + 1) + '.' + (r + 1), schema: { type: 'object', properties: { real: { type: 'boolean' }, reason: { type: 'string' } }, required: ['real'] } }
121
+ { label: 'refute ' + (i + 1) + '.' + (r + 1), tier: 'low', schema: { type: 'object', properties: { real: { type: 'boolean' }, reason: { type: 'string' } }, required: ['real'] } }
122
122
  )
123
123
  )).then((votes) => {
124
124
  const valid = votes.filter(Boolean)
@@ -134,7 +134,7 @@ const report = await agent(
134
134
  'Synthesize the surviving findings into a concise review. For each finding, note the skeptic agreement ' +
135
135
  '(realVotes/totalVotes). Do not include findings that failed the refutation pass.\\n\\n' +
136
136
  'TASK: ' + task + '\\n\\nSURVIVING FINDINGS JSON:\\n' + JSON.stringify(survivors),
137
- { label: 'consensus report' }
137
+ { label: 'consensus report', tier: 'medium' }
138
138
  )
139
139
 
140
140
  return { findings, judged: judged.filter(Boolean), survivors, report }`;
@@ -154,13 +154,13 @@ const diffSource = (args && args.diffSource) || 'provided diff'
154
154
 
155
155
  phase('Scan')
156
156
  const finders = await parallel([
157
- () => agent('You are a correctness reviewer. Find bugs, race conditions, error-handling gaps, and edge-case failures in this diff.\\n\\n' + diff, { label: 'correctness', schema: { type: 'object', properties: { findings: { type: 'array', items: { type: 'string' } } }, required: ['findings'] } }),
158
- () => agent('You are a reuse reviewer. Find duplicated logic, reinvented wheels, and missed opportunities to reuse existing code or libraries.\\n\\n' + diff, { label: 'reuse', schema: { type: 'object', properties: { findings: { type: 'array', items: { type: 'string' } } }, required: ['findings'] } }),
159
- () => agent('You are a simplification reviewer. Find over-engineering, unnecessary complexity, and simpler alternatives.\\n\\n' + diff, { label: 'simplification', schema: { type: 'object', properties: { findings: { type: 'array', items: { type: 'string' } } }, required: ['findings'] } }),
160
- () => agent('You are an efficiency reviewer. Find performance problems, avoidable allocations, and scaling risks.\\n\\n' + diff, { label: 'efficiency', schema: { type: 'object', properties: { findings: { type: 'array', items: { type: 'string' } } }, required: ['findings'] } }),
161
- () => agent('You are an architecture reviewer. Assess the change at the right altitude: design fit, layering, boundaries, and future maintenance.\\n\\n' + diff, { label: 'altitude', schema: { type: 'object', properties: { findings: { type: 'array', items: { type: 'string' } } }, required: ['findings'] } }),
162
- () => agent('You are a security reviewer. Find trust-boundary, injection, secret-leak, permission, and unsafe-input risks in this diff.\\n\\n' + diff, { label: 'security', schema: { type: 'object', properties: { findings: { type: 'array', items: { type: 'string' } } }, required: ['findings'] } }),
163
- () => agent('You are a testability reviewer. Find missing regression tests, observability gaps, and rollout/recovery risks in this diff.\\n\\n' + diff, { label: 'testability', schema: { type: 'object', properties: { findings: { type: 'array', items: { type: 'string' } } }, required: ['findings'] } }),
157
+ () => agent('You are a correctness reviewer. Find bugs, race conditions, error-handling gaps, and edge-case failures in this diff.\\n\\n' + diff, { label: 'correctness', tier: 'low', schema: { type: 'object', properties: { findings: { type: 'array', items: { type: 'string' } } }, required: ['findings'] } }),
158
+ () => agent('You are a reuse reviewer. Find duplicated logic, reinvented wheels, and missed opportunities to reuse existing code or libraries.\\n\\n' + diff, { label: 'reuse', tier: 'low', schema: { type: 'object', properties: { findings: { type: 'array', items: { type: 'string' } } }, required: ['findings'] } }),
159
+ () => agent('You are a simplification reviewer. Find over-engineering, unnecessary complexity, and simpler alternatives.\\n\\n' + diff, { label: 'simplification', tier: 'low', schema: { type: 'object', properties: { findings: { type: 'array', items: { type: 'string' } } }, required: ['findings'] } }),
160
+ () => agent('You are an efficiency reviewer. Find performance problems, avoidable allocations, and scaling risks.\\n\\n' + diff, { label: 'efficiency', tier: 'low', schema: { type: 'object', properties: { findings: { type: 'array', items: { type: 'string' } } }, required: ['findings'] } }),
161
+ () => agent('You are an architecture reviewer. Assess the change at the right altitude: design fit, layering, boundaries, and future maintenance.\\n\\n' + diff, { label: 'altitude', tier: 'medium', schema: { type: 'object', properties: { findings: { type: 'array', items: { type: 'string' } } }, required: ['findings'] } }),
162
+ () => agent('You are a security reviewer. Find trust-boundary, injection, secret-leak, permission, and unsafe-input risks in this diff.\\n\\n' + diff, { label: 'security', tier: 'low', schema: { type: 'object', properties: { findings: { type: 'array', items: { type: 'string' } } }, required: ['findings'] } }),
163
+ () => agent('You are a testability reviewer. Find missing regression tests, observability gaps, and rollout/recovery risks in this diff.\\n\\n' + diff, { label: 'testability', tier: 'low', schema: { type: 'object', properties: { findings: { type: 'array', items: { type: 'string' } } }, required: ['findings'] } }),
164
164
  ])
165
165
  const candidates = finders.filter(Boolean).flatMap((f) => (f && f.findings) || [])
166
166
 
@@ -168,7 +168,7 @@ phase('Verify')
168
168
  const verified = await parallel(candidates.map((finding, i) => () =>
169
169
  agent('You are a skeptical verifier. For this candidate finding, check whether it is a REAL issue in the diff. ' +
170
170
  'Default to real=false when uncertain.\\n\\nCANDIDATE: ' + finding + '\\n\\nDIFF:\\n' + diff,
171
- { label: 'verify ' + (i + 1), schema: { type: 'object', properties: { real: { type: 'boolean' }, reason: { type: 'string' } }, required: ['real'] } })
171
+ { label: 'verify ' + (i + 1), tier: 'low', schema: { type: 'object', properties: { real: { type: 'boolean' }, reason: { type: 'string' } }, required: ['real'] } })
172
172
  ))
173
173
 
174
174
  phase('Report')
@@ -179,7 +179,7 @@ const report = await agent(
179
179
  'Write a ranked code review report for this diff (' + diffSource + '). ' +
180
180
  'Include only verified findings, ordered by severity, each with a concrete location and fix suggestion.\\n\\n' +
181
181
  'VERIFIED FINDINGS JSON:\\n' + JSON.stringify(ranked),
182
- { label: 'final report' }
182
+ { label: 'final report', tier: 'medium' }
183
183
  )
184
184
 
185
185
  return { candidates, ranked, report }`;
@@ -199,13 +199,13 @@ const perspectives = (args && Array.isArray(args.perspectives) && args.perspecti
199
199
  phase('Perspectives')
200
200
  const views = await parallel(perspectives.map((p, i) => () =>
201
201
  agent('Evaluate this topic from the perspective of: ' + p + '. Be concrete and specific; note risks, trade-offs, and what matters most from this angle.\\n\\nTOPIC: ' + topic,
202
- { label: 'perspective ' + (i + 1), schema: { type: 'object', properties: { angle: { type: 'string' }, points: { type: 'array', items: { type: 'string' } }, risks: { type: 'array', items: { type: 'string' } } }, required: ['angle', 'points', 'risks'] } })
202
+ { label: 'perspective ' + (i + 1), tier: 'low', schema: { type: 'object', properties: { angle: { type: 'string' }, points: { type: 'array', items: { type: 'string' } }, risks: { type: 'array', items: { type: 'string' } } }, required: ['angle', 'points', 'risks'] } })
203
203
  ))
204
204
 
205
205
  phase('Synthesis')
206
206
  const synthesis = await agent(
207
207
  'Synthesize these perspectives into a balanced assessment. Identify where perspectives agree, where they conflict, and give a bottom-line recommendation with the key trade-offs.\\n\\nTOPIC: ' + topic + '\\n\\nPERSPECTIVES JSON:\\n' + JSON.stringify(views.filter(Boolean)),
208
- { label: 'synthesis' }
208
+ { label: 'synthesis', tier: 'medium' }
209
209
  )
210
210
 
211
211
  return { views: views.filter(Boolean), synthesis }`;
@@ -229,21 +229,21 @@ const map = await agent(
229
229
  'Explore the codebase at ' + root + ' and produce a structural map: top-level modules, entry points, ' +
230
230
  'data flow, and where complexity concentrates. Use the available file tools.\\n' +
231
231
  (focus ? 'Focus/checks requested: ' + focus + '\\n' : ''),
232
- { label: 'map', schema: { type: 'object', properties: { modules: { type: 'array', items: { type: 'object', properties: { name: { type: 'string' }, purpose: { type: 'string' }, risk: { type: 'string' } }, required: ['name', 'purpose'] } }, entryPoints: { type: 'array', items: { type: 'string' } }, complexityHotspots: { type: 'array', items: { type: 'string' } } }, required: ['modules'] } }
232
+ { label: 'map', tier: 'medium', schema: { type: 'object', properties: { modules: { type: 'array', items: { type: 'object', properties: { name: { type: 'string' }, purpose: { type: 'string' }, risk: { type: 'string' } }, required: ['name', 'purpose'] } }, entryPoints: { type: 'array', items: { type: 'string' } }, complexityHotspots: { type: 'array', items: { type: 'string' } } }, required: ['modules'] } }
233
233
  )
234
234
 
235
235
  phase('Audit')
236
236
  const audits = await parallel([
237
- () => agent('Audit this codebase for correctness risks: error handling, boundary conditions, concurrency, security. Use file tools.\\n\\nSTRUCTURE:\\n' + JSON.stringify(map), { label: 'correctness', schema: { type: 'object', properties: { findings: { type: 'array', items: { type: 'string' } }, severity: { type: 'string' } }, required: ['findings'] } }),
238
- () => agent('Audit this codebase for maintainability: coupling, duplication, naming, test coverage, documentation. Use file tools.\\n\\nSTRUCTURE:\\n' + JSON.stringify(map), { label: 'maintainability', schema: { type: 'object', properties: { findings: { type: 'array', items: { type: 'string' } }, severity: { type: 'string' } }, required: ['findings'] } }),
239
- () => agent('Audit this codebase for operational health: dependencies, build, deployment, observability, secrets handling. Use file tools.\\n\\nSTRUCTURE:\\n' + JSON.stringify(map), { label: 'operations', schema: { type: 'object', properties: { findings: { type: 'array', items: { type: 'string' } }, severity: { type: 'string' } }, required: ['findings'] } }),
237
+ () => agent('Audit this codebase for correctness risks: error handling, boundary conditions, concurrency, security. Use file tools.\\n\\nSTRUCTURE:\\n' + JSON.stringify(map), { label: 'correctness', tier: 'low', schema: { type: 'object', properties: { findings: { type: 'array', items: { type: 'string' } }, severity: { type: 'string' } }, required: ['findings'] } }),
238
+ () => agent('Audit this codebase for maintainability: coupling, duplication, naming, test coverage, documentation. Use file tools.\\n\\nSTRUCTURE:\\n' + JSON.stringify(map), { label: 'maintainability', tier: 'low', schema: { type: 'object', properties: { findings: { type: 'array', items: { type: 'string' } }, severity: { type: 'string' } }, required: ['findings'] } }),
239
+ () => agent('Audit this codebase for operational health: dependencies, build, deployment, observability, secrets handling. Use file tools.\\n\\nSTRUCTURE:\\n' + JSON.stringify(map), { label: 'operations', tier: 'low', schema: { type: 'object', properties: { findings: { type: 'array', items: { type: 'string' } }, severity: { type: 'string' } }, required: ['findings'] } }),
240
240
  ])
241
241
 
242
242
  phase('Report')
243
243
  const report = await agent(
244
244
  'Write a codebase health audit report: overall health score, ranked findings across dimensions with concrete locations, ' +
245
245
  'and a prioritized remediation roadmap.\\n\\nSTRUCTURE:\\n' + JSON.stringify(map) + '\\n\\nAUDITS JSON:\\n' + JSON.stringify(audits.filter(Boolean)),
246
- { label: 'audit report' }
246
+ { label: 'audit report', tier: 'medium' }
247
247
  )
248
248
 
249
249
  return { map, audits: audits.filter(Boolean), report }`;
@@ -29,7 +29,7 @@ export interface CapabilityEntry {
29
29
  }
30
30
 
31
31
  /** The contract format version. Bump on any breaking surface change. */
32
- export const WORKFLOW_CAPABILITY_CONTRACT_VERSION = "1.0.0";
32
+ export const WORKFLOW_CAPABILITY_CONTRACT_VERSION = "2.0.0";
33
33
 
34
34
  /**
35
35
  * The runtime globals a script can rely on. `options` documents each helper's
@@ -44,8 +44,12 @@ export const WORKFLOW_CAPABILITIES: readonly CapabilityEntry[] = [
44
44
  { name: "label", kind: "string", optional: true, default: "derived from phase and call count" },
45
45
  { name: "phase", kind: "string", optional: true, default: "current phase" },
46
46
  { name: "schema", kind: "plain JSON Schema", optional: true, default: "none" },
47
- { name: "model", kind: "string", optional: true, default: "pi-subagents model resolver" },
48
- { name: "tier", kind: "small|medium|large", optional: true, default: "pi-subagents workflow tier" },
47
+ {
48
+ name: "tier",
49
+ kind: "agent tier key",
50
+ optional: true,
51
+ default: "the agent's own tier, else agentTiers.defaultTier",
52
+ },
49
53
  { name: "isolation", kind: '"worktree"', optional: true, default: "pi-subagents worktree policy" },
50
54
  { name: "thread", kind: "string", optional: true, default: "fresh session per call" },
51
55
  { name: "toolset", kind: "string", optional: true, default: "configured host/agent toolset hint" },
@@ -58,8 +62,11 @@ export const WORKFLOW_CAPABILITIES: readonly CapabilityEntry[] = [
58
62
  "recoverable failures return null after retries; nonrecoverable failures throw",
59
63
  "schema noncompliance after bounded repair attempts is nonrecoverable",
60
64
  "resume replays only the longest unchanged prefix; the first miss and every later call execute live",
65
+ "replayed calls do not consume the real-dispatch maxAgents or token/phase budgets",
61
66
  "spawnKeys rotate by generation on resume so pi-subagents never raises a fingerprint conflict",
62
- "explicit model is resolved and scope-checked by pi-subagents; unavailable selectors fail closed",
67
+ "tier names a key in the host's agentTiers catalogue; a key it does not define is rejected before dispatch",
68
+ "resolveAgentTier in pi-subagents owns model, thinking, clamping, availability, and the resolution snapshot",
69
+ "there is no per-call model or thinking: a tier is the only model policy a workflow can express",
63
70
  "same-thread calls must be sequential and preserve a stable workflow thread name",
64
71
  "threads cannot be combined with worktree isolation",
65
72
  ],
@@ -72,8 +79,9 @@ export const WORKFLOW_CAPABILITIES: readonly CapabilityEntry[] = [
72
79
  constraints: [
73
80
  "requires functions rather than promises",
74
81
  "result order matches input order",
75
- "recoverable thunk failures become null; nonrecoverable failures throw",
82
+ "recoverable thunk failures become null; nonrecoverable failures throw after the batch barrier settles",
76
83
  "a breached agent cap cancels only its own batch",
84
+ "accepts at most 4096 thunks per call",
77
85
  ],
78
86
  },
79
87
  {
@@ -85,6 +93,28 @@ export const WORKFLOW_CAPABILITIES: readonly CapabilityEntry[] = [
85
93
  "items run concurrently while stages per item run sequentially",
86
94
  "each stage receives previousValue, originalItem, and zero-based index",
87
95
  "a null stage result is passed to the next stage",
96
+ "accepts at most 4096 items per call and awaits the whole batch before surfacing fatal errors",
97
+ ],
98
+ },
99
+ {
100
+ name: "orchestrate",
101
+ classification: "runtime-global",
102
+ signature: "orchestrate(tasks, options?) => Promise<{ results, tasks }>",
103
+ options: [
104
+ {
105
+ name: "onError",
106
+ kind: '"skip-dependents"|"continue"|"fail-fast"',
107
+ optional: true,
108
+ default: '"skip-dependents"',
109
+ },
110
+ ],
111
+ constraints: [
112
+ "task ids and dependencies are validated before any callback runs",
113
+ "ready tasks run in declaration-order layers with a barrier between layers",
114
+ "task context carries detached named results and statuses",
115
+ "ordinary task failures can retry up to three times; fatal workflow errors propagate",
116
+ "failed or skipped dependencies skip descendants by default; continue runs them with null values",
117
+ "the graph accepts at most 128 tasks and emits task start/retry/end/skip runtime events",
88
118
  ],
89
119
  },
90
120
  {
@@ -93,9 +123,9 @@ export const WORKFLOW_CAPABILITIES: readonly CapabilityEntry[] = [
93
123
  signature: "workflow(savedName, childArgs?) => Promise<unknown>",
94
124
  options: [],
95
125
  constraints: [
96
- "one nested level",
126
+ "one nested level; concurrent sibling nested calls are allowed",
97
127
  "shares limiter, counters, token accounting, and store",
98
- "nested runs get unique runIds via nestedCallSeq",
128
+ "nested calls use call-index/generation namespaces and replay as one parent result",
99
129
  ],
100
130
  },
101
131
  {
@@ -271,6 +301,7 @@ export const WORKFLOW_CAPABILITIES: readonly CapabilityEntry[] = [
271
301
  options: [],
272
302
  constraints: [
273
303
  "resumes a prior incomplete run with an edited script",
304
+ "original args and execution limits are reused on resume",
274
305
  "unchanged positional calls replay from cache until the first changed call",
275
306
  "always runs in the background",
276
307
  ],
@@ -283,6 +314,7 @@ export const WORKFLOW_CAPABILITIES: readonly CapabilityEntry[] = [
283
314
  constraints: [
284
315
  "must be the first statement",
285
316
  "name and description must be nonblank literals",
317
+ "workflow meta and phase metadata cannot contain model or thinking fields",
286
318
  "spread, computed keys, methods, template interpolation, and __proto__/constructor/prototype are rejected",
287
319
  ],
288
320
  },