@osolmaz/pi-workflows 0.12.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 (257) hide show
  1. package/README.md +56 -31
  2. package/dist/builtins/autodoc.workflow.d.ts +4 -4
  3. package/dist/builtins/autoimplement.workflow.d.ts +797 -142
  4. package/dist/builtins/autoimplement.workflow.js +17 -105
  5. package/dist/builtins/autoimplement.workflow.js.map +1 -1
  6. package/dist/builtins/autoplan.workflow.d.ts +126 -13
  7. package/dist/builtins/autoplan.workflow.js +200 -29
  8. package/dist/builtins/autoplan.workflow.js.map +1 -1
  9. package/dist/builtins/catalog.js +7 -5
  10. package/dist/builtins/catalog.js.map +1 -1
  11. package/dist/builtins/index.d.ts +4 -2
  12. package/dist/builtins/index.js +3 -1
  13. package/dist/builtins/index.js.map +1 -1
  14. package/dist/builtins/monitor.workflow.d.ts +56 -23
  15. package/dist/builtins/monitor.workflow.js +508 -329
  16. package/dist/builtins/monitor.workflow.js.map +1 -1
  17. package/dist/builtins/pi-agent-group.d.ts +72 -0
  18. package/dist/builtins/pi-agent-group.js +1087 -0
  19. package/dist/builtins/pi-agent-group.js.map +1 -0
  20. package/dist/builtins/plain-summary.workflow.d.ts +32 -0
  21. package/dist/builtins/plain-summary.workflow.js +134 -0
  22. package/dist/builtins/plain-summary.workflow.js.map +1 -0
  23. package/dist/builtins/plan-approval.workflow.d.ts +39 -5
  24. package/dist/builtins/plan-approval.workflow.js +92 -14
  25. package/dist/builtins/plan-approval.workflow.js.map +1 -1
  26. package/dist/builtins/plan-change.workflow.d.ts +449 -0
  27. package/dist/builtins/plan-change.workflow.js +256 -0
  28. package/dist/builtins/plan-change.workflow.js.map +1 -0
  29. package/dist/builtins/plan-presentation.js +2 -2
  30. package/dist/builtins/plan-presentation.js.map +1 -1
  31. package/dist/builtins/sanity-check.workflow.d.ts +50 -6
  32. package/dist/builtins/sanity-check.workflow.js +150 -28
  33. package/dist/builtins/sanity-check.workflow.js.map +1 -1
  34. package/dist/controllers/effects.d.ts +3 -2
  35. package/dist/controllers/effects.js +8 -1
  36. package/dist/controllers/effects.js.map +1 -1
  37. package/dist/controllers/index.d.ts +1 -1
  38. package/dist/controllers/index.js +1 -1
  39. package/dist/controllers/index.js.map +1 -1
  40. package/dist/controllers/manager.d.ts +1 -0
  41. package/dist/controllers/manager.js +41 -17
  42. package/dist/controllers/manager.js.map +1 -1
  43. package/dist/controllers/sqlite.d.ts +107 -85
  44. package/dist/controllers/sqlite.js +1611 -1486
  45. package/dist/controllers/sqlite.js.map +1 -1
  46. package/dist/controllers/store.d.ts +9 -8
  47. package/dist/controllers/store.js +1 -31
  48. package/dist/controllers/store.js.map +1 -1
  49. package/dist/controllers/types.d.ts +3 -0
  50. package/dist/controllers/workflow-engine-scheduler.d.ts +1 -1
  51. package/dist/controllers/workflow-engine-scheduler.js +3 -5
  52. package/dist/controllers/workflow-engine-scheduler.js.map +1 -1
  53. package/dist/controllers/workflows.d.ts +2 -2
  54. package/dist/controllers/workflows.js +11 -10
  55. package/dist/controllers/workflows.js.map +1 -1
  56. package/dist/extension/controller-host.js +2 -2
  57. package/dist/extension/controller-host.js.map +1 -1
  58. package/dist/extension/decision-channels.d.ts +4 -2
  59. package/dist/extension/decision-channels.js +333 -146
  60. package/dist/extension/decision-channels.js.map +1 -1
  61. package/dist/extension/executor.d.ts +9 -2
  62. package/dist/extension/executor.js +89 -2
  63. package/dist/extension/executor.js.map +1 -1
  64. package/dist/extension/herdr-viewer.d.ts +0 -1
  65. package/dist/extension/herdr-viewer.js +0 -4
  66. package/dist/extension/herdr-viewer.js.map +1 -1
  67. package/dist/extension/index.js +209 -127
  68. package/dist/extension/index.js.map +1 -1
  69. package/dist/extension/recorder.d.ts +3 -10
  70. package/dist/extension/recorder.js +13 -22
  71. package/dist/extension/recorder.js.map +1 -1
  72. package/dist/extension/session-events.d.ts +2 -2
  73. package/dist/extension/step-message.d.ts +7 -2
  74. package/dist/extension/step-message.js +94 -2
  75. package/dist/extension/step-message.js.map +1 -1
  76. package/dist/extension/widget.js +29 -3
  77. package/dist/extension/widget.js.map +1 -1
  78. package/dist/host/rpc-executor.d.ts +3 -0
  79. package/dist/host/rpc-executor.js +2 -0
  80. package/dist/host/rpc-executor.js.map +1 -1
  81. package/dist/host/runner.d.ts +3 -6
  82. package/dist/host/runner.js +83 -32
  83. package/dist/host/runner.js.map +1 -1
  84. package/dist/render/graph-render.js +15 -10
  85. package/dist/render/graph-render.js.map +1 -1
  86. package/dist/state/database.d.ts +35 -0
  87. package/dist/state/database.js +287 -0
  88. package/dist/state/database.js.map +1 -0
  89. package/dist/state/index.d.ts +4 -0
  90. package/dist/state/index.js +5 -0
  91. package/dist/state/index.js.map +1 -0
  92. package/dist/state/json.d.ts +6 -0
  93. package/dist/state/json.js +38 -0
  94. package/dist/state/json.js.map +1 -0
  95. package/dist/state/mutation.d.ts +57 -0
  96. package/dist/state/mutation.js +255 -0
  97. package/dist/state/mutation.js.map +1 -0
  98. package/dist/state/schema.d.ts +6 -0
  99. package/dist/state/schema.js +473 -0
  100. package/dist/state/schema.js.map +1 -0
  101. package/dist/viewer/cli.d.ts +2 -2
  102. package/dist/viewer/cli.js +142 -122
  103. package/dist/viewer/cli.js.map +1 -1
  104. package/dist/viewer/render.d.ts +4 -4
  105. package/dist/viewer/render.js +9 -23
  106. package/dist/viewer/render.js.map +1 -1
  107. package/dist/viewer/session-reducer.d.ts +1 -1
  108. package/dist/viewer/session-reducer.js +1 -1
  109. package/dist/viewer/tui.d.ts +3 -3
  110. package/dist/viewer/tui.js +15 -13
  111. package/dist/viewer/tui.js.map +1 -1
  112. package/dist/viewer/watch.d.ts +2 -5
  113. package/dist/viewer/watch.js +13 -13
  114. package/dist/viewer/watch.js.map +1 -1
  115. package/dist/workflows/catalog.js +7 -2
  116. package/dist/workflows/catalog.js.map +1 -1
  117. package/dist/workflows/composition.js +20 -2
  118. package/dist/workflows/composition.js.map +1 -1
  119. package/dist/workflows/decision-presentation.d.ts +1 -1
  120. package/dist/workflows/decision-presentation.js +51 -38
  121. package/dist/workflows/decision-presentation.js.map +1 -1
  122. package/dist/workflows/definition.d.ts +6 -2
  123. package/dist/workflows/definition.js +17 -0
  124. package/dist/workflows/definition.js.map +1 -1
  125. package/dist/workflows/engine.d.ts +7 -7
  126. package/dist/workflows/engine.js +196 -115
  127. package/dist/workflows/engine.js.map +1 -1
  128. package/dist/workflows/errors.d.ts +16 -3
  129. package/dist/workflows/errors.js +18 -3
  130. package/dist/workflows/errors.js.map +1 -1
  131. package/dist/workflows/human-decision.d.ts +45 -8
  132. package/dist/workflows/human-decision.js +641 -274
  133. package/dist/workflows/human-decision.js.map +1 -1
  134. package/dist/workflows/index.d.ts +5 -6
  135. package/dist/workflows/index.js +4 -5
  136. package/dist/workflows/index.js.map +1 -1
  137. package/dist/workflows/progress.d.ts +1 -0
  138. package/dist/workflows/progress.js +15 -3
  139. package/dist/workflows/progress.js.map +1 -1
  140. package/dist/workflows/schema.js +27 -1
  141. package/dist/workflows/schema.js.map +1 -1
  142. package/dist/workflows/store.d.ts +83 -103
  143. package/dist/workflows/store.js +907 -951
  144. package/dist/workflows/store.js.map +1 -1
  145. package/dist/workflows/types.d.ts +88 -126
  146. package/docs/CONTROLLERS.md +10 -8
  147. package/docs/DEFERRED_TURNS.md +2 -2
  148. package/docs/DESIGN_PHILOSOPHY.md +4 -2
  149. package/docs/HUMAN_DECISIONS.md +36 -47
  150. package/docs/HUMAN_DECISION_PRESENTATIONS.md +14 -24
  151. package/docs/MONITOR.md +179 -237
  152. package/docs/SQLITE_STATE.md +219 -0
  153. package/docs/WORKFLOW_COMPOSITION.md +27 -26
  154. package/docs/WORKFLOW_STEP_MESSAGES.md +20 -18
  155. package/docs/WORKFLOW_UPDATES.md +10 -9
  156. package/docs/development.md +9 -9
  157. package/docs/live-replay-protocol.md +28 -37
  158. package/docs/plans/2026-08-16-workflow-updates-plan.md +1 -1
  159. package/docs/plans/2026-08-19-human-decision-gates-plan.md +1 -1
  160. package/docs/plans/2026-08-21-autoimplement-timeout-fallback-plan.md +1 -1
  161. package/docs/plans/2026-08-21-plan-change-approval-policy-plan.md +322 -0
  162. package/docs/plans/2026-08-21-sanity-check-plan.md +217 -95
  163. package/docs/plans/2026-08-22-goal-finishing-monitor-plan.md +200 -0
  164. package/docs/plans/2026-08-23-assistant-agent-completion-plan.md +701 -0
  165. package/docs/plans/2026-08-23-sqlite-state-plan.md +970 -0
  166. package/docs/plans/replayable-run-bundles-implementation-plan.md +2 -2
  167. package/docs/plans/session-event-replay-implementation-plan.md +1 -1
  168. package/docs/plans/tui-viewer-implementation-plan.md +1 -1
  169. package/docs/session-event-journal.md +63 -434
  170. package/docs/tui-viewer.md +13 -15
  171. package/docs/workflows.md +127 -53
  172. package/examples/workflows/approved-plan.workflow.ts +19 -46
  173. package/examples/workflows/plain-summary.workflow.ts +1 -0
  174. package/herdr-plugin.toml +1 -1
  175. package/package.json +7 -7
  176. package/plugins/herdr/viewer.mjs +1 -11
  177. package/schemas/human-decision-accepted-v1.schema.json +15 -3
  178. package/schemas/human-decision-continuation-v1.schema.json +10 -1
  179. package/schemas/human-decision-delivery-v1.schema.json +8 -0
  180. package/schemas/human-decision-receipt-v1.schema.json +8 -0
  181. package/schemas/human-decision-request-v1.schema.json +24 -4
  182. package/skills/autoimplement/SKILL.md +27 -0
  183. package/skills/autoplan/SKILL.md +5 -4
  184. package/skills/monitor/SKILL.md +91 -135
  185. package/skills/pi-workflows/SKILL.md +9 -8
  186. package/skills/sanity-check/SKILL.md +44 -0
  187. package/src/builtins/autoimplement.workflow.ts +19 -118
  188. package/src/builtins/autoplan.workflow.ts +290 -40
  189. package/src/builtins/catalog.ts +7 -5
  190. package/src/builtins/index.ts +18 -1
  191. package/src/builtins/monitor.workflow.ts +673 -382
  192. package/src/builtins/pi-agent-group.ts +1407 -0
  193. package/src/builtins/plain-summary.workflow.ts +185 -0
  194. package/src/builtins/plan-approval.workflow.ts +157 -24
  195. package/src/builtins/plan-change.workflow.ts +321 -0
  196. package/src/builtins/plan-presentation.ts +2 -2
  197. package/src/builtins/sanity-check.workflow.ts +248 -48
  198. package/src/controllers/effects.ts +7 -0
  199. package/src/controllers/index.ts +0 -6
  200. package/src/controllers/manager.ts +72 -32
  201. package/src/controllers/sqlite.ts +2420 -1948
  202. package/src/controllers/store.ts +17 -38
  203. package/src/controllers/types.ts +3 -0
  204. package/src/controllers/workflow-engine-scheduler.ts +4 -8
  205. package/src/controllers/workflows.ts +45 -21
  206. package/src/extension/controller-host.ts +1 -2
  207. package/src/extension/decision-channels.ts +456 -203
  208. package/src/extension/executor.ts +105 -2
  209. package/src/extension/herdr-viewer.ts +0 -5
  210. package/src/extension/index.ts +251 -151
  211. package/src/extension/recorder.ts +13 -22
  212. package/src/extension/session-events.ts +2 -2
  213. package/src/extension/step-message.ts +106 -4
  214. package/src/extension/widget.ts +32 -5
  215. package/src/host/rpc-executor.ts +4 -0
  216. package/src/host/runner.ts +93 -41
  217. package/src/render/graph-render.ts +12 -6
  218. package/src/state/database.ts +375 -0
  219. package/src/state/index.ts +29 -0
  220. package/src/state/json.ts +42 -0
  221. package/src/state/mutation.ts +435 -0
  222. package/src/state/schema.ts +475 -0
  223. package/src/viewer/cli.ts +151 -144
  224. package/src/viewer/render.ts +19 -30
  225. package/src/viewer/session-reducer.ts +1 -1
  226. package/src/viewer/tui.ts +18 -17
  227. package/src/viewer/watch.ts +13 -17
  228. package/src/workflows/catalog.ts +7 -2
  229. package/src/workflows/composition.ts +21 -3
  230. package/src/workflows/decision-presentation.ts +56 -43
  231. package/src/workflows/definition.ts +36 -3
  232. package/src/workflows/engine.ts +228 -130
  233. package/src/workflows/errors.ts +27 -3
  234. package/src/workflows/human-decision.ts +921 -349
  235. package/src/workflows/index.ts +20 -30
  236. package/src/workflows/progress.ts +18 -3
  237. package/src/workflows/schema.ts +36 -1
  238. package/src/workflows/store.ts +1300 -1163
  239. package/src/workflows/types.ts +100 -137
  240. package/dist/builtins/sanity-check-session.d.ts +0 -17
  241. package/dist/builtins/sanity-check-session.js +0 -168
  242. package/dist/builtins/sanity-check-session.js.map +0 -1
  243. package/dist/workflows/artifacts.d.ts +0 -40
  244. package/dist/workflows/artifacts.js +0 -155
  245. package/dist/workflows/artifacts.js.map +0 -1
  246. package/dist/workflows/migrate-sources.d.ts +0 -42
  247. package/dist/workflows/migrate-sources.js +0 -133
  248. package/dist/workflows/migrate-sources.js.map +0 -1
  249. package/docs/run-bundles.md +0 -481
  250. package/schemas/human-decision-accepted-v2.schema.json +0 -50
  251. package/schemas/human-decision-delivery-v2.schema.json +0 -36
  252. package/schemas/human-decision-receipt-v2.schema.json +0 -39
  253. package/schemas/human-decision-request-v2.schema.json +0 -69
  254. package/schemas/human-decision-resolution-v2.schema.json +0 -27
  255. package/src/builtins/sanity-check-session.ts +0 -205
  256. package/src/workflows/artifacts.ts +0 -188
  257. package/src/workflows/migrate-sources.ts +0 -178
@@ -1,22 +1,21 @@
1
1
  import { action, agent, compute, defineWorkflow, includeWorkflow, includedResult, notify, shell, } from "../workflows/definition.js";
2
2
  import { estimateProgress, formatProgressReport, } from "../workflows/progress.js";
3
3
  import { validateProgressData } from "../workflows/updates.js";
4
- import autodocWorkflow, {} from "./autodoc.workflow.js";
5
4
  import autoimplementWorkflow, {} from "./autoimplement.workflow.js";
6
- import autoplanWorkflow from "./autoplan.workflow.js";
7
- import planApprovalWorkflow, {} from "./plan-approval.workflow.js";
5
+ import { parsePlanApprovalPolicy, } from "./plan-approval.workflow.js";
6
+ import planChangeWorkflow, {} from "./plan-change.workflow.js";
8
7
  const MIN_INTERVAL_MINUTES = 1;
9
8
  const MAX_INTERVAL_MINUTES = 24 * 60;
10
9
  const DEFAULT_INTERVAL_MINUTES = 30;
11
- const MIN_CHECK_TIMEOUT_MINUTES = 5;
12
- const MAX_CHECK_TIMEOUT_MINUTES = 24 * 60;
13
- const DEFAULT_MIN_CHECK_TIMEOUT_MINUTES = 60;
10
+ const DEFAULT_CHECK_TIMEOUT_MINUTES = 60;
14
11
  const DEFAULT_MAX_CHECKS = 1_000;
15
12
  const MAX_CHECKS = 1_000;
16
13
  const MAX_TRACKS = 256;
17
14
  const MAX_OBSERVATION_CHARS = 8_000;
18
15
  const MAX_REPORT_CHARS = 4_000;
19
16
  const MAX_REASON_CHARS = 2_000;
17
+ const MAX_ACTION_TEXT_CHARS = 8_000;
18
+ const MAX_ID_CHARS = 256;
20
19
  const SLEEP_TIMEOUT_MARGIN_MS = 60_000;
21
20
  const NODE_TIMEOUT_MARGIN_MS = 2 * 60_000;
22
21
  function requireRecord(value, label) {
@@ -25,6 +24,11 @@ function requireRecord(value, label) {
25
24
  }
26
25
  return value;
27
26
  }
27
+ function requireExactKeys(value, allowed, label) {
28
+ const unexpected = Object.keys(value).find((key) => !allowed.includes(key));
29
+ if (unexpected !== undefined)
30
+ throw new Error(`${label} field ${unexpected} is not supported`);
31
+ }
28
32
  function requireBoundedString(value, label, maxChars) {
29
33
  if (typeof value !== "string")
30
34
  throw new Error(`${label} must be a string`);
@@ -35,6 +39,12 @@ function requireBoundedString(value, label, maxChars) {
35
39
  throw new Error(`${label} must be at most ${maxChars} characters`);
36
40
  return trimmed;
37
41
  }
42
+ function requireStringArray(value, label) {
43
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
44
+ throw new Error(`${label} must be an array of strings`);
45
+ }
46
+ return value.map((item, index) => requireBoundedString(item, `${label}[${index}]`, MAX_ACTION_TEXT_CHARS));
47
+ }
38
48
  async function waitForUpdateSlot(signal) {
39
49
  await new Promise((resolve, reject) => {
40
50
  const onAbort = () => {
@@ -53,18 +63,8 @@ async function waitForUpdateSlot(signal) {
53
63
  }
54
64
  export function prepareMonitorInput(input) {
55
65
  const value = requireRecord(input, "monitor input");
56
- const allowed = new Set([
57
- "task",
58
- "everyMinutes",
59
- "stopWhen",
60
- "maxChecks",
61
- "checkTimeoutMinutes",
62
- "repair",
63
- ]);
64
- for (const field of Object.keys(value)) {
65
- if (!allowed.has(field))
66
- throw new Error(`monitor input field ${field} is not supported`);
67
- }
66
+ const allowed = ["task", "everyMinutes", "stopWhen", "maxChecks"];
67
+ requireExactKeys(value, allowed, "monitor input");
68
68
  const task = requireBoundedString(value.task, "task", 8_000);
69
69
  const everyMinutes = value.everyMinutes ?? DEFAULT_INTERVAL_MINUTES;
70
70
  if (!Number.isInteger(everyMinutes) ||
@@ -76,54 +76,6 @@ export function prepareMonitorInput(input) {
76
76
  if (!Number.isInteger(maxChecks) || maxChecks <= 0 || maxChecks > MAX_CHECKS) {
77
77
  throw new Error(`maxChecks must be an integer from 1 through ${MAX_CHECKS}`);
78
78
  }
79
- const checkTimeoutMinutes = value.checkTimeoutMinutes ?? Math.max(DEFAULT_MIN_CHECK_TIMEOUT_MINUTES, everyMinutes);
80
- if (!Number.isInteger(checkTimeoutMinutes) ||
81
- checkTimeoutMinutes < MIN_CHECK_TIMEOUT_MINUTES ||
82
- checkTimeoutMinutes > MAX_CHECK_TIMEOUT_MINUTES) {
83
- throw new Error(`checkTimeoutMinutes must be an integer from ${MIN_CHECK_TIMEOUT_MINUTES} through ${MAX_CHECK_TIMEOUT_MINUTES}`);
84
- }
85
- let repair;
86
- if (value.repair !== undefined) {
87
- const raw = requireRecord(value.repair, "repair policy");
88
- if (raw.authorized !== true)
89
- throw new Error("repair policy must set authorized to true");
90
- if (raw.constraints !== undefined &&
91
- (!Array.isArray(raw.constraints) || raw.constraints.some((item) => typeof item !== "string"))) {
92
- throw new Error("repair constraints must be an array of strings");
93
- }
94
- if (raw.merge !== undefined && typeof raw.merge !== "boolean") {
95
- throw new Error("repair merge must be a boolean");
96
- }
97
- let approval;
98
- if (raw.approval !== undefined) {
99
- const value = requireRecord(raw.approval, "repair approval");
100
- const maxReplans = value.maxReplans ?? 3;
101
- if (!Number.isInteger(maxReplans) ||
102
- maxReplans < 1 ||
103
- maxReplans > 20) {
104
- throw new Error("repair approval maxReplans must be from 1 through 20");
105
- }
106
- approval = {
107
- audience: requireBoundedString(value.audience, "repair approval audience", 200),
108
- maxReplans: maxReplans,
109
- };
110
- }
111
- repair = {
112
- authorized: true,
113
- ...(raw.scope !== undefined
114
- ? { scope: requireBoundedString(raw.scope, "repair scope", 4_000) }
115
- : {}),
116
- ...(raw.constraints !== undefined ? { constraints: [...raw.constraints] } : {}),
117
- ...(raw.repository !== undefined
118
- ? { repository: requireBoundedString(raw.repository, "repair repository", 4_000) }
119
- : {}),
120
- ...(raw.baseBranch !== undefined
121
- ? { baseBranch: requireBoundedString(raw.baseBranch, "repair base branch", 256) }
122
- : {}),
123
- ...(raw.merge !== undefined ? { merge: raw.merge !== false } : {}),
124
- ...(approval !== undefined ? { approval } : {}),
125
- };
126
- }
127
79
  return {
128
80
  task,
129
81
  everyMinutes,
@@ -131,66 +83,243 @@ export function prepareMonitorInput(input) {
131
83
  ? "Stop only when the user explicitly asks to stop."
132
84
  : requireBoundedString(value.stopWhen, "stopWhen", 4_000),
133
85
  maxChecks,
134
- checkTimeoutMinutes,
135
- ...(repair !== undefined ? { repair } : {}),
136
86
  };
137
87
  }
138
88
  function configFrom(outputs) {
139
89
  return outputs.prepare;
140
90
  }
141
- function completedChecks(context) {
142
- return context.state.steps.filter((step) => step.nodeId === "check" && step.outcome === "ok")
91
+ function completedObservations(context) {
92
+ return context.state.steps.filter((step) => step.nodeId === "observe" && step.outcome === "ok")
143
93
  .length;
144
94
  }
145
- export function validateMonitorCheck(output, repairAuthorized = false) {
146
- const value = requireRecord(output, "monitor check output");
147
- const allowed = new Set(["route", "observation", "report", "progress", "repair", "reason"]);
148
- for (const key of Object.keys(value))
149
- if (!allowed.has(key))
150
- throw new Error(`monitor check field ${key} is not supported`);
151
- if (value.route !== "continue" && value.route !== "repair" && value.route !== "stop") {
152
- throw new Error("route must be continue, repair, or stop");
153
- }
154
- if (value.route === "repair" && !repairAuthorized) {
155
- throw new Error("route repair requires explicit monitor repair authorization");
156
- }
157
- const check = {
95
+ function parseAuthority(input) {
96
+ const value = requireRecord(input, "action authority");
97
+ requireExactKeys(value, [
98
+ "status",
99
+ "basis",
100
+ "allowedMutations",
101
+ "forbiddenMutations",
102
+ "costLimit",
103
+ "providerRuntime",
104
+ "requiredChecks",
105
+ "stopConditions",
106
+ "allowedRecoveryActions",
107
+ "repository",
108
+ "baseBranch",
109
+ "merge",
110
+ "repairApproval",
111
+ ], "action authority");
112
+ if (value.status !== "authorized" && value.status !== "outside") {
113
+ throw new Error("action authority status must be authorized or outside");
114
+ }
115
+ if (typeof value.merge !== "boolean")
116
+ throw new Error("action authority merge must be boolean");
117
+ const approval = value.repairApproval === undefined
118
+ ? undefined
119
+ : requireRecord(value.repairApproval, "repair approval");
120
+ return {
121
+ status: value.status,
122
+ basis: requireBoundedString(value.basis, "action authority basis", MAX_ACTION_TEXT_CHARS),
123
+ allowedMutations: requireStringArray(value.allowedMutations, "action authority allowedMutations"),
124
+ forbiddenMutations: requireStringArray(value.forbiddenMutations, "action authority forbiddenMutations"),
125
+ costLimit: requireBoundedString(value.costLimit, "action authority costLimit", MAX_ACTION_TEXT_CHARS),
126
+ providerRuntime: requireBoundedString(value.providerRuntime, "action authority providerRuntime", MAX_ACTION_TEXT_CHARS),
127
+ requiredChecks: requireStringArray(value.requiredChecks, "action authority requiredChecks"),
128
+ stopConditions: requireStringArray(value.stopConditions, "action authority stopConditions"),
129
+ allowedRecoveryActions: requireStringArray(value.allowedRecoveryActions, "action authority allowedRecoveryActions"),
130
+ ...(value.repository !== undefined
131
+ ? {
132
+ repository: requireBoundedString(value.repository, "action authority repository", 4_000),
133
+ }
134
+ : {}),
135
+ ...(value.baseBranch !== undefined
136
+ ? {
137
+ baseBranch: requireBoundedString(value.baseBranch, "action authority baseBranch", 256),
138
+ }
139
+ : {}),
140
+ merge: value.merge,
141
+ repairApproval: parsePlanApprovalPolicy(approval),
142
+ };
143
+ }
144
+ function parseCostSafety(input) {
145
+ const value = requireRecord(input, "action cost");
146
+ requireExactKeys(value, ["paidAction", "status", "evidence"], "action cost");
147
+ if (typeof value.paidAction !== "boolean")
148
+ throw new Error("action cost paidAction must be boolean");
149
+ if (value.status !== "not-applicable" &&
150
+ value.status !== "within-limit" &&
151
+ value.status !== "missing" &&
152
+ value.status !== "exceeded") {
153
+ throw new Error("action cost status is invalid");
154
+ }
155
+ return {
156
+ paidAction: value.paidAction,
157
+ status: value.status,
158
+ evidence: requireBoundedString(value.evidence, "action cost evidence", MAX_ACTION_TEXT_CHARS),
159
+ };
160
+ }
161
+ function parseDefectSafety(input) {
162
+ const value = requireRecord(input, "action defect");
163
+ requireExactKeys(value, ["sharedCodeOrDataDefect", "paidWorkers", "evidence"], "action defect");
164
+ if (typeof value.sharedCodeOrDataDefect !== "boolean") {
165
+ throw new Error("action defect sharedCodeOrDataDefect must be boolean");
166
+ }
167
+ if (value.paidWorkers !== "not-applicable" &&
168
+ value.paidWorkers !== "stopped" &&
169
+ value.paidWorkers !== "running") {
170
+ throw new Error("action defect paidWorkers is invalid");
171
+ }
172
+ return {
173
+ sharedCodeOrDataDefect: value.sharedCodeOrDataDefect,
174
+ paidWorkers: value.paidWorkers,
175
+ evidence: requireBoundedString(value.evidence, "action defect evidence", MAX_ACTION_TEXT_CHARS),
176
+ };
177
+ }
178
+ function parseActionRequest(input) {
179
+ const value = requireRecord(input, "monitor action request");
180
+ requireExactKeys(value, [
181
+ "kind",
182
+ "incomplete",
183
+ "evidence",
184
+ "nextAction",
185
+ "authority",
186
+ "cost",
187
+ "defect",
188
+ "verification",
189
+ "failureId",
190
+ "targetStateId",
191
+ ], "monitor action request");
192
+ if (value.kind !== "advance" && value.kind !== "recover" && value.kind !== "repair") {
193
+ throw new Error("monitor action kind must be advance, recover, or repair");
194
+ }
195
+ const request = {
196
+ kind: value.kind,
197
+ incomplete: requireBoundedString(value.incomplete, "monitor action incomplete work", MAX_ACTION_TEXT_CHARS),
198
+ evidence: value.evidence ?? null,
199
+ nextAction: requireBoundedString(value.nextAction, "monitor action nextAction", MAX_ACTION_TEXT_CHARS),
200
+ authority: parseAuthority(value.authority),
201
+ cost: parseCostSafety(value.cost),
202
+ defect: parseDefectSafety(value.defect),
203
+ verification: requireBoundedString(value.verification, "monitor action verification", MAX_ACTION_TEXT_CHARS),
204
+ failureId: requireBoundedString(value.failureId, "monitor action failureId", MAX_ID_CHARS),
205
+ targetStateId: requireBoundedString(value.targetStateId, "monitor action targetStateId", MAX_ID_CHARS),
206
+ };
207
+ if (request.authority.status !== "authorized") {
208
+ throw new Error("route act requires action authority status authorized");
209
+ }
210
+ if (request.authority.allowedMutations.length === 0) {
211
+ throw new Error("route act requires at least one allowed mutation");
212
+ }
213
+ if (request.cost.paidAction &&
214
+ (request.cost.status === "missing" || request.cost.status === "exceeded")) {
215
+ throw new Error("route act cannot launch paid work without verified remaining authority");
216
+ }
217
+ if (request.cost.paidAction && request.cost.status !== "within-limit") {
218
+ throw new Error("paid route act requires cost status within-limit");
219
+ }
220
+ if (!request.cost.paidAction && request.cost.status !== "not-applicable") {
221
+ throw new Error("unpaid route act requires cost status not-applicable");
222
+ }
223
+ if (request.kind === "repair" &&
224
+ request.defect.sharedCodeOrDataDefect &&
225
+ request.defect.paidWorkers === "running") {
226
+ throw new Error("paid workers must stop before a shared code or data repair");
227
+ }
228
+ return request;
229
+ }
230
+ export function validateMonitorObservation(output) {
231
+ const value = requireRecord(output, "monitor observation output");
232
+ requireExactKeys(value, [
233
+ "route",
234
+ "goalState",
235
+ "workState",
236
+ "observation",
237
+ "report",
238
+ "targetStateId",
239
+ "authorizedActions",
240
+ "progress",
241
+ "action",
242
+ "reason",
243
+ ], "monitor observation");
244
+ if (value.route !== "wait" && value.route !== "act" && value.route !== "stop") {
245
+ throw new Error("route must be wait, act, or stop");
246
+ }
247
+ if (value.goalState !== "complete" &&
248
+ value.goalState !== "incomplete" &&
249
+ value.goalState !== "blocked") {
250
+ throw new Error("goalState must be complete, incomplete, or blocked");
251
+ }
252
+ if (value.workState !== "running" &&
253
+ value.workState !== "waiting" &&
254
+ value.workState !== "idle" &&
255
+ value.workState !== "failed" &&
256
+ value.workState !== "stopped" &&
257
+ value.workState !== "unknown") {
258
+ throw new Error("workState is invalid");
259
+ }
260
+ const observation = {
158
261
  route: value.route,
262
+ goalState: value.goalState,
263
+ workState: value.workState,
159
264
  observation: requireBoundedString(value.observation, "observation", MAX_OBSERVATION_CHARS),
160
265
  report: requireBoundedString(value.report, "report", MAX_REPORT_CHARS),
266
+ targetStateId: requireBoundedString(value.targetStateId, "targetStateId", MAX_ID_CHARS),
267
+ authorizedActions: requireStringArray(value.authorizedActions, "authorizedActions"),
161
268
  reason: requireBoundedString(value.reason, "reason", MAX_REASON_CHARS),
162
269
  };
163
270
  if (value.progress !== undefined)
164
- check.progress = validateMonitorProgress(value.progress);
165
- if (value.repair !== undefined) {
166
- const repair = requireRecord(value.repair, "monitor repair request");
167
- check.repair = {
168
- problem: requireBoundedString(repair.problem, "repair problem", 8_000),
169
- evidence: repair.evidence ?? null,
170
- issueFingerprint: requireBoundedString(repair.issueFingerprint, "repair issue fingerprint", 256),
171
- };
271
+ observation.progress = validateMonitorProgress(value.progress);
272
+ if (value.action !== undefined)
273
+ observation.action = parseActionRequest(value.action);
274
+ if (observation.route === "act" && observation.action === undefined) {
275
+ throw new Error("route act requires action details");
276
+ }
277
+ if (observation.action !== undefined &&
278
+ observation.action.targetStateId !== observation.targetStateId) {
279
+ throw new Error("monitor action targetStateId must match the observed targetStateId");
280
+ }
281
+ if (observation.route !== "act" && observation.action !== undefined) {
282
+ throw new Error("action details are only valid for route act");
283
+ }
284
+ if (observation.route === "act" && observation.goalState !== "incomplete") {
285
+ throw new Error("route act requires goalState incomplete");
172
286
  }
173
- if (value.route === "repair" && check.repair === undefined) {
174
- throw new Error("route repair requires repair details");
287
+ if (observation.route === "act" &&
288
+ observation.workState !== "idle" &&
289
+ observation.workState !== "failed" &&
290
+ observation.workState !== "stopped") {
291
+ throw new Error("route act requires idle, failed, or stopped work");
175
292
  }
176
- return check;
293
+ if (observation.route === "wait" && observation.goalState !== "incomplete") {
294
+ throw new Error("route wait requires goalState incomplete");
295
+ }
296
+ if (observation.route === "wait" &&
297
+ observation.workState !== "running" &&
298
+ observation.workState !== "waiting") {
299
+ throw new Error("route wait requires running work or an external wait");
300
+ }
301
+ if (observation.goalState === "complete" && observation.route !== "stop") {
302
+ throw new Error("goalState complete requires route stop");
303
+ }
304
+ if (observation.goalState === "blocked" && observation.route !== "stop") {
305
+ throw new Error("goalState blocked requires route stop");
306
+ }
307
+ return observation;
177
308
  }
178
309
  function validateMonitorProgress(input) {
179
310
  const value = requireRecord(input, "progress");
180
- if (Object.keys(value).some((key) => key !== "tracks"))
181
- throw new Error("progress only supports tracks");
311
+ requireExactKeys(value, ["tracks"], "progress");
182
312
  if (!Array.isArray(value.tracks) || value.tracks.length < 1 || value.tracks.length > MAX_TRACKS) {
183
313
  throw new Error(`progress.tracks must contain 1 through ${MAX_TRACKS} entries`);
184
314
  }
185
315
  const keys = new Set();
186
316
  const tracks = value.tracks.map((raw, index) => {
187
317
  const track = requireRecord(raw, `progress.tracks[${index}]`);
188
- if (Object.keys(track).some((key) => key !== "key" && key !== "data")) {
189
- throw new Error(`progress.tracks[${index}] has an unsupported field`);
190
- }
318
+ requireExactKeys(track, ["key", "data"], `progress.tracks[${index}]`);
191
319
  const key = requireBoundedString(track.key, `progress.tracks[${index}].key`, 128);
192
- if (!/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/.test(key))
320
+ if (!/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/.test(key)) {
193
321
  throw new Error(`progress.tracks[${index}].key is invalid`);
322
+ }
194
323
  if (keys.has(key))
195
324
  throw new Error(`progress track key ${key} is duplicated`);
196
325
  keys.add(key);
@@ -201,15 +330,18 @@ function validateMonitorProgress(input) {
201
330
  });
202
331
  return { tracks };
203
332
  }
333
+ function currentObservation(outputs) {
334
+ return (outputs.guard ?? outputs.observe);
335
+ }
204
336
  function estimateTracks(outputs) {
205
- const check = outputs.check;
206
- if (check.progress === undefined)
337
+ const observation = currentObservation(outputs);
338
+ if (observation.progress === undefined)
207
339
  return { tracks: [] };
208
340
  const previous = outputs.estimate;
209
341
  const previousByKey = new Map((previous?.tracks ?? []).map((track) => [track.key, track]));
210
342
  const at = new Date().toISOString();
211
343
  return {
212
- tracks: check.progress.tracks.map((track) => {
344
+ tracks: observation.progress.tracks.map((track) => {
213
345
  const samples = [
214
346
  ...(previousByKey.get(track.key)?.samples ?? []),
215
347
  { at, data: track.data },
@@ -218,85 +350,175 @@ function estimateTracks(outputs) {
218
350
  }),
219
351
  };
220
352
  }
221
- function repeatedRepairWithoutProgress(context) {
222
- const current = context.outputs.check;
223
- const fingerprint = current.repair?.issueFingerprint;
224
- if (fingerprint === undefined)
353
+ function actionFrom(outputs) {
354
+ const request = currentObservation(outputs).action;
355
+ if (request === undefined)
356
+ throw new Error("monitor action details are missing");
357
+ return request;
358
+ }
359
+ function repeatedCompletedRepair(context) {
360
+ const action = context.outputs.observe.action;
361
+ if (action?.kind !== "repair")
225
362
  return false;
226
- const steps = context.state.steps;
227
- const currentCheckIndex = steps.findLastIndex((step) => step.nodeId === "check");
228
- for (let index = currentCheckIndex - 1; index >= 0; index -= 1) {
229
- const step = steps[index];
230
- if (step?.nodeId !== "check")
363
+ const currentIndex = context.state.steps.findLastIndex((step) => step.nodeId === "observe");
364
+ for (let index = currentIndex - 1; index >= 0; index -= 1) {
365
+ const step = context.state.steps[index];
366
+ if (step?.nodeId !== "observe")
231
367
  continue;
232
368
  const prior = step.output;
233
- if (prior.repair?.issueFingerprint !== fingerprint)
369
+ if (prior.action?.kind !== "repair" ||
370
+ prior.action.failureId !== action.failureId ||
371
+ prior.action.targetStateId !== action.targetStateId) {
234
372
  continue;
235
- return steps
236
- .slice(index + 1, currentCheckIndex)
237
- .some((candidate) => candidate.nodeId === "implementation");
373
+ }
374
+ return context.state.steps
375
+ .slice(index + 1, currentIndex)
376
+ .some((candidate) => candidate.nodeId === "repairComplete" && candidate.outcome === "ok");
238
377
  }
239
378
  return false;
240
379
  }
241
- function currentRepairPlan(outputs) {
242
- const documentation = outputs.documentation;
243
- if (documentation?.exit === "ready" &&
244
- documentation.output?.plan !== undefined &&
245
- typeof documentation.output.planDigest === "string") {
246
- return {
247
- plan: documentation.output.plan,
248
- planDigest: documentation.output.planDigest,
249
- documents: documentation.output.documentation?.files ?? [],
250
- };
380
+ function guardObservation(context) {
381
+ const observation = context.outputs.observe;
382
+ if (!repeatedCompletedRepair(context))
383
+ return observation;
384
+ const action = observation.action;
385
+ if (action === undefined)
386
+ return observation;
387
+ return {
388
+ route: "stop",
389
+ goalState: "blocked",
390
+ workState: observation.workState,
391
+ observation: observation.observation,
392
+ report: "The same failure and target state returned after one completed repair.",
393
+ targetStateId: observation.targetStateId,
394
+ authorizedActions: observation.authorizedActions,
395
+ ...(observation.progress !== undefined ? { progress: observation.progress } : {}),
396
+ reason: `Repair stopped because ${action.failureId} returned in target state ${action.targetStateId}.`,
397
+ };
398
+ }
399
+ export function validateMonitorActionResult(output, expected) {
400
+ const value = requireRecord(output, "monitor action result");
401
+ requireExactKeys(value, ["status", "summary", "evidence", "verification", "failureId", "targetStateId"], "monitor action result");
402
+ if (value.status !== "succeeded" && value.status !== "failed" && value.status !== "blocked") {
403
+ throw new Error("monitor action result status must be succeeded, failed, or blocked");
404
+ }
405
+ const result = {
406
+ status: value.status,
407
+ summary: requireBoundedString(value.summary, "monitor action result summary", MAX_REPORT_CHARS),
408
+ evidence: value.evidence ?? null,
409
+ verification: requireBoundedString(value.verification, "monitor action result verification", MAX_ACTION_TEXT_CHARS),
410
+ failureId: requireBoundedString(value.failureId, "monitor action result failureId", MAX_ID_CHARS),
411
+ targetStateId: requireBoundedString(value.targetStateId, "monitor action result targetStateId", MAX_ID_CHARS),
412
+ };
413
+ if (result.failureId !== expected.failureId || result.targetStateId !== expected.targetStateId) {
414
+ throw new Error("monitor action result must preserve the requested failure and target-state IDs");
251
415
  }
252
- const design = includedResult(autoplanWorkflow, outputs.initialDesign);
253
- if (design.exit !== "ready")
254
- throw new Error("monitor design did not return a ready plan");
255
- return { plan: design.output.plan, planDigest: design.output.planDigest, documents: [] };
416
+ return result;
417
+ }
418
+ function currentRepairPlan(outputs) {
419
+ const result = includedResult(planChangeWorkflow, outputs.planChange);
420
+ if (result.exit !== "ready")
421
+ throw new Error("monitor plan change did not return a ready plan");
422
+ return {
423
+ plan: result.output.plan,
424
+ planDigest: result.output.planDigest,
425
+ documents: result.output.documents,
426
+ };
427
+ }
428
+ function authorityScope(actionRequest) {
429
+ const authority = actionRequest.authority;
430
+ return [
431
+ `Authorization basis: ${authority.basis}`,
432
+ `Allowed mutations: ${authority.allowedMutations.join("; ")}`,
433
+ `Forbidden mutations: ${authority.forbiddenMutations.join("; ") || "none recorded"}`,
434
+ `Cost limit: ${authority.costLimit}`,
435
+ `Provider/runtime contract: ${authority.providerRuntime}`,
436
+ `Allowed recovery actions: ${authority.allowedRecoveryActions.join("; ") || "none recorded"}`,
437
+ ].join("\n");
256
438
  }
257
- function latestReplanInstructions(outputs) {
258
- const approval = outputs.approval;
259
- return approval?.exit === "replan" && typeof approval.output?.instructions === "string"
260
- ? approval.output.instructions
261
- : undefined;
439
+ function authorityConstraints(actionRequest) {
440
+ const authority = actionRequest.authority;
441
+ return [
442
+ ...authority.forbiddenMutations.map((item) => `Forbidden: ${item}`),
443
+ `Cost limit: ${authority.costLimit}`,
444
+ `Provider/runtime contract: ${authority.providerRuntime}`,
445
+ ...authority.requiredChecks.map((item) => `Required check: ${item}`),
446
+ ...authority.stopConditions.map((item) => `Stop condition: ${item}`),
447
+ `Repair verification: ${actionRequest.verification}`,
448
+ ];
262
449
  }
263
450
  function repairBlockedReason(outputs) {
264
- const guard = outputs.repairGuard;
265
- if (guard?.route === "blocked" && guard.reason !== undefined)
266
- return guard.reason;
267
- const replan = outputs.replanGuard;
268
- if (replan?.route === "blocked" && replan.reason !== undefined)
269
- return replan.reason;
270
- const approval = outputs.approval;
271
- if (approval?.exit === "stop")
272
- return "The operator stopped the proposed repair.";
273
- const documentation = outputs.documentation;
451
+ const planChange = outputs.planChange;
274
452
  const implementation = outputs.implementation;
275
- const design = outputs.initialDesign;
276
453
  return (implementation?.output?.reason ??
277
- documentation?.output?.reason ??
278
- design?.output?.reason ??
279
- "The repair did not produce new verified progress.");
454
+ planChange?.output?.reason ??
455
+ "The repair did not produce a verified result.");
456
+ }
457
+ function recordedActionFromStep(step) {
458
+ if (step.outcome !== "ok")
459
+ return undefined;
460
+ if (step.nodeId === "act") {
461
+ const result = step.output;
462
+ return {
463
+ status: result.status,
464
+ summary: result.summary,
465
+ evidence: result.evidence,
466
+ verification: result.verification,
467
+ failureId: result.failureId,
468
+ targetStateId: result.targetStateId,
469
+ };
470
+ }
471
+ if (step.nodeId === "repairComplete")
472
+ return step.output;
473
+ return undefined;
474
+ }
475
+ function latestRecordedAction(context) {
476
+ for (let index = context.state.steps.length - 1; index >= 0; index -= 1) {
477
+ const step = context.state.steps[index];
478
+ if (step === undefined)
479
+ continue;
480
+ const recorded = recordedActionFromStep(step);
481
+ if (recorded !== undefined)
482
+ return recorded;
483
+ }
484
+ return undefined;
280
485
  }
281
486
  function reportMessage(context) {
282
- const check = context.outputs.check;
487
+ const observation = currentObservation(context.outputs);
283
488
  const estimate = context.outputs.estimate;
284
489
  const config = configFrom(context.outputs);
285
- const suffix = [];
286
- if (estimate.tracks.length > 0) {
287
- suffix.push(formatProgressReport(estimate.tracks.map((track) => track.estimate), check.route === "continue" ? config.everyMinutes : undefined, new Date(), 2_000));
288
- }
289
- if (check.route === "continue" && completedChecks(context) >= config.maxChecks) {
290
- suffix.push(`Reached the ${config.maxChecks}-check safety limit.`);
291
- }
292
- const suffixText = suffix.filter(Boolean).join("\n");
293
- if (suffixText.length === 0)
294
- return check.report;
295
- const reportBudget = Math.max(1, MAX_REPORT_CHARS - suffixText.length - 1);
296
- const report = check.report.length <= reportBudget
297
- ? check.report
298
- : `${check.report.slice(0, Math.max(0, reportBudget - 1))}…`;
299
- return `${report}\n${suffixText}`;
490
+ const progress = estimate.tracks.length === 0
491
+ ? "Progress: unavailable"
492
+ : formatProgressReport(estimate.tracks.map((track) => track.estimate), undefined, new Date(), 2_000);
493
+ const lines = [
494
+ `Monitor: ${observation.route === "stop" ? "stopping" : "active"}`,
495
+ `Goal: ${observation.goalState}`,
496
+ `Work: ${observation.workState}`,
497
+ progress,
498
+ ];
499
+ const lastAction = latestRecordedAction(context);
500
+ if (lastAction !== undefined)
501
+ lines.push(`Last action: ${lastAction.summary}`);
502
+ if (observation.action !== undefined && completedObservations(context) < config.maxChecks) {
503
+ lines.push(`Next action: ${observation.action.nextAction}`);
504
+ }
505
+ if (observation.route === "wait" && completedObservations(context) < config.maxChecks) {
506
+ lines.push(`Next check: ${config.everyMinutes} minutes`);
507
+ }
508
+ if (completedObservations(context) >= config.maxChecks && observation.route !== "stop") {
509
+ lines.push(`Safety limit: reached ${config.maxChecks} observations`);
510
+ }
511
+ lines.push(`Status: ${observation.report}`);
512
+ const message = lines.join("\n");
513
+ return message.length <= MAX_REPORT_CHARS
514
+ ? message
515
+ : `${message.slice(0, MAX_REPORT_CHARS - 1)}…`;
516
+ }
517
+ function previousActionPrompt(context) {
518
+ const actionRecord = latestRecordedAction(context);
519
+ return actionRecord === undefined
520
+ ? "There is no previous completed action."
521
+ : `Previous action result: ${JSON.stringify(actionRecord)}`;
300
522
  }
301
523
  const monitorWorkflow = defineWorkflow({
302
524
  source: import.meta.url,
@@ -314,66 +536,26 @@ const monitorWorkflow = defineWorkflow({
314
536
  startAt: "prepare",
315
537
  maxSteps: 200_000,
316
538
  includes: {
317
- initialDesign: includeWorkflow({
318
- workflow: "autoplan",
319
- contract: autoplanWorkflow,
320
- input: ({ outputs }) => {
321
- const config = configFrom(outputs);
322
- const repair = outputs.check.repair;
323
- if (repair === undefined)
324
- throw new Error("monitor repair details are missing");
325
- const prior = outputs.initialDesign;
326
- const instructions = latestReplanInstructions(outputs);
327
- return {
328
- problem: repair.problem,
329
- ...(config.repair?.scope !== undefined ? { scope: config.repair.scope } : {}),
330
- ...(config.repair?.constraints !== undefined
331
- ? { constraints: config.repair.constraints }
332
- : {}),
333
- ...(prior?.exit === "ready" && prior.output?.plan !== undefined
334
- ? { previousPlan: prior.output.plan }
335
- : {}),
336
- newEvidence: instructions === undefined
337
- ? repair.evidence
338
- : { observedEvidence: repair.evidence, operatorInstructions: instructions },
339
- };
340
- },
341
- }),
342
- documentation: includeWorkflow(autodocWorkflow, {
539
+ planChange: includeWorkflow(planChangeWorkflow, {
343
540
  input: ({ outputs }) => {
344
- const config = configFrom(outputs);
345
- const repair = outputs.check.repair;
346
- const design = includedResult(autoplanWorkflow, outputs.initialDesign);
347
- if (design.exit !== "ready")
348
- throw new Error("monitor design did not return a ready plan");
349
- if (repair === undefined)
350
- throw new Error("monitor repair details are missing");
541
+ const request = actionFrom(outputs);
542
+ if (request.kind !== "repair")
543
+ throw new Error("monitor repair action is missing");
351
544
  return {
352
- task: repair.problem,
353
- plan: design.output.plan,
354
- ...(config.repair?.repository !== undefined
355
- ? { repository: config.repair.repository }
545
+ task: request.nextAction,
546
+ scope: authorityScope(request),
547
+ constraints: authorityConstraints(request),
548
+ ...(request.authority.repository !== undefined
549
+ ? { repository: request.authority.repository }
356
550
  : {}),
357
- evidence: latestReplanInstructions(outputs) ?? repair.evidence,
358
- };
359
- },
360
- }),
361
- approval: includeWorkflow(planApprovalWorkflow, {
362
- input: ({ outputs, state }) => {
363
- const config = configFrom(outputs);
364
- const repair = outputs.check.repair;
365
- const approval = config.repair?.approval;
366
- if (repair === undefined || approval === undefined) {
367
- throw new Error("monitor approval was entered without an approval policy");
368
- }
369
- const current = currentRepairPlan(outputs);
370
- const revision = state.steps.filter((step) => step.nodeId === "approval/approve").length + 1;
371
- return {
372
- task: repair.problem,
373
- plan: current.plan,
374
- planDigest: current.planDigest,
375
- audience: approval.audience,
376
- revision,
551
+ newEvidence: {
552
+ incomplete: request.incomplete,
553
+ evidence: request.evidence,
554
+ failureId: request.failureId,
555
+ targetStateId: request.targetStateId,
556
+ defect: request.defect,
557
+ },
558
+ approval: request.authority.repairApproval,
377
559
  };
378
560
  },
379
561
  }),
@@ -381,136 +563,144 @@ const monitorWorkflow = defineWorkflow({
381
563
  workflow: "autoimplement",
382
564
  contract: autoimplementWorkflow,
383
565
  input: ({ outputs }) => {
384
- const config = configFrom(outputs);
385
- const repair = outputs.check.repair;
566
+ const request = actionFrom(outputs);
386
567
  const documented = currentRepairPlan(outputs);
387
- if (repair === undefined)
388
- throw new Error("monitor repair details are missing");
389
- const request = {
390
- task: repair.problem,
568
+ if (request.kind !== "repair")
569
+ throw new Error("monitor repair action is missing");
570
+ const implementationInput = {
571
+ task: request.nextAction,
391
572
  plan: documented.plan,
392
573
  documentation: {
393
574
  status: "current",
394
575
  planDigest: documented.planDigest,
395
576
  documents: documented.documents,
396
577
  },
397
- ...(config.repair?.scope !== undefined ? { scope: config.repair.scope } : {}),
398
- ...(config.repair?.constraints !== undefined
399
- ? { constraints: config.repair.constraints }
400
- : {}),
401
- ...(config.repair?.repository !== undefined
402
- ? { repository: config.repair.repository }
578
+ scope: authorityScope(request),
579
+ constraints: authorityConstraints(request),
580
+ ...(request.authority.repository !== undefined
581
+ ? { repository: request.authority.repository }
403
582
  : {}),
404
- ...(config.repair?.baseBranch !== undefined
405
- ? { baseBranch: config.repair.baseBranch }
583
+ ...(request.authority.baseBranch !== undefined
584
+ ? { baseBranch: request.authority.baseBranch }
406
585
  : {}),
407
- merge: config.repair?.merge === true,
586
+ approval: request.authority.repairApproval,
587
+ merge: request.authority.merge,
408
588
  };
409
- return request;
589
+ return implementationInput;
410
590
  },
411
591
  }),
412
592
  },
413
593
  nodes: {
414
594
  prepare: compute({ run: ({ input }) => prepareMonitorInput(input) }),
415
- check: agent({
416
- statusDetail: "checking monitored target",
417
- timeoutMs: ({ outputs }) => configFrom(outputs).checkTimeoutMinutes * 60_000,
595
+ observe: agent({
596
+ statusDetail: "observing monitored goal",
597
+ timeoutMs: DEFAULT_CHECK_TIMEOUT_MINUTES * 60_000,
418
598
  prompt: (context) => {
419
599
  const config = configFrom(context.outputs);
420
- const previous = context.outputs.check;
600
+ const previous = context.outputs.observe;
421
601
  const priorEstimate = context.outputs.estimate;
422
602
  return [
423
- `Perform monitoring check ${completedChecks(context) + 1} of at most ${config.maxChecks}.`,
424
- `Task: ${config.task}`,
603
+ `Perform read-only observation ${completedObservations(context) + 1} of at most ${config.maxChecks}.`,
604
+ `Goal and authority: ${config.task}`,
425
605
  `Stop when: ${config.stopWhen}`,
606
+ "Use normal read-only tools to inspect authoritative target state, durable outputs, failures, checkpoints, active work, and applicable authority. Do not mutate any file, process, Job, service, remote resource, or configuration during this step.",
607
+ "Preserve the exact goal, allowed files and systems, forbidden changes, cost ceiling, provider/runtime contract, required checks, stop conditions, and allowed recovery actions from the task, conversation, and repository instructions. Do not invent permission.",
608
+ "List the safe actions the user has already authorized, even when no action is needed now. Choose wait only when useful target work is moving or an external event must finish. Choose act only when the goal is incomplete, work is idle, failed, or stopped, and one safe action is fully authorized. Choose stop when the goal is complete or safe continuation is blocked.",
609
+ "For act, describe one exact action. Use advance for normal next work, recover for an operational restart or resume, and repair only for a code or configuration defect. A normal start, resume, or restart must not become repair. If a paid action lacks verified remaining authority or exceeds its limit, choose stop. Before repair of a shared code or data defect, affected paid workers must already be stopped.",
610
+ "You are the regular Pi model and observation adapter. Publish measured facts with workflow update when useful. Do not require the target to implement a Pi-specific API, file, store, schema, command, service, transport, or dependency. Do not invent counts, rates, or ETA values.",
426
611
  previous === undefined
427
- ? "There is no previous observation."
428
- : `Previous accepted observation: ${previous.observation}`,
612
+ ? "There is no previous accepted observation."
613
+ : `Previous accepted observation: ${JSON.stringify(previous)}`,
614
+ previousActionPrompt(context),
429
615
  priorEstimate?.tracks.length
430
616
  ? `Previous progress: ${formatProgressReport(priorEstimate.tracks.map((track) => track.estimate))}`
431
617
  : "There is no previous measured progress.",
432
- config.repair === undefined
433
- ? "Observe only. This monitor has no mutation authorization."
434
- : "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.",
435
- "Use available tools to inspect the current source of truth.",
436
- "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.",
437
- "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.",
438
- config.repair === undefined
439
- ? "Choose route continue or stop."
440
- : "Choose route continue, repair, or stop.",
441
618
  ].join("\n\n");
442
619
  },
443
- expectedOutput: '{ "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" }',
444
- validate: (output, context) => validateMonitorCheck(output, configFrom(context.outputs).repair !== undefined),
620
+ expectedOutput: '{ "route": "wait" | "act" | "stop", "goalState": "complete" | "incomplete" | "blocked", "workState": "running" | "waiting" | "idle" | "failed" | "stopped" | "unknown", "observation": "factual state", "report": "concise factual summary", "targetStateId": "stable observed target-state ID", "authorizedActions": ["safe action already authorized by the user"], "progress": { "tracks": [{ "key": "stable-key", "data": { "schema": "pi-workflows.progress.v1", "status": "running", "completed": 1, "total": 2, "unit": "items" } }] } (optional), "action": { "kind": "advance" | "recover" | "repair", "incomplete": "what remains", "evidence": {}, "nextAction": "one exact action", "authority": { "status": "authorized", "basis": "existing authority", "allowedMutations": ["allowed file, system, or resource"], "forbiddenMutations": [], "costLimit": "recorded limit or not applicable", "providerRuntime": "recorded contract or not applicable", "requiredChecks": [], "stopConditions": [], "allowedRecoveryActions": [], "repository": "optional absolute path", "baseBranch": "optional branch", "merge": false, "repairApproval": { "mode": "auto" | "required" | "skip" } }, "cost": { "paidAction": false, "status": "not-applicable" | "within-limit", "evidence": "cost evidence" }, "defect": { "sharedCodeOrDataDefect": false, "paidWorkers": "not-applicable" | "stopped" | "running", "evidence": "worker evidence" }, "verification": "how to prove success", "failureId": "stable failure ID", "targetStateId": "stable target-state ID" } (required only for act), "reason": "short reason" }',
621
+ validate: validateMonitorObservation,
445
622
  }),
623
+ guard: compute({ run: guardObservation }),
446
624
  estimate: compute({ run: ({ outputs }) => estimateTracks(outputs) }),
447
625
  publish_progress: action({
448
626
  statusDetail: "publishing monitor progress",
449
627
  run: async ({ outputs, publishUpdate, signal }) => {
450
- const check = outputs.check;
451
- if (check.progress === undefined)
628
+ const observation = currentObservation(outputs);
629
+ if (observation.progress === undefined)
452
630
  return { published: 0 };
453
- for (const track of check.progress.tracks) {
631
+ for (const track of observation.progress.tracks) {
454
632
  await waitForUpdateSlot(signal);
455
633
  await publishUpdate({ type: "progress", key: track.key, data: track.data });
456
634
  }
457
- return { published: check.progress.tracks.length };
635
+ return { published: observation.progress.tracks.length };
458
636
  },
459
637
  }),
460
638
  report: notify({
461
639
  statusDetail: "queueing monitor update",
462
- message: (context) => reportMessage(context),
640
+ message: reportMessage,
463
641
  kind: "progress",
464
642
  }),
465
643
  decide: compute({
466
644
  run: (context) => {
467
- const check = context.outputs.check;
645
+ const observation = currentObservation(context.outputs);
468
646
  const config = configFrom(context.outputs);
469
- const checks = completedChecks(context);
470
- if (check.route === "stop")
471
- return { route: "stop", reason: check.reason, checks };
647
+ const checks = completedObservations(context);
648
+ if (observation.route === "stop")
649
+ return { route: "stop", reason: observation.reason, checks };
472
650
  if (checks >= config.maxChecks) {
473
651
  return {
474
652
  route: "stop",
475
- reason: `Reached the ${config.maxChecks}-check safety limit.`,
653
+ reason: `Reached the ${config.maxChecks}-observation safety limit.`,
476
654
  checks,
477
655
  };
478
656
  }
479
- if (check.route === "repair")
480
- return { route: "repair", reason: check.reason, checks };
481
- return { route: "continue", reason: check.reason, checks };
657
+ if (observation.route === "wait")
658
+ return { route: "wait", reason: observation.reason, checks };
659
+ const request = actionFrom(context.outputs);
660
+ return { route: request.kind, reason: observation.reason, checks };
482
661
  },
483
662
  }),
484
- repairGuard: compute({
485
- run: (context) => repeatedRepairWithoutProgress(context)
486
- ? {
487
- route: "blocked",
488
- reason: "The same issue returned after a completed repair with no changed target evidence.",
489
- }
490
- : { route: "repair", reason: "The issue is new or has changed evidence." },
491
- }),
492
- approvalRoute: compute({
493
- run: ({ outputs }) => ({
494
- route: configFrom(outputs).repair?.approval === undefined ? "implement" : "approve",
495
- }),
663
+ act: agent({
664
+ statusDetail: "performing authorized monitor action",
665
+ timeoutMs: DEFAULT_CHECK_TIMEOUT_MINUTES * 60_000,
666
+ prompt: ({ outputs }) => {
667
+ const request = actionFrom(outputs);
668
+ if (request.kind === "repair")
669
+ throw new Error("repair must use the composed repair path");
670
+ return [
671
+ `Perform this one ${request.kind} action with normal tools: ${request.nextAction}`,
672
+ `Incomplete work: ${request.incomplete}`,
673
+ `Evidence: ${JSON.stringify(request.evidence)}`,
674
+ `Authorization: ${JSON.stringify(request.authority)}`,
675
+ `Cost safety: ${JSON.stringify(request.cost)}`,
676
+ `Defect safety: ${JSON.stringify(request.defect)}`,
677
+ `Verification: ${request.verification}`,
678
+ "Perform only the stated action and only on the allowed files, systems, and resources. Do not plan, document, redesign, broaden scope, change a protected contract, or perform another action. Verify the direct result before submitting. Preserve the supplied failure and target-state IDs exactly.",
679
+ ].join("\n\n");
680
+ },
681
+ expectedOutput: '{ "status": "succeeded" | "failed" | "blocked", "summary": "action performed and real result", "evidence": {}, "verification": "verification performed", "failureId": "unchanged failure ID", "targetStateId": "unchanged target-state ID" }',
682
+ validate: (output, context) => validateMonitorActionResult(output, actionFrom(context.outputs)),
496
683
  }),
497
- replanGuard: compute({
498
- run: (context) => {
499
- const limit = configFrom(context.outputs).repair?.approval?.maxReplans ?? 3;
500
- const replans = context.state.steps.filter((step) => step.nodeId === "approval/replan").length;
501
- return replans > limit
502
- ? {
503
- route: "blocked",
504
- reason: `Monitor repair reached the ${limit}-replan safety limit.`,
505
- }
506
- : { route: "design", replans, limit };
684
+ repairComplete: compute({
685
+ run: ({ outputs }) => {
686
+ const request = actionFrom(outputs);
687
+ if (request.kind !== "repair")
688
+ throw new Error("monitor repair action is missing");
689
+ return {
690
+ status: "succeeded",
691
+ summary: request.nextAction,
692
+ evidence: outputs.implementation,
693
+ verification: request.verification,
694
+ failureId: request.failureId,
695
+ targetStateId: request.targetStateId,
696
+ };
507
697
  },
508
698
  }),
509
699
  repairBlocked: compute({
510
700
  run: (context) => ({
511
701
  reason: repairBlockedReason(context.outputs),
512
- observation: context.outputs.check.observation,
513
- checks: completedChecks(context),
702
+ observation: currentObservation(context.outputs).observation,
703
+ checks: completedObservations(context),
514
704
  reported: true,
515
705
  }),
516
706
  }),
@@ -519,11 +709,11 @@ const monitorWorkflow = defineWorkflow({
519
709
  kind: "final",
520
710
  message: ({ outputs }) => {
521
711
  const result = outputs.repairBlocked;
522
- return `Automatic repair stopped: ${result.reason}`;
712
+ return `Monitor repair stopped: ${result.reason}`;
523
713
  },
524
714
  }),
525
715
  schedule: action({
526
- statusDetail: "scheduling next monitor check",
716
+ statusDetail: "scheduling next monitor observation",
527
717
  run: async ({ outputs, publishUpdate }) => {
528
718
  const config = configFrom(outputs);
529
719
  const lastCheckAt = new Date().toISOString();
@@ -542,7 +732,7 @@ const monitorWorkflow = defineWorkflow({
542
732
  },
543
733
  }),
544
734
  sleep: shell({
545
- statusDetail: "waiting for next monitor check",
735
+ statusDetail: "waiting for next monitor observation",
546
736
  timeoutMs: MAX_INTERVAL_MINUTES * 60_000 + NODE_TIMEOUT_MARGIN_MS,
547
737
  exec: ({ outputs }) => {
548
738
  const sleepMs = configFrom(outputs).everyMinutes * 60_000;
@@ -557,26 +747,24 @@ const monitorWorkflow = defineWorkflow({
557
747
  }),
558
748
  finish: compute({
559
749
  run: ({ outputs }) => {
560
- const check = outputs.check;
750
+ const observation = currentObservation(outputs);
561
751
  const decision = outputs.decide;
562
752
  const repair = outputs.repairBlocked;
563
753
  return {
564
- reason: repair?.reason ?? decision?.reason ?? check.reason,
565
- observation: check.observation,
754
+ reason: repair?.reason ?? decision?.reason ?? observation.reason,
755
+ observation: observation.observation,
756
+ goalState: repair === undefined ? observation.goalState : "blocked",
757
+ workState: observation.workState,
566
758
  checks: decision?.checks ?? 1,
567
759
  reported: true,
568
- ...(outputs.implementation !== undefined
569
- ? { repair: outputs.implementation }
570
- : repair !== undefined
571
- ? { repair }
572
- : {}),
573
760
  };
574
761
  },
575
762
  }),
576
763
  },
577
764
  edges: [
578
- { from: "prepare", to: "check" },
579
- { from: "check", to: "estimate" },
765
+ { from: "prepare", to: "observe" },
766
+ { from: "observe", to: "guard" },
767
+ { from: "guard", to: "estimate" },
580
768
  { from: "estimate", to: "publish_progress" },
581
769
  { from: "publish_progress", to: "report" },
582
770
  { from: "report", to: "decide" },
@@ -584,34 +772,25 @@ const monitorWorkflow = defineWorkflow({
584
772
  from: "decide",
585
773
  switch: {
586
774
  on: "$.route",
587
- cases: { stop: "finish", continue: "schedule", repair: "repairGuard" },
775
+ cases: {
776
+ stop: "finish",
777
+ wait: "schedule",
778
+ advance: "act",
779
+ recover: "act",
780
+ repair: "planChange",
781
+ },
588
782
  },
589
783
  },
590
- {
591
- from: "repairGuard",
592
- switch: { on: "$.route", cases: { repair: "initialDesign", blocked: "repairBlocked" } },
593
- },
594
- { from: "initialDesign.ready", to: "documentation" },
595
- { from: "initialDesign.blocked", to: "repairBlocked" },
596
- { from: "documentation.ready", to: "approvalRoute" },
597
- { from: "documentation.blocked", to: "repairBlocked" },
598
- {
599
- from: "approvalRoute",
600
- switch: { on: "$.route", cases: { approve: "approval", implement: "implementation" } },
601
- },
602
- { from: "approval.continue", to: "implementation" },
603
- { from: "approval.stop", to: "repairBlocked" },
604
- { from: "approval.replan", to: "replanGuard" },
605
- {
606
- from: "replanGuard",
607
- switch: { on: "$.route", cases: { design: "initialDesign", blocked: "repairBlocked" } },
608
- },
609
- { from: "implementation.completed", to: "check" },
784
+ { from: "act", to: "observe" },
785
+ { from: "planChange.ready", to: "implementation" },
786
+ { from: "planChange.blocked", to: "repairBlocked" },
787
+ { from: "implementation.completed", to: "repairComplete" },
610
788
  { from: "implementation.blocked", to: "repairBlocked" },
789
+ { from: "repairComplete", to: "observe" },
611
790
  { from: "repairBlocked", to: "repairReport" },
612
791
  { from: "repairReport", to: "finish" },
613
792
  { from: "schedule", to: "sleep" },
614
- { from: "sleep", to: "check" },
793
+ { from: "sleep", to: "observe" },
615
794
  ],
616
795
  });
617
796
  export default monitorWorkflow;