@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
@@ -20,46 +20,33 @@ import type {
20
20
  WorkflowProgressData,
21
21
  } from "../workflows/types.js";
22
22
  import { validateProgressData } from "../workflows/updates.js";
23
- import autodocWorkflow, { type AutodocInput } from "./autodoc.workflow.js";
24
23
  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";
24
+ import {
25
+ parsePlanApprovalPolicy,
26
+ type ResolvedPlanApprovalPolicy,
27
+ } from "./plan-approval.workflow.js";
28
+ import planChangeWorkflow, { type NormalizedPlanChangeInput } from "./plan-change.workflow.js";
27
29
 
28
30
  const MIN_INTERVAL_MINUTES = 1;
29
31
  const MAX_INTERVAL_MINUTES = 24 * 60;
30
32
  const DEFAULT_INTERVAL_MINUTES = 30;
31
- const MIN_CHECK_TIMEOUT_MINUTES = 5;
32
- const MAX_CHECK_TIMEOUT_MINUTES = 24 * 60;
33
- const DEFAULT_MIN_CHECK_TIMEOUT_MINUTES = 60;
33
+ const DEFAULT_CHECK_TIMEOUT_MINUTES = 60;
34
34
  const DEFAULT_MAX_CHECKS = 1_000;
35
35
  const MAX_CHECKS = 1_000;
36
36
  const MAX_TRACKS = 256;
37
37
  const MAX_OBSERVATION_CHARS = 8_000;
38
38
  const MAX_REPORT_CHARS = 4_000;
39
39
  const MAX_REASON_CHARS = 2_000;
40
+ const MAX_ACTION_TEXT_CHARS = 8_000;
41
+ const MAX_ID_CHARS = 256;
40
42
  const SLEEP_TIMEOUT_MARGIN_MS = 60_000;
41
43
  const NODE_TIMEOUT_MARGIN_MS = 2 * 60_000;
42
44
 
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
-
56
45
  export type MonitorInput = {
57
46
  task: string;
58
47
  everyMinutes?: number;
59
48
  stopWhen?: string;
60
49
  maxChecks?: number;
61
- checkTimeoutMinutes?: number;
62
- repair?: MonitorRepairPolicy;
63
50
  };
64
51
 
65
52
  type MonitorConfig = {
@@ -67,25 +54,78 @@ type MonitorConfig = {
67
54
  everyMinutes: number;
68
55
  stopWhen: string;
69
56
  maxChecks: number;
70
- checkTimeoutMinutes: number;
71
- repair?: MonitorRepairPolicy;
72
57
  };
73
- type MonitorRoute = "continue" | "repair" | "stop";
58
+ type MonitorRoute = "wait" | "act" | "stop";
59
+ type MonitorGoalState = "complete" | "incomplete" | "blocked";
60
+ type MonitorWorkState = "running" | "waiting" | "idle" | "failed" | "stopped" | "unknown";
61
+ type MonitorActionKind = "advance" | "recover" | "repair";
74
62
  type MonitorTrack = { key: string; data: WorkflowProgressData };
75
- type MonitorRepairRequest = {
76
- problem: string;
63
+ type MonitorAuthority = {
64
+ status: "authorized" | "outside";
65
+ basis: string;
66
+ allowedMutations: string[];
67
+ forbiddenMutations: string[];
68
+ costLimit: string;
69
+ providerRuntime: string;
70
+ requiredChecks: string[];
71
+ stopConditions: string[];
72
+ allowedRecoveryActions: string[];
73
+ repository?: string;
74
+ baseBranch?: string;
75
+ merge: boolean;
76
+ repairApproval: ResolvedPlanApprovalPolicy;
77
+ };
78
+ type MonitorCostSafety = {
79
+ paidAction: boolean;
80
+ status: "not-applicable" | "within-limit" | "missing" | "exceeded";
81
+ evidence: string;
82
+ };
83
+ type MonitorDefectSafety = {
84
+ sharedCodeOrDataDefect: boolean;
85
+ paidWorkers: "not-applicable" | "stopped" | "running";
86
+ evidence: string;
87
+ };
88
+ type MonitorActionRequest = {
89
+ kind: MonitorActionKind;
90
+ incomplete: string;
77
91
  evidence: unknown;
78
- issueFingerprint: string;
92
+ nextAction: string;
93
+ authority: MonitorAuthority;
94
+ cost: MonitorCostSafety;
95
+ defect: MonitorDefectSafety;
96
+ verification: string;
97
+ failureId: string;
98
+ targetStateId: string;
79
99
  };
80
- type MonitorCheck = {
100
+ export type MonitorObservation = {
81
101
  route: MonitorRoute;
102
+ goalState: MonitorGoalState;
103
+ workState: MonitorWorkState;
82
104
  observation: string;
83
105
  report: string;
106
+ targetStateId: string;
107
+ authorizedActions: string[];
84
108
  progress?: { tracks: MonitorTrack[] };
85
- repair?: MonitorRepairRequest;
109
+ action?: MonitorActionRequest;
86
110
  reason: string;
87
111
  };
112
+ type MonitorActionResult = {
113
+ status: "succeeded" | "failed" | "blocked";
114
+ summary: string;
115
+ evidence: unknown;
116
+ verification: string;
117
+ failureId: string;
118
+ targetStateId: string;
119
+ };
88
120
  type MonitorEstimate = { tracks: ProgressTrackState[] };
121
+ type RecordedAction = {
122
+ status: "succeeded" | "failed" | "blocked";
123
+ summary: string;
124
+ evidence: unknown;
125
+ verification: string;
126
+ failureId: string;
127
+ targetStateId: string;
128
+ };
89
129
 
90
130
  function requireRecord(value: unknown, label: string): Record<string, unknown> {
91
131
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
@@ -94,6 +134,15 @@ function requireRecord(value: unknown, label: string): Record<string, unknown> {
94
134
  return value as Record<string, unknown>;
95
135
  }
96
136
 
137
+ function requireExactKeys(
138
+ value: Record<string, unknown>,
139
+ allowed: readonly string[],
140
+ label: string,
141
+ ): void {
142
+ const unexpected = Object.keys(value).find((key) => !allowed.includes(key));
143
+ if (unexpected !== undefined) throw new Error(`${label} field ${unexpected} is not supported`);
144
+ }
145
+
97
146
  function requireBoundedString(value: unknown, label: string, maxChars: number): string {
98
147
  if (typeof value !== "string") throw new Error(`${label} must be a string`);
99
148
  const trimmed = value.trim();
@@ -102,6 +151,15 @@ function requireBoundedString(value: unknown, label: string, maxChars: number):
102
151
  return trimmed;
103
152
  }
104
153
 
154
+ function requireStringArray(value: unknown, label: string): string[] {
155
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
156
+ throw new Error(`${label} must be an array of strings`);
157
+ }
158
+ return value.map((item, index) =>
159
+ requireBoundedString(item, `${label}[${index}]`, MAX_ACTION_TEXT_CHARS),
160
+ );
161
+ }
162
+
105
163
  async function waitForUpdateSlot(signal: AbortSignal): Promise<void> {
106
164
  await new Promise<void>((resolve, reject) => {
107
165
  const onAbort = () => {
@@ -119,17 +177,8 @@ async function waitForUpdateSlot(signal: AbortSignal): Promise<void> {
119
177
 
120
178
  export function prepareMonitorInput(input: unknown): MonitorConfig {
121
179
  const value = requireRecord(input, "monitor input") as Partial<MonitorInput>;
122
- const allowed = new Set([
123
- "task",
124
- "everyMinutes",
125
- "stopWhen",
126
- "maxChecks",
127
- "checkTimeoutMinutes",
128
- "repair",
129
- ]);
130
- for (const field of Object.keys(value)) {
131
- if (!allowed.has(field)) throw new Error(`monitor input field ${field} is not supported`);
132
- }
180
+ const allowed = ["task", "everyMinutes", "stopWhen", "maxChecks"] as const;
181
+ requireExactKeys(value, allowed, "monitor input");
133
182
  const task = requireBoundedString(value.task, "task", 8_000);
134
183
  const everyMinutes = value.everyMinutes ?? DEFAULT_INTERVAL_MINUTES;
135
184
  if (
@@ -145,62 +194,6 @@ export function prepareMonitorInput(input: unknown): MonitorConfig {
145
194
  if (!Number.isInteger(maxChecks) || maxChecks <= 0 || maxChecks > MAX_CHECKS) {
146
195
  throw new Error(`maxChecks must be an integer from 1 through ${MAX_CHECKS}`);
147
196
  }
148
- const checkTimeoutMinutes =
149
- value.checkTimeoutMinutes ?? Math.max(DEFAULT_MIN_CHECK_TIMEOUT_MINUTES, everyMinutes);
150
- if (
151
- !Number.isInteger(checkTimeoutMinutes) ||
152
- checkTimeoutMinutes < MIN_CHECK_TIMEOUT_MINUTES ||
153
- checkTimeoutMinutes > MAX_CHECK_TIMEOUT_MINUTES
154
- ) {
155
- throw new Error(
156
- `checkTimeoutMinutes must be an integer from ${MIN_CHECK_TIMEOUT_MINUTES} through ${MAX_CHECK_TIMEOUT_MINUTES}`,
157
- );
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
- }
204
197
  return {
205
198
  task,
206
199
  everyMinutes,
@@ -209,8 +202,6 @@ export function prepareMonitorInput(input: unknown): MonitorConfig {
209
202
  ? "Stop only when the user explicitly asks to stop."
210
203
  : requireBoundedString(value.stopWhen, "stopWhen", 4_000),
211
204
  maxChecks,
212
- checkTimeoutMinutes,
213
- ...(repair !== undefined ? { repair } : {}),
214
205
  };
215
206
  }
216
207
 
@@ -218,63 +209,305 @@ function configFrom(outputs: Record<string, unknown>): MonitorConfig {
218
209
  return outputs.prepare as MonitorConfig;
219
210
  }
220
211
 
221
- function completedChecks(context: WorkflowNodeContext): number {
222
- return context.state.steps.filter((step) => step.nodeId === "check" && step.outcome === "ok")
212
+ function completedObservations(context: WorkflowNodeContext): number {
213
+ return context.state.steps.filter((step) => step.nodeId === "observe" && step.outcome === "ok")
223
214
  .length;
224
215
  }
225
216
 
226
- export function validateMonitorCheck(output: unknown, repairAuthorized = false): MonitorCheck {
227
- const value = requireRecord(output, "monitor check output");
228
- const allowed = new Set(["route", "observation", "report", "progress", "repair", "reason"]);
229
- for (const key of Object.keys(value))
230
- if (!allowed.has(key)) throw new Error(`monitor check field ${key} is not supported`);
231
- if (value.route !== "continue" && value.route !== "repair" && value.route !== "stop") {
232
- throw new Error("route must be continue, repair, or stop");
217
+ function parseAuthority(input: unknown): MonitorAuthority {
218
+ const value = requireRecord(input, "action authority");
219
+ requireExactKeys(
220
+ value,
221
+ [
222
+ "status",
223
+ "basis",
224
+ "allowedMutations",
225
+ "forbiddenMutations",
226
+ "costLimit",
227
+ "providerRuntime",
228
+ "requiredChecks",
229
+ "stopConditions",
230
+ "allowedRecoveryActions",
231
+ "repository",
232
+ "baseBranch",
233
+ "merge",
234
+ "repairApproval",
235
+ ],
236
+ "action authority",
237
+ );
238
+ if (value.status !== "authorized" && value.status !== "outside") {
239
+ throw new Error("action authority status must be authorized or outside");
240
+ }
241
+ if (typeof value.merge !== "boolean") throw new Error("action authority merge must be boolean");
242
+ const approval =
243
+ value.repairApproval === undefined
244
+ ? undefined
245
+ : requireRecord(value.repairApproval, "repair approval");
246
+ return {
247
+ status: value.status,
248
+ basis: requireBoundedString(value.basis, "action authority basis", MAX_ACTION_TEXT_CHARS),
249
+ allowedMutations: requireStringArray(
250
+ value.allowedMutations,
251
+ "action authority allowedMutations",
252
+ ),
253
+ forbiddenMutations: requireStringArray(
254
+ value.forbiddenMutations,
255
+ "action authority forbiddenMutations",
256
+ ),
257
+ costLimit: requireBoundedString(
258
+ value.costLimit,
259
+ "action authority costLimit",
260
+ MAX_ACTION_TEXT_CHARS,
261
+ ),
262
+ providerRuntime: requireBoundedString(
263
+ value.providerRuntime,
264
+ "action authority providerRuntime",
265
+ MAX_ACTION_TEXT_CHARS,
266
+ ),
267
+ requiredChecks: requireStringArray(value.requiredChecks, "action authority requiredChecks"),
268
+ stopConditions: requireStringArray(value.stopConditions, "action authority stopConditions"),
269
+ allowedRecoveryActions: requireStringArray(
270
+ value.allowedRecoveryActions,
271
+ "action authority allowedRecoveryActions",
272
+ ),
273
+ ...(value.repository !== undefined
274
+ ? {
275
+ repository: requireBoundedString(value.repository, "action authority repository", 4_000),
276
+ }
277
+ : {}),
278
+ ...(value.baseBranch !== undefined
279
+ ? {
280
+ baseBranch: requireBoundedString(value.baseBranch, "action authority baseBranch", 256),
281
+ }
282
+ : {}),
283
+ merge: value.merge,
284
+ repairApproval: parsePlanApprovalPolicy(approval),
285
+ };
286
+ }
287
+
288
+ function parseCostSafety(input: unknown): MonitorCostSafety {
289
+ const value = requireRecord(input, "action cost");
290
+ requireExactKeys(value, ["paidAction", "status", "evidence"], "action cost");
291
+ if (typeof value.paidAction !== "boolean")
292
+ throw new Error("action cost paidAction must be boolean");
293
+ if (
294
+ value.status !== "not-applicable" &&
295
+ value.status !== "within-limit" &&
296
+ value.status !== "missing" &&
297
+ value.status !== "exceeded"
298
+ ) {
299
+ throw new Error("action cost status is invalid");
300
+ }
301
+ return {
302
+ paidAction: value.paidAction,
303
+ status: value.status,
304
+ evidence: requireBoundedString(value.evidence, "action cost evidence", MAX_ACTION_TEXT_CHARS),
305
+ };
306
+ }
307
+
308
+ function parseDefectSafety(input: unknown): MonitorDefectSafety {
309
+ const value = requireRecord(input, "action defect");
310
+ requireExactKeys(value, ["sharedCodeOrDataDefect", "paidWorkers", "evidence"], "action defect");
311
+ if (typeof value.sharedCodeOrDataDefect !== "boolean") {
312
+ throw new Error("action defect sharedCodeOrDataDefect must be boolean");
313
+ }
314
+ if (
315
+ value.paidWorkers !== "not-applicable" &&
316
+ value.paidWorkers !== "stopped" &&
317
+ value.paidWorkers !== "running"
318
+ ) {
319
+ throw new Error("action defect paidWorkers is invalid");
320
+ }
321
+ return {
322
+ sharedCodeOrDataDefect: value.sharedCodeOrDataDefect,
323
+ paidWorkers: value.paidWorkers,
324
+ evidence: requireBoundedString(value.evidence, "action defect evidence", MAX_ACTION_TEXT_CHARS),
325
+ };
326
+ }
327
+
328
+ function parseActionRequest(input: unknown): MonitorActionRequest {
329
+ const value = requireRecord(input, "monitor action request");
330
+ requireExactKeys(
331
+ value,
332
+ [
333
+ "kind",
334
+ "incomplete",
335
+ "evidence",
336
+ "nextAction",
337
+ "authority",
338
+ "cost",
339
+ "defect",
340
+ "verification",
341
+ "failureId",
342
+ "targetStateId",
343
+ ],
344
+ "monitor action request",
345
+ );
346
+ if (value.kind !== "advance" && value.kind !== "recover" && value.kind !== "repair") {
347
+ throw new Error("monitor action kind must be advance, recover, or repair");
348
+ }
349
+ const request: MonitorActionRequest = {
350
+ kind: value.kind,
351
+ incomplete: requireBoundedString(
352
+ value.incomplete,
353
+ "monitor action incomplete work",
354
+ MAX_ACTION_TEXT_CHARS,
355
+ ),
356
+ evidence: value.evidence ?? null,
357
+ nextAction: requireBoundedString(
358
+ value.nextAction,
359
+ "monitor action nextAction",
360
+ MAX_ACTION_TEXT_CHARS,
361
+ ),
362
+ authority: parseAuthority(value.authority),
363
+ cost: parseCostSafety(value.cost),
364
+ defect: parseDefectSafety(value.defect),
365
+ verification: requireBoundedString(
366
+ value.verification,
367
+ "monitor action verification",
368
+ MAX_ACTION_TEXT_CHARS,
369
+ ),
370
+ failureId: requireBoundedString(value.failureId, "monitor action failureId", MAX_ID_CHARS),
371
+ targetStateId: requireBoundedString(
372
+ value.targetStateId,
373
+ "monitor action targetStateId",
374
+ MAX_ID_CHARS,
375
+ ),
376
+ };
377
+ if (request.authority.status !== "authorized") {
378
+ throw new Error("route act requires action authority status authorized");
379
+ }
380
+ if (request.authority.allowedMutations.length === 0) {
381
+ throw new Error("route act requires at least one allowed mutation");
382
+ }
383
+ if (
384
+ request.cost.paidAction &&
385
+ (request.cost.status === "missing" || request.cost.status === "exceeded")
386
+ ) {
387
+ throw new Error("route act cannot launch paid work without verified remaining authority");
388
+ }
389
+ if (request.cost.paidAction && request.cost.status !== "within-limit") {
390
+ throw new Error("paid route act requires cost status within-limit");
391
+ }
392
+ if (!request.cost.paidAction && request.cost.status !== "not-applicable") {
393
+ throw new Error("unpaid route act requires cost status not-applicable");
394
+ }
395
+ if (
396
+ request.kind === "repair" &&
397
+ request.defect.sharedCodeOrDataDefect &&
398
+ request.defect.paidWorkers === "running"
399
+ ) {
400
+ throw new Error("paid workers must stop before a shared code or data repair");
401
+ }
402
+ return request;
403
+ }
404
+
405
+ export function validateMonitorObservation(output: unknown): MonitorObservation {
406
+ const value = requireRecord(output, "monitor observation output");
407
+ requireExactKeys(
408
+ value,
409
+ [
410
+ "route",
411
+ "goalState",
412
+ "workState",
413
+ "observation",
414
+ "report",
415
+ "targetStateId",
416
+ "authorizedActions",
417
+ "progress",
418
+ "action",
419
+ "reason",
420
+ ],
421
+ "monitor observation",
422
+ );
423
+ if (value.route !== "wait" && value.route !== "act" && value.route !== "stop") {
424
+ throw new Error("route must be wait, act, or stop");
233
425
  }
234
- if (value.route === "repair" && !repairAuthorized) {
235
- throw new Error("route repair requires explicit monitor repair authorization");
426
+ if (
427
+ value.goalState !== "complete" &&
428
+ value.goalState !== "incomplete" &&
429
+ value.goalState !== "blocked"
430
+ ) {
431
+ throw new Error("goalState must be complete, incomplete, or blocked");
432
+ }
433
+ if (
434
+ value.workState !== "running" &&
435
+ value.workState !== "waiting" &&
436
+ value.workState !== "idle" &&
437
+ value.workState !== "failed" &&
438
+ value.workState !== "stopped" &&
439
+ value.workState !== "unknown"
440
+ ) {
441
+ throw new Error("workState is invalid");
236
442
  }
237
- const check: MonitorCheck = {
443
+ const observation: MonitorObservation = {
238
444
  route: value.route,
445
+ goalState: value.goalState,
446
+ workState: value.workState,
239
447
  observation: requireBoundedString(value.observation, "observation", MAX_OBSERVATION_CHARS),
240
448
  report: requireBoundedString(value.report, "report", MAX_REPORT_CHARS),
449
+ targetStateId: requireBoundedString(value.targetStateId, "targetStateId", MAX_ID_CHARS),
450
+ authorizedActions: requireStringArray(value.authorizedActions, "authorizedActions"),
241
451
  reason: requireBoundedString(value.reason, "reason", MAX_REASON_CHARS),
242
452
  };
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
- };
453
+ if (value.progress !== undefined) observation.progress = validateMonitorProgress(value.progress);
454
+ if (value.action !== undefined) observation.action = parseActionRequest(value.action);
455
+ if (observation.route === "act" && observation.action === undefined) {
456
+ throw new Error("route act requires action details");
457
+ }
458
+ if (
459
+ observation.action !== undefined &&
460
+ observation.action.targetStateId !== observation.targetStateId
461
+ ) {
462
+ throw new Error("monitor action targetStateId must match the observed targetStateId");
463
+ }
464
+ if (observation.route !== "act" && observation.action !== undefined) {
465
+ throw new Error("action details are only valid for route act");
255
466
  }
256
- if (value.route === "repair" && check.repair === undefined) {
257
- throw new Error("route repair requires repair details");
467
+ if (observation.route === "act" && observation.goalState !== "incomplete") {
468
+ throw new Error("route act requires goalState incomplete");
258
469
  }
259
- return check;
470
+ if (
471
+ observation.route === "act" &&
472
+ observation.workState !== "idle" &&
473
+ observation.workState !== "failed" &&
474
+ observation.workState !== "stopped"
475
+ ) {
476
+ throw new Error("route act requires idle, failed, or stopped work");
477
+ }
478
+ if (observation.route === "wait" && observation.goalState !== "incomplete") {
479
+ throw new Error("route wait requires goalState incomplete");
480
+ }
481
+ if (
482
+ observation.route === "wait" &&
483
+ observation.workState !== "running" &&
484
+ observation.workState !== "waiting"
485
+ ) {
486
+ throw new Error("route wait requires running work or an external wait");
487
+ }
488
+ if (observation.goalState === "complete" && observation.route !== "stop") {
489
+ throw new Error("goalState complete requires route stop");
490
+ }
491
+ if (observation.goalState === "blocked" && observation.route !== "stop") {
492
+ throw new Error("goalState blocked requires route stop");
493
+ }
494
+ return observation;
260
495
  }
261
496
 
262
497
  function validateMonitorProgress(input: unknown): { tracks: MonitorTrack[] } {
263
498
  const value = requireRecord(input, "progress");
264
- if (Object.keys(value).some((key) => key !== "tracks"))
265
- throw new Error("progress only supports tracks");
499
+ requireExactKeys(value, ["tracks"], "progress");
266
500
  if (!Array.isArray(value.tracks) || value.tracks.length < 1 || value.tracks.length > MAX_TRACKS) {
267
501
  throw new Error(`progress.tracks must contain 1 through ${MAX_TRACKS} entries`);
268
502
  }
269
503
  const keys = new Set<string>();
270
504
  const tracks = value.tracks.map((raw, index) => {
271
505
  const track = requireRecord(raw, `progress.tracks[${index}]`);
272
- if (Object.keys(track).some((key) => key !== "key" && key !== "data")) {
273
- throw new Error(`progress.tracks[${index}] has an unsupported field`);
274
- }
506
+ requireExactKeys(track, ["key", "data"], `progress.tracks[${index}]`);
275
507
  const key = requireBoundedString(track.key, `progress.tracks[${index}].key`, 128);
276
- if (!/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/.test(key))
508
+ if (!/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/.test(key)) {
277
509
  throw new Error(`progress.tracks[${index}].key is invalid`);
510
+ }
278
511
  if (keys.has(key)) throw new Error(`progress track key ${key} is duplicated`);
279
512
  keys.add(key);
280
513
  return {
@@ -285,14 +518,18 @@ function validateMonitorProgress(input: unknown): { tracks: MonitorTrack[] } {
285
518
  return { tracks };
286
519
  }
287
520
 
521
+ function currentObservation(outputs: Record<string, unknown>): MonitorObservation {
522
+ return (outputs.guard ?? outputs.observe) as MonitorObservation;
523
+ }
524
+
288
525
  function estimateTracks(outputs: Record<string, unknown>): MonitorEstimate {
289
- const check = outputs.check as MonitorCheck;
290
- if (check.progress === undefined) return { tracks: [] };
526
+ const observation = currentObservation(outputs);
527
+ if (observation.progress === undefined) return { tracks: [] };
291
528
  const previous = outputs.estimate as MonitorEstimate | undefined;
292
529
  const previousByKey = new Map((previous?.tracks ?? []).map((track) => [track.key, track]));
293
530
  const at = new Date().toISOString();
294
531
  return {
295
- tracks: check.progress.tracks.map((track) => {
532
+ tracks: observation.progress.tracks.map((track) => {
296
533
  const samples: ProgressSample[] = [
297
534
  ...(previousByKey.get(track.key)?.samples ?? []),
298
535
  { at, data: track.data },
@@ -302,114 +539,216 @@ function estimateTracks(outputs: Record<string, unknown>): MonitorEstimate {
302
539
  };
303
540
  }
304
541
 
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");
542
+ function actionFrom(outputs: Record<string, unknown>): MonitorActionRequest {
543
+ const request = currentObservation(outputs).action;
544
+ if (request === undefined) throw new Error("monitor action details are missing");
545
+ return request;
546
+ }
547
+
548
+ function repeatedCompletedRepair(context: WorkflowNodeContext): boolean {
549
+ const action = (context.outputs.observe as MonitorObservation).action;
550
+ if (action?.kind !== "repair") return false;
551
+ const currentIndex = context.state.steps.findLastIndex((step) => step.nodeId === "observe");
552
+ for (let index = currentIndex - 1; index >= 0; index -= 1) {
553
+ const step = context.state.steps[index];
554
+ if (step?.nodeId !== "observe") continue;
555
+ const prior = step.output as MonitorObservation;
556
+ if (
557
+ prior.action?.kind !== "repair" ||
558
+ prior.action.failureId !== action.failureId ||
559
+ prior.action.targetStateId !== action.targetStateId
560
+ ) {
561
+ continue;
562
+ }
563
+ return context.state.steps
564
+ .slice(index + 1, currentIndex)
565
+ .some((candidate) => candidate.nodeId === "repairComplete" && candidate.outcome === "ok");
319
566
  }
320
567
  return false;
321
568
  }
322
569
 
570
+ function guardObservation(context: WorkflowNodeContext): MonitorObservation {
571
+ const observation = context.outputs.observe as MonitorObservation;
572
+ if (!repeatedCompletedRepair(context)) return observation;
573
+ const action = observation.action;
574
+ if (action === undefined) return observation;
575
+ return {
576
+ route: "stop",
577
+ goalState: "blocked",
578
+ workState: observation.workState,
579
+ observation: observation.observation,
580
+ report: "The same failure and target state returned after one completed repair.",
581
+ targetStateId: observation.targetStateId,
582
+ authorizedActions: observation.authorizedActions,
583
+ ...(observation.progress !== undefined ? { progress: observation.progress } : {}),
584
+ reason: `Repair stopped because ${action.failureId} returned in target state ${action.targetStateId}.`,
585
+ };
586
+ }
587
+
588
+ export function validateMonitorActionResult(
589
+ output: unknown,
590
+ expected: Pick<MonitorActionRequest, "failureId" | "targetStateId">,
591
+ ): MonitorActionResult {
592
+ const value = requireRecord(output, "monitor action result");
593
+ requireExactKeys(
594
+ value,
595
+ ["status", "summary", "evidence", "verification", "failureId", "targetStateId"],
596
+ "monitor action result",
597
+ );
598
+ if (value.status !== "succeeded" && value.status !== "failed" && value.status !== "blocked") {
599
+ throw new Error("monitor action result status must be succeeded, failed, or blocked");
600
+ }
601
+ const result: MonitorActionResult = {
602
+ status: value.status,
603
+ summary: requireBoundedString(value.summary, "monitor action result summary", MAX_REPORT_CHARS),
604
+ evidence: value.evidence ?? null,
605
+ verification: requireBoundedString(
606
+ value.verification,
607
+ "monitor action result verification",
608
+ MAX_ACTION_TEXT_CHARS,
609
+ ),
610
+ failureId: requireBoundedString(
611
+ value.failureId,
612
+ "monitor action result failureId",
613
+ MAX_ID_CHARS,
614
+ ),
615
+ targetStateId: requireBoundedString(
616
+ value.targetStateId,
617
+ "monitor action result targetStateId",
618
+ MAX_ID_CHARS,
619
+ ),
620
+ };
621
+ if (result.failureId !== expected.failureId || result.targetStateId !== expected.targetStateId) {
622
+ throw new Error(
623
+ "monitor action result must preserve the requested failure and target-state IDs",
624
+ );
625
+ }
626
+ return result;
627
+ }
628
+
323
629
  function currentRepairPlan(outputs: Record<string, unknown>): {
324
630
  plan: unknown;
325
631
  planDigest: string;
326
632
  documents: string[];
327
633
  } {
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: [] };
634
+ const result = includedResult(planChangeWorkflow, outputs.planChange);
635
+ if (result.exit !== "ready") throw new Error("monitor plan change did not return a ready plan");
636
+ return {
637
+ plan: result.output.plan,
638
+ planDigest: result.output.planDigest,
639
+ documents: result.output.documents,
640
+ };
352
641
  }
353
642
 
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;
643
+ function authorityScope(actionRequest: MonitorActionRequest): string {
644
+ const authority = actionRequest.authority;
645
+ return [
646
+ `Authorization basis: ${authority.basis}`,
647
+ `Allowed mutations: ${authority.allowedMutations.join("; ")}`,
648
+ `Forbidden mutations: ${authority.forbiddenMutations.join("; ") || "none recorded"}`,
649
+ `Cost limit: ${authority.costLimit}`,
650
+ `Provider/runtime contract: ${authority.providerRuntime}`,
651
+ `Allowed recovery actions: ${authority.allowedRecoveryActions.join("; ") || "none recorded"}`,
652
+ ].join("\n");
653
+ }
654
+
655
+ function authorityConstraints(actionRequest: MonitorActionRequest): string[] {
656
+ const authority = actionRequest.authority;
657
+ return [
658
+ ...authority.forbiddenMutations.map((item) => `Forbidden: ${item}`),
659
+ `Cost limit: ${authority.costLimit}`,
660
+ `Provider/runtime contract: ${authority.providerRuntime}`,
661
+ ...authority.requiredChecks.map((item) => `Required check: ${item}`),
662
+ ...authority.stopConditions.map((item) => `Stop condition: ${item}`),
663
+ `Repair verification: ${actionRequest.verification}`,
664
+ ];
361
665
  }
362
666
 
363
667
  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
668
+ const planChange = outputs.planChange as
371
669
  | { exit?: string; output?: { reason?: string } }
372
670
  | undefined;
373
671
  const implementation = outputs.implementation as
374
672
  | { exit?: string; output?: { reason?: string } }
375
673
  | undefined;
376
- const design = outputs.initialDesign as
377
- | { exit?: string; output?: { reason?: string } }
378
- | undefined;
379
674
  return (
380
675
  implementation?.output?.reason ??
381
- documentation?.output?.reason ??
382
- design?.output?.reason ??
383
- "The repair did not produce new verified progress."
676
+ planChange?.output?.reason ??
677
+ "The repair did not produce a verified result."
384
678
  );
385
679
  }
386
680
 
681
+ function recordedActionFromStep(
682
+ step: WorkflowNodeContext["state"]["steps"][number],
683
+ ): RecordedAction | undefined {
684
+ if (step.outcome !== "ok") return undefined;
685
+ if (step.nodeId === "act") {
686
+ const result = step.output as MonitorActionResult;
687
+ return {
688
+ status: result.status,
689
+ summary: result.summary,
690
+ evidence: result.evidence,
691
+ verification: result.verification,
692
+ failureId: result.failureId,
693
+ targetStateId: result.targetStateId,
694
+ };
695
+ }
696
+ if (step.nodeId === "repairComplete") return step.output as RecordedAction;
697
+ return undefined;
698
+ }
699
+
700
+ function latestRecordedAction(context: WorkflowNodeContext): RecordedAction | undefined {
701
+ for (let index = context.state.steps.length - 1; index >= 0; index -= 1) {
702
+ const step = context.state.steps[index];
703
+ if (step === undefined) continue;
704
+ const recorded = recordedActionFromStep(step);
705
+ if (recorded !== undefined) return recorded;
706
+ }
707
+ return undefined;
708
+ }
709
+
387
710
  function reportMessage(context: WorkflowNodeContext): string {
388
- const check = context.outputs.check as MonitorCheck;
711
+ const observation = currentObservation(context.outputs);
389
712
  const estimate = context.outputs.estimate as MonitorEstimate;
390
713
  const config = configFrom(context.outputs);
391
- const suffix: string[] = [];
392
- if (estimate.tracks.length > 0) {
393
- suffix.push(
394
- formatProgressReport(
395
- estimate.tracks.map((track) => track.estimate),
396
- check.route === "continue" ? config.everyMinutes : undefined,
397
- new Date(),
398
- 2_000,
399
- ),
400
- );
714
+ const progress =
715
+ estimate.tracks.length === 0
716
+ ? "Progress: unavailable"
717
+ : formatProgressReport(
718
+ estimate.tracks.map((track) => track.estimate),
719
+ undefined,
720
+ new Date(),
721
+ 2_000,
722
+ );
723
+ const lines = [
724
+ `Monitor: ${observation.route === "stop" ? "stopping" : "active"}`,
725
+ `Goal: ${observation.goalState}`,
726
+ `Work: ${observation.workState}`,
727
+ progress,
728
+ ];
729
+ const lastAction = latestRecordedAction(context);
730
+ if (lastAction !== undefined) lines.push(`Last action: ${lastAction.summary}`);
731
+ if (observation.action !== undefined && completedObservations(context) < config.maxChecks) {
732
+ lines.push(`Next action: ${observation.action.nextAction}`);
733
+ }
734
+ if (observation.route === "wait" && completedObservations(context) < config.maxChecks) {
735
+ lines.push(`Next check: ${config.everyMinutes} minutes`);
736
+ }
737
+ if (completedObservations(context) >= config.maxChecks && observation.route !== "stop") {
738
+ lines.push(`Safety limit: reached ${config.maxChecks} observations`);
401
739
  }
402
- if (check.route === "continue" && completedChecks(context) >= config.maxChecks) {
403
- suffix.push(`Reached the ${config.maxChecks}-check safety limit.`);
404
- }
405
- const suffixText = suffix.filter(Boolean).join("\n");
406
- if (suffixText.length === 0) return check.report;
407
- const reportBudget = Math.max(1, MAX_REPORT_CHARS - suffixText.length - 1);
408
- const report =
409
- check.report.length <= reportBudget
410
- ? check.report
411
- : `${check.report.slice(0, Math.max(0, reportBudget - 1))}…`;
412
- return `${report}\n${suffixText}`;
740
+ lines.push(`Status: ${observation.report}`);
741
+ const message = lines.join("\n");
742
+ return message.length <= MAX_REPORT_CHARS
743
+ ? message
744
+ : `${message.slice(0, MAX_REPORT_CHARS - 1)}…`;
745
+ }
746
+
747
+ function previousActionPrompt(context: WorkflowNodeContext): string {
748
+ const actionRecord = latestRecordedAction(context);
749
+ return actionRecord === undefined
750
+ ? "There is no previous completed action."
751
+ : `Previous action result: ${JSON.stringify(actionRecord)}`;
413
752
  }
414
753
 
415
754
  const monitorWorkflow: WorkflowDefinition = defineWorkflow({
@@ -427,67 +766,25 @@ const monitorWorkflow: WorkflowDefinition = defineWorkflow({
427
766
  startAt: "prepare",
428
767
  maxSteps: 200_000,
429
768
  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");
769
+ planChange: includeWorkflow(planChangeWorkflow, {
770
+ input: ({ outputs }): NormalizedPlanChangeInput => {
771
+ const request = actionFrom(outputs);
772
+ if (request.kind !== "repair") throw new Error("monitor repair action is missing");
464
773
  return {
465
- task: repair.problem,
466
- plan: design.output.plan,
467
- ...(config.repair?.repository !== undefined
468
- ? { repository: config.repair.repository }
774
+ task: request.nextAction,
775
+ scope: authorityScope(request),
776
+ constraints: authorityConstraints(request),
777
+ ...(request.authority.repository !== undefined
778
+ ? { repository: request.authority.repository }
469
779
  : {}),
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,
780
+ newEvidence: {
781
+ incomplete: request.incomplete,
782
+ evidence: request.evidence,
783
+ failureId: request.failureId,
784
+ targetStateId: request.targetStateId,
785
+ defect: request.defect,
786
+ },
787
+ approval: request.authority.repairApproval,
491
788
  };
492
789
  },
493
790
  }),
@@ -495,138 +792,143 @@ const monitorWorkflow: WorkflowDefinition = defineWorkflow({
495
792
  workflow: "autoimplement",
496
793
  contract: autoimplementWorkflow,
497
794
  input: ({ outputs }) => {
498
- const config = configFrom(outputs);
499
- const repair = (outputs.check as MonitorCheck).repair;
795
+ const request = actionFrom(outputs);
500
796
  const documented = currentRepairPlan(outputs);
501
- if (repair === undefined) throw new Error("monitor repair details are missing");
502
- const request: AutoimplementInput = {
503
- task: repair.problem,
797
+ if (request.kind !== "repair") throw new Error("monitor repair action is missing");
798
+ const implementationInput: AutoimplementInput = {
799
+ task: request.nextAction,
504
800
  plan: documented.plan,
505
801
  documentation: {
506
802
  status: "current",
507
803
  planDigest: documented.planDigest,
508
804
  documents: documented.documents,
509
805
  },
510
- ...(config.repair?.scope !== undefined ? { scope: config.repair.scope } : {}),
511
- ...(config.repair?.constraints !== undefined
512
- ? { constraints: config.repair.constraints }
806
+ scope: authorityScope(request),
807
+ constraints: authorityConstraints(request),
808
+ ...(request.authority.repository !== undefined
809
+ ? { repository: request.authority.repository }
513
810
  : {}),
514
- ...(config.repair?.repository !== undefined
515
- ? { repository: config.repair.repository }
811
+ ...(request.authority.baseBranch !== undefined
812
+ ? { baseBranch: request.authority.baseBranch }
516
813
  : {}),
517
- ...(config.repair?.baseBranch !== undefined
518
- ? { baseBranch: config.repair.baseBranch }
519
- : {}),
520
- merge: config.repair?.merge === true,
814
+ approval: request.authority.repairApproval,
815
+ merge: request.authority.merge,
521
816
  };
522
- return request;
817
+ return implementationInput;
523
818
  },
524
819
  }),
525
820
  },
526
821
  nodes: {
527
822
  prepare: compute({ run: ({ input }) => prepareMonitorInput(input) }),
528
- check: agent({
529
- statusDetail: "checking monitored target",
530
- timeoutMs: ({ outputs }) => configFrom(outputs).checkTimeoutMinutes * 60_000,
823
+ observe: agent({
824
+ statusDetail: "observing monitored goal",
825
+ timeoutMs: DEFAULT_CHECK_TIMEOUT_MINUTES * 60_000,
531
826
  prompt: (context) => {
532
827
  const config = configFrom(context.outputs);
533
- const previous = context.outputs.check as MonitorCheck | undefined;
828
+ const previous = context.outputs.observe as MonitorObservation | undefined;
534
829
  const priorEstimate = context.outputs.estimate as MonitorEstimate | undefined;
535
830
  return [
536
- `Perform monitoring check ${completedChecks(context) + 1} of at most ${config.maxChecks}.`,
537
- `Task: ${config.task}`,
831
+ `Perform read-only observation ${completedObservations(context) + 1} of at most ${config.maxChecks}.`,
832
+ `Goal and authority: ${config.task}`,
538
833
  `Stop when: ${config.stopWhen}`,
834
+ "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.",
835
+ "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.",
836
+ "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.",
837
+ "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.",
838
+ "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.",
539
839
  previous === undefined
540
- ? "There is no previous observation."
541
- : `Previous accepted observation: ${previous.observation}`,
840
+ ? "There is no previous accepted observation."
841
+ : `Previous accepted observation: ${JSON.stringify(previous)}`,
842
+ previousActionPrompt(context),
542
843
  priorEstimate?.tracks.length
543
844
  ? `Previous progress: ${formatProgressReport(priorEstimate.tracks.map((track) => track.estimate))}`
544
845
  : "There is no previous measured progress.",
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.",
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.",
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.",
551
- config.repair === undefined
552
- ? "Choose route continue or stop."
553
- : "Choose route continue, repair, or stop.",
554
846
  ].join("\n\n");
555
847
  },
556
848
  expectedOutput:
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),
849
+ '{ "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" }',
850
+ validate: validateMonitorObservation,
560
851
  }),
852
+ guard: compute({ run: guardObservation }),
561
853
  estimate: compute({ run: ({ outputs }) => estimateTracks(outputs) }),
562
854
  publish_progress: action({
563
855
  statusDetail: "publishing monitor progress",
564
856
  run: async ({ outputs, publishUpdate, signal }) => {
565
- const check = outputs.check as MonitorCheck;
566
- if (check.progress === undefined) return { published: 0 };
567
- for (const track of check.progress.tracks) {
857
+ const observation = currentObservation(outputs);
858
+ if (observation.progress === undefined) return { published: 0 };
859
+ for (const track of observation.progress.tracks) {
568
860
  await waitForUpdateSlot(signal);
569
861
  await publishUpdate({ type: "progress", key: track.key, data: track.data });
570
862
  }
571
- return { published: check.progress.tracks.length };
863
+ return { published: observation.progress.tracks.length };
572
864
  },
573
865
  }),
574
866
  report: notify({
575
867
  statusDetail: "queueing monitor update",
576
- message: (context) => reportMessage(context),
868
+ message: reportMessage,
577
869
  kind: "progress",
578
870
  }),
579
871
  decide: compute({
580
872
  run: (context) => {
581
- const check = context.outputs.check as MonitorCheck;
873
+ const observation = currentObservation(context.outputs);
582
874
  const config = configFrom(context.outputs);
583
- const checks = completedChecks(context);
584
- if (check.route === "stop") return { route: "stop", reason: check.reason, checks };
875
+ const checks = completedObservations(context);
876
+ if (observation.route === "stop")
877
+ return { route: "stop", reason: observation.reason, checks };
585
878
  if (checks >= config.maxChecks) {
586
879
  return {
587
880
  route: "stop",
588
- reason: `Reached the ${config.maxChecks}-check safety limit.`,
881
+ reason: `Reached the ${config.maxChecks}-observation safety limit.`,
589
882
  checks,
590
883
  };
591
884
  }
592
- if (check.route === "repair") return { route: "repair", reason: check.reason, checks };
593
- return { route: "continue", reason: check.reason, checks };
885
+ if (observation.route === "wait")
886
+ return { route: "wait", reason: observation.reason, checks };
887
+ const request = actionFrom(context.outputs);
888
+ return { route: request.kind, reason: observation.reason, checks };
594
889
  },
595
890
  }),
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
- }),
891
+ act: agent({
892
+ statusDetail: "performing authorized monitor action",
893
+ timeoutMs: DEFAULT_CHECK_TIMEOUT_MINUTES * 60_000,
894
+ prompt: ({ outputs }) => {
895
+ const request = actionFrom(outputs);
896
+ if (request.kind === "repair") throw new Error("repair must use the composed repair path");
897
+ return [
898
+ `Perform this one ${request.kind} action with normal tools: ${request.nextAction}`,
899
+ `Incomplete work: ${request.incomplete}`,
900
+ `Evidence: ${JSON.stringify(request.evidence)}`,
901
+ `Authorization: ${JSON.stringify(request.authority)}`,
902
+ `Cost safety: ${JSON.stringify(request.cost)}`,
903
+ `Defect safety: ${JSON.stringify(request.defect)}`,
904
+ `Verification: ${request.verification}`,
905
+ "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.",
906
+ ].join("\n\n");
907
+ },
908
+ expectedOutput:
909
+ '{ "status": "succeeded" | "failed" | "blocked", "summary": "action performed and real result", "evidence": {}, "verification": "verification performed", "failureId": "unchanged failure ID", "targetStateId": "unchanged target-state ID" }',
910
+ validate: (output, context) =>
911
+ validateMonitorActionResult(output, actionFrom(context.outputs)),
610
912
  }),
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 };
913
+ repairComplete: compute({
914
+ run: ({ outputs }): RecordedAction => {
915
+ const request = actionFrom(outputs);
916
+ if (request.kind !== "repair") throw new Error("monitor repair action is missing");
917
+ return {
918
+ status: "succeeded",
919
+ summary: request.nextAction,
920
+ evidence: outputs.implementation,
921
+ verification: request.verification,
922
+ failureId: request.failureId,
923
+ targetStateId: request.targetStateId,
924
+ };
623
925
  },
624
926
  }),
625
927
  repairBlocked: compute({
626
928
  run: (context) => ({
627
929
  reason: repairBlockedReason(context.outputs),
628
- observation: (context.outputs.check as MonitorCheck).observation,
629
- checks: completedChecks(context),
930
+ observation: currentObservation(context.outputs).observation,
931
+ checks: completedObservations(context),
630
932
  reported: true,
631
933
  }),
632
934
  }),
@@ -635,11 +937,11 @@ const monitorWorkflow: WorkflowDefinition = defineWorkflow({
635
937
  kind: "final",
636
938
  message: ({ outputs }) => {
637
939
  const result = outputs.repairBlocked as { reason: string };
638
- return `Automatic repair stopped: ${result.reason}`;
940
+ return `Monitor repair stopped: ${result.reason}`;
639
941
  },
640
942
  }),
641
943
  schedule: action({
642
- statusDetail: "scheduling next monitor check",
944
+ statusDetail: "scheduling next monitor observation",
643
945
  run: async ({ outputs, publishUpdate }) => {
644
946
  const config = configFrom(outputs);
645
947
  const lastCheckAt = new Date().toISOString();
@@ -660,7 +962,7 @@ const monitorWorkflow: WorkflowDefinition = defineWorkflow({
660
962
  },
661
963
  }),
662
964
  sleep: shell({
663
- statusDetail: "waiting for next monitor check",
965
+ statusDetail: "waiting for next monitor observation",
664
966
  timeoutMs: MAX_INTERVAL_MINUTES * 60_000 + NODE_TIMEOUT_MARGIN_MS,
665
967
  exec: ({ outputs }) => {
666
968
  const sleepMs = configFrom(outputs).everyMinutes * 60_000;
@@ -675,26 +977,24 @@ const monitorWorkflow: WorkflowDefinition = defineWorkflow({
675
977
  }),
676
978
  finish: compute({
677
979
  run: ({ outputs }) => {
678
- const check = outputs.check as MonitorCheck;
980
+ const observation = currentObservation(outputs);
679
981
  const decision = outputs.decide as { reason?: string; checks?: number } | undefined;
680
982
  const repair = outputs.repairBlocked as { reason?: string } | undefined;
681
983
  return {
682
- reason: repair?.reason ?? decision?.reason ?? check.reason,
683
- observation: check.observation,
984
+ reason: repair?.reason ?? decision?.reason ?? observation.reason,
985
+ observation: observation.observation,
986
+ goalState: repair === undefined ? observation.goalState : "blocked",
987
+ workState: observation.workState,
684
988
  checks: decision?.checks ?? 1,
685
989
  reported: true,
686
- ...(outputs.implementation !== undefined
687
- ? { repair: outputs.implementation }
688
- : repair !== undefined
689
- ? { repair }
690
- : {}),
691
990
  };
692
991
  },
693
992
  }),
694
993
  },
695
994
  edges: [
696
- { from: "prepare", to: "check" },
697
- { from: "check", to: "estimate" },
995
+ { from: "prepare", to: "observe" },
996
+ { from: "observe", to: "guard" },
997
+ { from: "guard", to: "estimate" },
698
998
  { from: "estimate", to: "publish_progress" },
699
999
  { from: "publish_progress", to: "report" },
700
1000
  { from: "report", to: "decide" },
@@ -702,34 +1002,25 @@ const monitorWorkflow: WorkflowDefinition = defineWorkflow({
702
1002
  from: "decide",
703
1003
  switch: {
704
1004
  on: "$.route",
705
- cases: { stop: "finish", continue: "schedule", repair: "repairGuard" },
1005
+ cases: {
1006
+ stop: "finish",
1007
+ wait: "schedule",
1008
+ advance: "act",
1009
+ recover: "act",
1010
+ repair: "planChange",
1011
+ },
706
1012
  },
707
1013
  },
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" },
1014
+ { from: "act", to: "observe" },
1015
+ { from: "planChange.ready", to: "implementation" },
1016
+ { from: "planChange.blocked", to: "repairBlocked" },
1017
+ { from: "implementation.completed", to: "repairComplete" },
728
1018
  { from: "implementation.blocked", to: "repairBlocked" },
1019
+ { from: "repairComplete", to: "observe" },
729
1020
  { from: "repairBlocked", to: "repairReport" },
730
1021
  { from: "repairReport", to: "finish" },
731
1022
  { from: "schedule", to: "sleep" },
732
- { from: "sleep", to: "check" },
1023
+ { from: "sleep", to: "observe" },
733
1024
  ],
734
1025
  });
735
1026