@osolmaz/pi-workflows 0.9.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/README.md +64 -19
  2. package/dist/builtins/autodoc.workflow.d.ts +58 -0
  3. package/dist/builtins/autodoc.workflow.js +266 -0
  4. package/dist/builtins/autodoc.workflow.js.map +1 -0
  5. package/dist/builtins/autoimplement.workflow.d.ts +353 -0
  6. package/dist/builtins/autoimplement.workflow.js +961 -0
  7. package/dist/builtins/autoimplement.workflow.js.map +1 -0
  8. package/dist/builtins/autoplan.workflow.d.ts +58 -0
  9. package/dist/builtins/autoplan.workflow.js +190 -0
  10. package/dist/builtins/autoplan.workflow.js.map +1 -0
  11. package/dist/builtins/catalog.js +9 -1
  12. package/dist/builtins/catalog.js.map +1 -1
  13. package/dist/builtins/index.d.ts +6 -0
  14. package/dist/builtins/index.js +7 -0
  15. package/dist/builtins/index.js.map +1 -0
  16. package/dist/builtins/monitor.workflow.d.ts +29 -3
  17. package/dist/builtins/monitor.workflow.js +325 -13
  18. package/dist/builtins/monitor.workflow.js.map +1 -1
  19. package/dist/builtins/plan-approval.workflow.d.ts +83 -0
  20. package/dist/builtins/plan-approval.workflow.js +148 -0
  21. package/dist/builtins/plan-approval.workflow.js.map +1 -0
  22. package/dist/builtins/plan-presentation.d.ts +7 -0
  23. package/dist/builtins/plan-presentation.js +44 -0
  24. package/dist/builtins/plan-presentation.js.map +1 -0
  25. package/dist/extension/decision-channels.d.ts +132 -0
  26. package/dist/extension/decision-channels.js +1082 -0
  27. package/dist/extension/decision-channels.js.map +1 -0
  28. package/dist/extension/index.d.ts +1 -0
  29. package/dist/extension/index.js +426 -11
  30. package/dist/extension/index.js.map +1 -1
  31. package/dist/extension/widget.js +26 -2
  32. package/dist/extension/widget.js.map +1 -1
  33. package/dist/extension/workflow-tool.d.ts +1 -37
  34. package/dist/extension/workflow-tool.js +1 -42
  35. package/dist/extension/workflow-tool.js.map +1 -1
  36. package/dist/host/rpc-bridge.js +4 -21
  37. package/dist/host/rpc-bridge.js.map +1 -1
  38. package/dist/render/graph-render.js +46 -10
  39. package/dist/render/graph-render.js.map +1 -1
  40. package/dist/viewer/render.js +36 -2
  41. package/dist/viewer/render.js.map +1 -1
  42. package/dist/workflows/catalog.d.ts +1 -0
  43. package/dist/workflows/catalog.js +6 -0
  44. package/dist/workflows/catalog.js.map +1 -1
  45. package/dist/workflows/composition.d.ts +45 -0
  46. package/dist/workflows/composition.js +485 -0
  47. package/dist/workflows/composition.js.map +1 -0
  48. package/dist/workflows/decision-presentation.d.ts +18 -0
  49. package/dist/workflows/decision-presentation.js +417 -0
  50. package/dist/workflows/decision-presentation.js.map +1 -0
  51. package/dist/workflows/decision.d.ts +11 -5
  52. package/dist/workflows/decision.js.map +1 -1
  53. package/dist/workflows/definition.d.ts +22 -3
  54. package/dist/workflows/definition.js +46 -3
  55. package/dist/workflows/definition.js.map +1 -1
  56. package/dist/workflows/engine.d.ts +2 -1
  57. package/dist/workflows/engine.js +199 -18
  58. package/dist/workflows/engine.js.map +1 -1
  59. package/dist/workflows/graph.js +8 -6
  60. package/dist/workflows/graph.js.map +1 -1
  61. package/dist/workflows/human-decision.d.ts +87 -0
  62. package/dist/workflows/human-decision.js +583 -0
  63. package/dist/workflows/human-decision.js.map +1 -0
  64. package/dist/workflows/index.d.ts +5 -2
  65. package/dist/workflows/index.js +4 -1
  66. package/dist/workflows/index.js.map +1 -1
  67. package/dist/workflows/loader.d.ts +5 -4
  68. package/dist/workflows/loader.js +118 -18
  69. package/dist/workflows/loader.js.map +1 -1
  70. package/dist/workflows/schema.d.ts +3 -1
  71. package/dist/workflows/schema.js +64 -2
  72. package/dist/workflows/schema.js.map +1 -1
  73. package/dist/workflows/store.js +39 -2
  74. package/dist/workflows/store.js.map +1 -1
  75. package/dist/workflows/tool-input.d.ts +72 -0
  76. package/dist/workflows/tool-input.js +141 -0
  77. package/dist/workflows/tool-input.js.map +1 -0
  78. package/dist/workflows/types.d.ts +300 -2
  79. package/docs/CONTROLLERS.md +1 -1
  80. package/docs/DESIGN_PHILOSOPHY.md +1 -1
  81. package/docs/HUMAN_DECISIONS.md +371 -0
  82. package/docs/HUMAN_DECISION_PRESENTATIONS.md +323 -0
  83. package/docs/MONITOR.md +46 -18
  84. package/docs/WORKFLOW_COMPOSITION.md +333 -0
  85. package/docs/plans/2026-08-19-human-decision-gates-plan.md +296 -0
  86. package/docs/plans/2026-08-19-human-decision-presentations-plan.md +172 -0
  87. package/docs/plans/2026-08-19-provider-compatible-workflow-tool-schema-plan.md +45 -0
  88. package/docs/plans/2026-08-19-workflow-composition-plan.md +308 -0
  89. package/docs/run-bundles.md +45 -10
  90. package/docs/workflows.md +106 -15
  91. package/examples/workflows/approved-plan.workflow.ts +58 -0
  92. package/examples/workflows/autoimplement.workflow.ts +1 -92
  93. package/examples/workflows/autoplan.workflow.ts +1 -0
  94. package/examples/workflows/human-decision.workflow.ts +62 -0
  95. package/herdr-plugin.toml +1 -1
  96. package/package.json +6 -1
  97. package/schemas/decision-presentation-v1.schema.json +83 -0
  98. package/schemas/human-decision-accepted-v1.schema.json +44 -0
  99. package/schemas/human-decision-accepted-v2.schema.json +50 -0
  100. package/schemas/human-decision-answer-attempt-v1.schema.json +37 -0
  101. package/schemas/human-decision-cancellation-v1.schema.json +15 -0
  102. package/schemas/human-decision-continuation-v1.schema.json +16 -0
  103. package/schemas/human-decision-delivery-v1.schema.json +28 -0
  104. package/schemas/human-decision-delivery-v2.schema.json +36 -0
  105. package/schemas/human-decision-receipt-v1.schema.json +33 -0
  106. package/schemas/human-decision-receipt-v2.schema.json +39 -0
  107. package/schemas/human-decision-request-v1.schema.json +59 -0
  108. package/schemas/human-decision-request-v2.schema.json +69 -0
  109. package/schemas/human-decision-resolution-v1.schema.json +27 -0
  110. package/schemas/human-decision-resolution-v2.schema.json +27 -0
  111. package/schemas/human-decision-settlement-v1.schema.json +28 -0
  112. package/skills/autodoc/SKILL.md +43 -0
  113. package/skills/autoimplement/SKILL.md +58 -0
  114. package/skills/autoimplement/agents/openai.yaml +4 -0
  115. package/skills/autoplan/SKILL.md +25 -0
  116. package/skills/monitor/SKILL.md +8 -1
  117. package/skills/pi-workflows/SKILL.md +5 -3
  118. package/src/builtins/autodoc.workflow.ts +325 -0
  119. package/src/builtins/autoimplement.workflow.ts +1123 -0
  120. package/src/builtins/autoplan.workflow.ts +231 -0
  121. package/src/builtins/catalog.ts +9 -1
  122. package/src/builtins/index.ts +32 -0
  123. package/src/builtins/monitor.workflow.ts +396 -15
  124. package/src/builtins/plan-approval.workflow.ts +190 -0
  125. package/src/builtins/plan-presentation.ts +57 -0
  126. package/src/extension/decision-channels.ts +1533 -0
  127. package/src/extension/index.ts +499 -9
  128. package/src/extension/widget.ts +39 -2
  129. package/src/extension/workflow-tool.ts +5 -81
  130. package/src/host/rpc-bridge.ts +7 -30
  131. package/src/render/graph-render.ts +54 -5
  132. package/src/viewer/render.ts +56 -3
  133. package/src/workflows/catalog.ts +7 -0
  134. package/src/workflows/composition.ts +642 -0
  135. package/src/workflows/decision-presentation.ts +501 -0
  136. package/src/workflows/decision.ts +12 -5
  137. package/src/workflows/definition.ts +118 -8
  138. package/src/workflows/engine.ts +257 -20
  139. package/src/workflows/graph.ts +8 -6
  140. package/src/workflows/human-decision.ts +819 -0
  141. package/src/workflows/index.ts +89 -0
  142. package/src/workflows/loader.ts +186 -18
  143. package/src/workflows/schema.ts +79 -2
  144. package/src/workflows/store.ts +45 -2
  145. package/src/workflows/tool-input.ts +202 -0
  146. package/src/workflows/types.ts +374 -2
  147. package/examples/workflows/elegant-solution.workflow.ts +0 -95
@@ -1,4 +1,13 @@
1
- import { action, agent, compute, defineWorkflow, notify, shell } from "../workflows/definition.js";
1
+ import {
2
+ action,
3
+ agent,
4
+ compute,
5
+ defineWorkflow,
6
+ includeWorkflow,
7
+ includedResult,
8
+ notify,
9
+ shell,
10
+ } from "../workflows/definition.js";
2
11
  import {
3
12
  estimateProgress,
4
13
  formatProgressReport,
@@ -11,6 +20,10 @@ import type {
11
20
  WorkflowProgressData,
12
21
  } from "../workflows/types.js";
13
22
  import { validateProgressData } from "../workflows/updates.js";
23
+ import autodocWorkflow, { type AutodocInput } from "./autodoc.workflow.js";
24
+ import autoimplementWorkflow, { type AutoimplementInput } from "./autoimplement.workflow.js";
25
+ import autoplanWorkflow from "./autoplan.workflow.js";
26
+ import planApprovalWorkflow, { type PlanApprovalInput } from "./plan-approval.workflow.js";
14
27
 
15
28
  const MIN_INTERVAL_MINUTES = 1;
16
29
  const MAX_INTERVAL_MINUTES = 24 * 60;
@@ -27,22 +40,49 @@ const MAX_REASON_CHARS = 2_000;
27
40
  const SLEEP_TIMEOUT_MARGIN_MS = 60_000;
28
41
  const NODE_TIMEOUT_MARGIN_MS = 2 * 60_000;
29
42
 
43
+ export type MonitorRepairPolicy = {
44
+ authorized: true;
45
+ scope?: string;
46
+ constraints?: string[];
47
+ repository?: string;
48
+ baseBranch?: string;
49
+ merge?: boolean;
50
+ approval?: {
51
+ audience: string;
52
+ maxReplans: number;
53
+ };
54
+ };
55
+
30
56
  export type MonitorInput = {
31
57
  task: string;
32
58
  everyMinutes?: number;
33
59
  stopWhen?: string;
34
60
  maxChecks?: number;
35
61
  checkTimeoutMinutes?: number;
62
+ repair?: MonitorRepairPolicy;
36
63
  };
37
64
 
38
- type MonitorConfig = Required<MonitorInput>;
39
- type MonitorRoute = "continue" | "stop";
65
+ type MonitorConfig = {
66
+ task: string;
67
+ everyMinutes: number;
68
+ stopWhen: string;
69
+ maxChecks: number;
70
+ checkTimeoutMinutes: number;
71
+ repair?: MonitorRepairPolicy;
72
+ };
73
+ type MonitorRoute = "continue" | "repair" | "stop";
40
74
  type MonitorTrack = { key: string; data: WorkflowProgressData };
75
+ type MonitorRepairRequest = {
76
+ problem: string;
77
+ evidence: unknown;
78
+ issueFingerprint: string;
79
+ };
41
80
  type MonitorCheck = {
42
81
  route: MonitorRoute;
43
82
  observation: string;
44
83
  report: string;
45
84
  progress?: { tracks: MonitorTrack[] };
85
+ repair?: MonitorRepairRequest;
46
86
  reason: string;
47
87
  };
48
88
  type MonitorEstimate = { tracks: ProgressTrackState[] };
@@ -79,7 +119,14 @@ async function waitForUpdateSlot(signal: AbortSignal): Promise<void> {
79
119
 
80
120
  export function prepareMonitorInput(input: unknown): MonitorConfig {
81
121
  const value = requireRecord(input, "monitor input") as Partial<MonitorInput>;
82
- const allowed = new Set(["task", "everyMinutes", "stopWhen", "maxChecks", "checkTimeoutMinutes"]);
122
+ const allowed = new Set([
123
+ "task",
124
+ "everyMinutes",
125
+ "stopWhen",
126
+ "maxChecks",
127
+ "checkTimeoutMinutes",
128
+ "repair",
129
+ ]);
83
130
  for (const field of Object.keys(value)) {
84
131
  if (!allowed.has(field)) throw new Error(`monitor input field ${field} is not supported`);
85
132
  }
@@ -109,6 +156,51 @@ export function prepareMonitorInput(input: unknown): MonitorConfig {
109
156
  `checkTimeoutMinutes must be an integer from ${MIN_CHECK_TIMEOUT_MINUTES} through ${MAX_CHECK_TIMEOUT_MINUTES}`,
110
157
  );
111
158
  }
159
+ let repair: MonitorRepairPolicy | undefined;
160
+ if (value.repair !== undefined) {
161
+ const raw = requireRecord(value.repair, "repair policy");
162
+ if (raw.authorized !== true) throw new Error("repair policy must set authorized to true");
163
+ if (
164
+ raw.constraints !== undefined &&
165
+ (!Array.isArray(raw.constraints) || raw.constraints.some((item) => typeof item !== "string"))
166
+ ) {
167
+ throw new Error("repair constraints must be an array of strings");
168
+ }
169
+ if (raw.merge !== undefined && typeof raw.merge !== "boolean") {
170
+ throw new Error("repair merge must be a boolean");
171
+ }
172
+ let approval: MonitorRepairPolicy["approval"];
173
+ if (raw.approval !== undefined) {
174
+ const value = requireRecord(raw.approval, "repair approval");
175
+ const maxReplans = value.maxReplans ?? 3;
176
+ if (
177
+ !Number.isInteger(maxReplans) ||
178
+ (maxReplans as number) < 1 ||
179
+ (maxReplans as number) > 20
180
+ ) {
181
+ throw new Error("repair approval maxReplans must be from 1 through 20");
182
+ }
183
+ approval = {
184
+ audience: requireBoundedString(value.audience, "repair approval audience", 200),
185
+ maxReplans: maxReplans as number,
186
+ };
187
+ }
188
+ repair = {
189
+ authorized: true,
190
+ ...(raw.scope !== undefined
191
+ ? { scope: requireBoundedString(raw.scope, "repair scope", 4_000) }
192
+ : {}),
193
+ ...(raw.constraints !== undefined ? { constraints: [...raw.constraints] as string[] } : {}),
194
+ ...(raw.repository !== undefined
195
+ ? { repository: requireBoundedString(raw.repository, "repair repository", 4_000) }
196
+ : {}),
197
+ ...(raw.baseBranch !== undefined
198
+ ? { baseBranch: requireBoundedString(raw.baseBranch, "repair base branch", 256) }
199
+ : {}),
200
+ ...(raw.merge !== undefined ? { merge: raw.merge !== false } : {}),
201
+ ...(approval !== undefined ? { approval } : {}),
202
+ };
203
+ }
112
204
  return {
113
205
  task,
114
206
  everyMinutes,
@@ -118,6 +210,7 @@ export function prepareMonitorInput(input: unknown): MonitorConfig {
118
210
  : requireBoundedString(value.stopWhen, "stopWhen", 4_000),
119
211
  maxChecks,
120
212
  checkTimeoutMinutes,
213
+ ...(repair !== undefined ? { repair } : {}),
121
214
  };
122
215
  }
123
216
 
@@ -130,13 +223,17 @@ function completedChecks(context: WorkflowNodeContext): number {
130
223
  .length;
131
224
  }
132
225
 
133
- export function validateMonitorCheck(output: unknown): MonitorCheck {
226
+ export function validateMonitorCheck(output: unknown, repairAuthorized = false): MonitorCheck {
134
227
  const value = requireRecord(output, "monitor check output");
135
- const allowed = new Set(["route", "observation", "report", "progress", "reason"]);
228
+ const allowed = new Set(["route", "observation", "report", "progress", "repair", "reason"]);
136
229
  for (const key of Object.keys(value))
137
230
  if (!allowed.has(key)) throw new Error(`monitor check field ${key} is not supported`);
138
- if (value.route !== "continue" && value.route !== "stop")
139
- throw new Error("route must be continue or stop");
231
+ if (value.route !== "continue" && value.route !== "repair" && value.route !== "stop") {
232
+ throw new Error("route must be continue, repair, or stop");
233
+ }
234
+ if (value.route === "repair" && !repairAuthorized) {
235
+ throw new Error("route repair requires explicit monitor repair authorization");
236
+ }
140
237
  const check: MonitorCheck = {
141
238
  route: value.route,
142
239
  observation: requireBoundedString(value.observation, "observation", MAX_OBSERVATION_CHARS),
@@ -144,6 +241,21 @@ export function validateMonitorCheck(output: unknown): MonitorCheck {
144
241
  reason: requireBoundedString(value.reason, "reason", MAX_REASON_CHARS),
145
242
  };
146
243
  if (value.progress !== undefined) check.progress = validateMonitorProgress(value.progress);
244
+ if (value.repair !== undefined) {
245
+ const repair = requireRecord(value.repair, "monitor repair request");
246
+ check.repair = {
247
+ problem: requireBoundedString(repair.problem, "repair problem", 8_000),
248
+ evidence: repair.evidence ?? null,
249
+ issueFingerprint: requireBoundedString(
250
+ repair.issueFingerprint,
251
+ "repair issue fingerprint",
252
+ 256,
253
+ ),
254
+ };
255
+ }
256
+ if (value.route === "repair" && check.repair === undefined) {
257
+ throw new Error("route repair requires repair details");
258
+ }
147
259
  return check;
148
260
  }
149
261
 
@@ -190,6 +302,88 @@ function estimateTracks(outputs: Record<string, unknown>): MonitorEstimate {
190
302
  };
191
303
  }
192
304
 
305
+ function repeatedRepairWithoutProgress(context: WorkflowNodeContext): boolean {
306
+ const current = context.outputs.check as MonitorCheck;
307
+ const fingerprint = current.repair?.issueFingerprint;
308
+ if (fingerprint === undefined) return false;
309
+ const steps = context.state.steps;
310
+ const currentCheckIndex = steps.findLastIndex((step) => step.nodeId === "check");
311
+ for (let index = currentCheckIndex - 1; index >= 0; index -= 1) {
312
+ const step = steps[index];
313
+ if (step?.nodeId !== "check") continue;
314
+ const prior = step.output as MonitorCheck;
315
+ if (prior.repair?.issueFingerprint !== fingerprint) continue;
316
+ return steps
317
+ .slice(index + 1, currentCheckIndex)
318
+ .some((candidate) => candidate.nodeId === "implementation");
319
+ }
320
+ return false;
321
+ }
322
+
323
+ function currentRepairPlan(outputs: Record<string, unknown>): {
324
+ plan: unknown;
325
+ planDigest: string;
326
+ documents: string[];
327
+ } {
328
+ const documentation = outputs.documentation as
329
+ | {
330
+ exit?: string;
331
+ output?: {
332
+ plan?: unknown;
333
+ planDigest?: string;
334
+ documentation?: { files?: string[] };
335
+ };
336
+ }
337
+ | undefined;
338
+ if (
339
+ documentation?.exit === "ready" &&
340
+ documentation.output?.plan !== undefined &&
341
+ typeof documentation.output.planDigest === "string"
342
+ ) {
343
+ return {
344
+ plan: documentation.output.plan,
345
+ planDigest: documentation.output.planDigest,
346
+ documents: documentation.output.documentation?.files ?? [],
347
+ };
348
+ }
349
+ const design = includedResult(autoplanWorkflow, outputs.initialDesign);
350
+ if (design.exit !== "ready") throw new Error("monitor design did not return a ready plan");
351
+ return { plan: design.output.plan, planDigest: design.output.planDigest, documents: [] };
352
+ }
353
+
354
+ function latestReplanInstructions(outputs: Record<string, unknown>): string | undefined {
355
+ const approval = outputs.approval as
356
+ | { exit?: string; output?: { instructions?: unknown } }
357
+ | undefined;
358
+ return approval?.exit === "replan" && typeof approval.output?.instructions === "string"
359
+ ? approval.output.instructions
360
+ : undefined;
361
+ }
362
+
363
+ function repairBlockedReason(outputs: Record<string, unknown>): string {
364
+ const guard = outputs.repairGuard as { reason?: string; route?: string } | undefined;
365
+ if (guard?.route === "blocked" && guard.reason !== undefined) return guard.reason;
366
+ const replan = outputs.replanGuard as { reason?: string; route?: string } | undefined;
367
+ if (replan?.route === "blocked" && replan.reason !== undefined) return replan.reason;
368
+ const approval = outputs.approval as { exit?: string; output?: { status?: string } } | undefined;
369
+ if (approval?.exit === "stop") return "The operator stopped the proposed repair.";
370
+ const documentation = outputs.documentation as
371
+ | { exit?: string; output?: { reason?: string } }
372
+ | undefined;
373
+ const implementation = outputs.implementation as
374
+ | { exit?: string; output?: { reason?: string } }
375
+ | undefined;
376
+ const design = outputs.initialDesign as
377
+ | { exit?: string; output?: { reason?: string } }
378
+ | undefined;
379
+ return (
380
+ implementation?.output?.reason ??
381
+ documentation?.output?.reason ??
382
+ design?.output?.reason ??
383
+ "The repair did not produce new verified progress."
384
+ );
385
+ }
386
+
193
387
  function reportMessage(context: WorkflowNodeContext): string {
194
388
  const check = context.outputs.check as MonitorCheck;
195
389
  const estimate = context.outputs.estimate as MonitorEstimate;
@@ -219,7 +413,10 @@ function reportMessage(context: WorkflowNodeContext): string {
219
413
  }
220
414
 
221
415
  const monitorWorkflow: WorkflowDefinition = defineWorkflow({
416
+ source: import.meta.url,
417
+ contractId: "pi-workflows.monitor.v1",
222
418
  name: "monitor",
419
+ input: prepareMonitorInput,
223
420
  title: ({ input }) => {
224
421
  try {
225
422
  return `monitor: ${prepareMonitorInput(input).task.slice(0, 80)}`;
@@ -228,7 +425,104 @@ const monitorWorkflow: WorkflowDefinition = defineWorkflow({
228
425
  }
229
426
  },
230
427
  startAt: "prepare",
231
- maxSteps: 9_010,
428
+ maxSteps: 200_000,
429
+ includes: {
430
+ initialDesign: includeWorkflow({
431
+ workflow: "autoplan",
432
+ contract: autoplanWorkflow,
433
+ input: ({ outputs }) => {
434
+ const config = configFrom(outputs);
435
+ const repair = (outputs.check as MonitorCheck).repair;
436
+ if (repair === undefined) throw new Error("monitor repair details are missing");
437
+ const prior = outputs.initialDesign as
438
+ | { exit?: string; output?: { plan?: unknown } }
439
+ | undefined;
440
+ const instructions = latestReplanInstructions(outputs);
441
+ return {
442
+ problem: repair.problem,
443
+ ...(config.repair?.scope !== undefined ? { scope: config.repair.scope } : {}),
444
+ ...(config.repair?.constraints !== undefined
445
+ ? { constraints: config.repair.constraints }
446
+ : {}),
447
+ ...(prior?.exit === "ready" && prior.output?.plan !== undefined
448
+ ? { previousPlan: prior.output.plan }
449
+ : {}),
450
+ newEvidence:
451
+ instructions === undefined
452
+ ? repair.evidence
453
+ : { observedEvidence: repair.evidence, operatorInstructions: instructions },
454
+ };
455
+ },
456
+ }),
457
+ documentation: includeWorkflow(autodocWorkflow, {
458
+ input: ({ outputs }): AutodocInput => {
459
+ const config = configFrom(outputs);
460
+ const repair = (outputs.check as MonitorCheck).repair;
461
+ const design = includedResult(autoplanWorkflow, outputs.initialDesign);
462
+ if (design.exit !== "ready") throw new Error("monitor design did not return a ready plan");
463
+ if (repair === undefined) throw new Error("monitor repair details are missing");
464
+ return {
465
+ task: repair.problem,
466
+ plan: design.output.plan,
467
+ ...(config.repair?.repository !== undefined
468
+ ? { repository: config.repair.repository }
469
+ : {}),
470
+ evidence: latestReplanInstructions(outputs) ?? repair.evidence,
471
+ };
472
+ },
473
+ }),
474
+ approval: includeWorkflow(planApprovalWorkflow, {
475
+ input: ({ outputs, state }): PlanApprovalInput => {
476
+ const config = configFrom(outputs);
477
+ const repair = (outputs.check as MonitorCheck).repair;
478
+ const approval = config.repair?.approval;
479
+ if (repair === undefined || approval === undefined) {
480
+ throw new Error("monitor approval was entered without an approval policy");
481
+ }
482
+ const current = currentRepairPlan(outputs);
483
+ const revision =
484
+ state.steps.filter((step) => step.nodeId === "approval/approve").length + 1;
485
+ return {
486
+ task: repair.problem,
487
+ plan: current.plan,
488
+ planDigest: current.planDigest,
489
+ audience: approval.audience,
490
+ revision,
491
+ };
492
+ },
493
+ }),
494
+ implementation: includeWorkflow({
495
+ workflow: "autoimplement",
496
+ contract: autoimplementWorkflow,
497
+ input: ({ outputs }) => {
498
+ const config = configFrom(outputs);
499
+ const repair = (outputs.check as MonitorCheck).repair;
500
+ const documented = currentRepairPlan(outputs);
501
+ if (repair === undefined) throw new Error("monitor repair details are missing");
502
+ const request: AutoimplementInput = {
503
+ task: repair.problem,
504
+ plan: documented.plan,
505
+ documentation: {
506
+ status: "current",
507
+ planDigest: documented.planDigest,
508
+ documents: documented.documents,
509
+ },
510
+ ...(config.repair?.scope !== undefined ? { scope: config.repair.scope } : {}),
511
+ ...(config.repair?.constraints !== undefined
512
+ ? { constraints: config.repair.constraints }
513
+ : {}),
514
+ ...(config.repair?.repository !== undefined
515
+ ? { repository: config.repair.repository }
516
+ : {}),
517
+ ...(config.repair?.baseBranch !== undefined
518
+ ? { baseBranch: config.repair.baseBranch }
519
+ : {}),
520
+ merge: config.repair?.merge === true,
521
+ };
522
+ return request;
523
+ },
524
+ }),
525
+ },
232
526
  nodes: {
233
527
  prepare: compute({ run: ({ input }) => prepareMonitorInput(input) }),
234
528
  check: agent({
@@ -248,15 +542,21 @@ const monitorWorkflow: WorkflowDefinition = defineWorkflow({
248
542
  priorEstimate?.tracks.length
249
543
  ? `Previous progress: ${formatProgressReport(priorEstimate.tracks.map((track) => track.estimate))}`
250
544
  : "There is no previous measured progress.",
251
- "Use available tools to inspect the current source of truth. Observe only unless the task explicitly authorizes a mutation.",
545
+ config.repair === undefined
546
+ ? "Observe only. This monitor has no mutation authorization."
547
+ : "Repair is explicitly authorized within the supplied repair policy. Choose repair only for a concrete issue that can be changed within that scope. Include a stable issue fingerprint based on the issue and observed target state. Do not change protected model, benchmark, credential, hardware, spending, or scope decisions.",
548
+ "Use available tools to inspect the current source of truth.",
252
549
  "You are the regular Pi model running this check and the observation adapter. When useful measurable facts appear during the check, publish them with workflow action update. Include the latest tracks in the final submission. Do not require the monitored target to implement a Pi-specific progress API, file, store, schema, or command.",
253
550
  "Every accepted check must include a concise user-facing report. Add progress tracks only when the target provides measurable facts. Submit observed counts and target-provided finish times; do not invent rates or an ETA.",
254
- "Choose route continue or stop.",
551
+ config.repair === undefined
552
+ ? "Choose route continue or stop."
553
+ : "Choose route continue, repair, or stop.",
255
554
  ].join("\n\n");
256
555
  },
257
556
  expectedOutput:
258
- '{ "route": "continue" | "stop", "observation": "current factual state", "report": "concise status update", "progress": { "tracks": [{ "key": "stable-key", "data": { "schema": "pi-workflows.progress.v1", "status": "running", "completed": 1, "total": 2, "unit": "items" } }] } (optional), "reason": "short reason" }',
259
- validate: (output) => validateMonitorCheck(output),
557
+ '{ "route": "continue" | "repair" | "stop", "observation": "current factual state", "report": "concise status update", "progress": { "tracks": [{ "key": "stable-key", "data": { "schema": "pi-workflows.progress.v1", "status": "running", "completed": 1, "total": 2, "unit": "items" } }] } (optional), "repair": { "problem": "fixable issue", "evidence": "observed evidence", "issueFingerprint": "stable issue and target-state fingerprint" } (required for repair), "reason": "short reason" }',
558
+ validate: (output, context) =>
559
+ validateMonitorCheck(output, configFrom(context.outputs).repair !== undefined),
260
560
  }),
261
561
  estimate: compute({ run: ({ outputs }) => estimateTracks(outputs) }),
262
562
  publish_progress: action({
@@ -289,9 +589,55 @@ const monitorWorkflow: WorkflowDefinition = defineWorkflow({
289
589
  checks,
290
590
  };
291
591
  }
592
+ if (check.route === "repair") return { route: "repair", reason: check.reason, checks };
292
593
  return { route: "continue", reason: check.reason, checks };
293
594
  },
294
595
  }),
596
+ repairGuard: compute({
597
+ run: (context) =>
598
+ repeatedRepairWithoutProgress(context)
599
+ ? {
600
+ route: "blocked",
601
+ reason:
602
+ "The same issue returned after a completed repair with no changed target evidence.",
603
+ }
604
+ : { route: "repair", reason: "The issue is new or has changed evidence." },
605
+ }),
606
+ approvalRoute: compute({
607
+ run: ({ outputs }) => ({
608
+ route: configFrom(outputs).repair?.approval === undefined ? "implement" : "approve",
609
+ }),
610
+ }),
611
+ replanGuard: compute({
612
+ run: (context) => {
613
+ const limit = configFrom(context.outputs).repair?.approval?.maxReplans ?? 3;
614
+ const replans = context.state.steps.filter(
615
+ (step) => step.nodeId === "approval/replan",
616
+ ).length;
617
+ return replans > limit
618
+ ? {
619
+ route: "blocked",
620
+ reason: `Monitor repair reached the ${limit}-replan safety limit.`,
621
+ }
622
+ : { route: "design", replans, limit };
623
+ },
624
+ }),
625
+ repairBlocked: compute({
626
+ run: (context) => ({
627
+ reason: repairBlockedReason(context.outputs),
628
+ observation: (context.outputs.check as MonitorCheck).observation,
629
+ checks: completedChecks(context),
630
+ reported: true,
631
+ }),
632
+ }),
633
+ repairReport: notify({
634
+ statusDetail: "reporting blocked monitor repair",
635
+ kind: "final",
636
+ message: ({ outputs }) => {
637
+ const result = outputs.repairBlocked as { reason: string };
638
+ return `Automatic repair stopped: ${result.reason}`;
639
+ },
640
+ }),
295
641
  schedule: action({
296
642
  statusDetail: "scheduling next monitor check",
297
643
  run: async ({ outputs, publishUpdate }) => {
@@ -331,11 +677,17 @@ const monitorWorkflow: WorkflowDefinition = defineWorkflow({
331
677
  run: ({ outputs }) => {
332
678
  const check = outputs.check as MonitorCheck;
333
679
  const decision = outputs.decide as { reason?: string; checks?: number } | undefined;
680
+ const repair = outputs.repairBlocked as { reason?: string } | undefined;
334
681
  return {
335
- reason: decision?.reason ?? check.reason,
682
+ reason: repair?.reason ?? decision?.reason ?? check.reason,
336
683
  observation: check.observation,
337
684
  checks: decision?.checks ?? 1,
338
685
  reported: true,
686
+ ...(outputs.implementation !== undefined
687
+ ? { repair: outputs.implementation }
688
+ : repair !== undefined
689
+ ? { repair }
690
+ : {}),
339
691
  };
340
692
  },
341
693
  }),
@@ -346,7 +698,36 @@ const monitorWorkflow: WorkflowDefinition = defineWorkflow({
346
698
  { from: "estimate", to: "publish_progress" },
347
699
  { from: "publish_progress", to: "report" },
348
700
  { from: "report", to: "decide" },
349
- { from: "decide", switch: { on: "$.route", cases: { stop: "finish", continue: "schedule" } } },
701
+ {
702
+ from: "decide",
703
+ switch: {
704
+ on: "$.route",
705
+ cases: { stop: "finish", continue: "schedule", repair: "repairGuard" },
706
+ },
707
+ },
708
+ {
709
+ from: "repairGuard",
710
+ switch: { on: "$.route", cases: { repair: "initialDesign", blocked: "repairBlocked" } },
711
+ },
712
+ { from: "initialDesign.ready", to: "documentation" },
713
+ { from: "initialDesign.blocked", to: "repairBlocked" },
714
+ { from: "documentation.ready", to: "approvalRoute" },
715
+ { from: "documentation.blocked", to: "repairBlocked" },
716
+ {
717
+ from: "approvalRoute",
718
+ switch: { on: "$.route", cases: { approve: "approval", implement: "implementation" } },
719
+ },
720
+ { from: "approval.continue", to: "implementation" },
721
+ { from: "approval.stop", to: "repairBlocked" },
722
+ { from: "approval.replan", to: "replanGuard" },
723
+ {
724
+ from: "replanGuard",
725
+ switch: { on: "$.route", cases: { design: "initialDesign", blocked: "repairBlocked" } },
726
+ },
727
+ { from: "implementation.completed", to: "check" },
728
+ { from: "implementation.blocked", to: "repairBlocked" },
729
+ { from: "repairBlocked", to: "repairReport" },
730
+ { from: "repairReport", to: "finish" },
350
731
  { from: "schedule", to: "sleep" },
351
732
  { from: "sleep", to: "check" },
352
733
  ],