@voybio/ace-swarm 2.4.8 → 2.4.10

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 (187) hide show
  1. package/CHANGELOG.md +52 -14
  2. package/README.md +14 -10
  3. package/assets/.agents/ACE/ACE_coders/AGENTS.md +23 -30
  4. package/assets/.agents/ACE/ACE_coders/INSTRUCTIONS.md +21 -30
  5. package/assets/.agents/skills/landing-review-watcher/SKILL.md +1 -1
  6. package/assets/.agents/skills/learn-from-work/SKILL.md +41 -0
  7. package/assets/agent-state/ACE_WORKFLOW.md +25 -2
  8. package/assets/agent-state/MODULES/schemas/ACE_RUNTIME_PROFILE.schema.json +143 -2
  9. package/assets/agent-state/MODULES/schemas/RUNTIME_EXECUTOR_SESSION_REGISTRY.schema.json +2 -2
  10. package/assets/agent-state/MODULES/schemas/SWARM_HANDOFF.schema.json +10 -0
  11. package/assets/eval-battery/battery.json +1 -1
  12. package/assets/instructions/ACE_Coder.instructions.md +9 -13
  13. package/assets/scripts/ace-hook-dispatch.mjs +16 -411
  14. package/assets/tasks/SWARM_HANDOFF.example_ui_to_coders.json +1 -2
  15. package/assets/vendor/README.md +9 -0
  16. package/assets/vendor/vericify-reader.mjs +272 -0
  17. package/assets/vendor/vericify-reader.provenance.json +10 -0
  18. package/dist/ace-autonomy.d.ts +58 -0
  19. package/dist/ace-autonomy.js +211 -6
  20. package/dist/ace-internal-tools.d.ts +1 -0
  21. package/dist/ace-internal-tools.js +3 -3
  22. package/dist/ace-server-instructions.d.ts +3 -3
  23. package/dist/ace-server-instructions.js +46 -192
  24. package/dist/cli.js +44 -6
  25. package/dist/experience-reflector.d.ts +7 -0
  26. package/dist/experience-reflector.js +39 -0
  27. package/dist/goal-engine.d.ts +64 -7
  28. package/dist/goal-engine.js +197 -41
  29. package/dist/handoff-registry.d.ts +10 -0
  30. package/dist/handoff-registry.js +59 -0
  31. package/dist/helpers/artifacts.d.ts +6 -2
  32. package/dist/helpers/artifacts.js +8 -6
  33. package/dist/helpers/bootstrap.js +13 -20
  34. package/dist/helpers/path-utils.js +2 -0
  35. package/dist/helpers/store-resolution.js +18 -3
  36. package/dist/helpers/workspace-root.d.ts +2 -0
  37. package/dist/helpers/workspace-root.js +9 -0
  38. package/dist/helpers.d.ts +1 -0
  39. package/dist/helpers.js +1 -0
  40. package/dist/hermes/bridge-protocol.d.ts +1 -1
  41. package/dist/hermes/session-manager.d.ts +17 -0
  42. package/dist/hermes/session-manager.js +67 -14
  43. package/dist/host-goal-engine.d.ts +138 -0
  44. package/dist/host-goal-engine.js +793 -0
  45. package/dist/host-goal-status.d.ts +113 -0
  46. package/dist/host-goal-status.js +175 -0
  47. package/dist/host-learning.d.ts +68 -0
  48. package/dist/host-learning.js +115 -0
  49. package/dist/host-observation.d.ts +12 -0
  50. package/dist/host-observation.js +91 -0
  51. package/dist/host-plan-revision.d.ts +14 -0
  52. package/dist/host-plan-revision.js +101 -0
  53. package/dist/host-verification-job.d.ts +12 -0
  54. package/dist/host-verification-job.js +183 -0
  55. package/dist/host-work-context.d.ts +12 -0
  56. package/dist/host-work-context.js +8 -0
  57. package/dist/job-scheduler.d.ts +58 -13
  58. package/dist/job-scheduler.js +298 -48
  59. package/dist/job-worker.d.ts +3 -1
  60. package/dist/job-worker.js +10 -3
  61. package/dist/kanban.d.ts +4 -1
  62. package/dist/kanban.js +44 -16
  63. package/dist/learned-artifacts.d.ts +64 -0
  64. package/dist/learned-artifacts.js +321 -0
  65. package/dist/learning-controls.d.ts +142 -0
  66. package/dist/learning-controls.js +135 -0
  67. package/dist/learning-input-context.d.ts +9 -0
  68. package/dist/learning-input-context.js +62 -0
  69. package/dist/learning-lifecycle.d.ts +10 -0
  70. package/dist/learning-lifecycle.js +11 -0
  71. package/dist/learning-overlays.d.ts +3 -0
  72. package/dist/learning-overlays.js +17 -0
  73. package/dist/learning-policy.d.ts +40 -0
  74. package/dist/learning-policy.js +38 -0
  75. package/dist/learning-records.d.ts +35 -0
  76. package/dist/learning-records.js +17 -0
  77. package/dist/learning-scorecard.d.ts +82 -0
  78. package/dist/learning-scorecard.js +86 -0
  79. package/dist/learning-serving.d.ts +29 -0
  80. package/dist/learning-serving.js +134 -0
  81. package/dist/linked-work.d.ts +9 -0
  82. package/dist/linked-work.js +28 -0
  83. package/dist/local-model-runtime.js +1 -1
  84. package/dist/mcp-command-progress.d.ts +16 -0
  85. package/dist/mcp-command-progress.js +70 -0
  86. package/dist/model-bridge.d.ts +14 -13
  87. package/dist/model-bridge.js +73 -41
  88. package/dist/orchestrator-supervisor.d.ts +111 -6
  89. package/dist/orchestrator-supervisor.js +67 -43
  90. package/dist/plan-proposal.js +3 -3
  91. package/dist/playbook-curator.d.ts +1 -0
  92. package/dist/playbook-curator.js +11 -9
  93. package/dist/process-identity.d.ts +8 -0
  94. package/dist/process-identity.js +38 -0
  95. package/dist/progress-command.d.ts +34 -0
  96. package/dist/progress-command.js +153 -0
  97. package/dist/prompts.js +15 -15
  98. package/dist/resources.js +2 -2
  99. package/dist/runtime-command.d.ts +8 -1
  100. package/dist/runtime-command.js +48 -7
  101. package/dist/runtime-executor.js +33 -11
  102. package/dist/runtime-profile-syntax.d.ts +6 -0
  103. package/dist/runtime-profile-syntax.js +121 -0
  104. package/dist/runtime-profile.d.ts +37 -5
  105. package/dist/runtime-profile.js +36 -129
  106. package/dist/runtime-tool-specs.js +1 -1
  107. package/dist/safe-edit.d.ts +7 -3
  108. package/dist/safe-edit.js +40 -47
  109. package/dist/schemas.js +4 -2
  110. package/dist/server.d.ts +2 -16
  111. package/dist/server.js +14 -20
  112. package/dist/shared.d.ts +2 -0
  113. package/dist/shared.js +2 -0
  114. package/dist/skill-auditor.d.ts +19 -0
  115. package/dist/skill-auditor.js +32 -2
  116. package/dist/skill-catalog.d.ts +4 -1
  117. package/dist/skill-catalog.js +6 -5
  118. package/dist/store/ace-packed-store.d.ts +5 -1
  119. package/dist/store/ace-packed-store.js +33 -1
  120. package/dist/store/bootstrap-store.js +19 -12
  121. package/dist/store/cache-workspace.d.ts +8 -0
  122. package/dist/store/cache-workspace.js +94 -27
  123. package/dist/store/importer.js +11 -20
  124. package/dist/store/knowledge-bake.d.ts +3 -1
  125. package/dist/store/knowledge-bake.js +10 -1
  126. package/dist/store/legacy-vericify-posts.d.ts +8 -0
  127. package/dist/store/legacy-vericify-posts.js +76 -0
  128. package/dist/store/materializers/hook-context-materializer.d.ts +2 -1
  129. package/dist/store/materializers/hook-context-materializer.js +6 -2
  130. package/dist/store/materializers/host-file-materializer.js +2 -2
  131. package/dist/store/materializers/projection-manager.d.ts +4 -1
  132. package/dist/store/materializers/projection-manager.js +10 -3
  133. package/dist/store/materializers/todo-syncer.d.ts +39 -20
  134. package/dist/store/materializers/todo-syncer.js +208 -110
  135. package/dist/store/materializers/vericify-projector.js +15 -26
  136. package/dist/store/repositories/goal-repository.d.ts +51 -2
  137. package/dist/store/repositories/goal-repository.js +114 -48
  138. package/dist/store/repositories/playbook-repository.d.ts +41 -3
  139. package/dist/store/repositories/playbook-repository.js +61 -5
  140. package/dist/store/repositories/todo-repository.d.ts +8 -2
  141. package/dist/store/repositories/todo-repository.js +1 -1
  142. package/dist/store/repositories/vericify-repository.d.ts +4 -0
  143. package/dist/store/repositories/vericify-repository.js +46 -0
  144. package/dist/store/skills-install.d.ts +1 -0
  145. package/dist/store/skills-install.js +20 -16
  146. package/dist/store/source-preservation.d.ts +24 -0
  147. package/dist/store/source-preservation.js +79 -0
  148. package/dist/store/store-artifacts.js +8 -1
  149. package/dist/store/test-write-boundary.d.ts +16 -0
  150. package/dist/store/test-write-boundary.js +175 -0
  151. package/dist/store/types.d.ts +5 -1
  152. package/dist/store/write-coordinator.js +16 -0
  153. package/dist/task-plan-context.d.ts +11 -0
  154. package/dist/task-plan-context.js +59 -0
  155. package/dist/todo-state.d.ts +19 -9
  156. package/dist/todo-state.js +204 -321
  157. package/dist/tools-agent.d.ts +1 -1
  158. package/dist/tools-agent.js +38 -62
  159. package/dist/tools-files.d.ts +0 -3
  160. package/dist/tools-files.js +21 -11
  161. package/dist/tools-framework.js +16 -25
  162. package/dist/tools-goal.d.ts +2 -4
  163. package/dist/tools-goal.js +119 -16
  164. package/dist/tools-handoff.d.ts +3 -2
  165. package/dist/tools-handoff.js +53 -23
  166. package/dist/tools-lifecycle.js +1 -1
  167. package/dist/tools-memory.d.ts +0 -3
  168. package/dist/tools-memory.js +51 -6
  169. package/dist/tools-scheduler.js +20 -17
  170. package/dist/tools-todo.js +160 -148
  171. package/dist/vericify-bridge.d.ts +8 -0
  172. package/dist/vericify-bridge.js +18 -0
  173. package/dist/vericify-context.d.ts +4 -0
  174. package/dist/vericify-context.js +26 -5
  175. package/dist/vericify-native.d.ts +101 -0
  176. package/dist/vericify-native.js +221 -0
  177. package/dist/verification-worker.d.ts +2 -0
  178. package/dist/verification-worker.js +31 -0
  179. package/docs/mcp-tool-reference.md +50 -10
  180. package/docs/troubleshooting.md +29 -0
  181. package/package.json +19 -6
  182. package/scripts/dev/run-tests-isolated.mjs +165 -0
  183. package/scripts/dev/run_hermes_crispr_live.mjs +1 -0
  184. package/scripts/dev/test-isolation-guard.mjs +20 -0
  185. package/scripts/dev/test-preload.mjs +153 -0
  186. package/scripts/dev/test-source-inventory.mjs +31 -0
  187. package/docs/development.md +0 -46
@@ -2,7 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
2
2
  import { appendFileSync, existsSync, mkdirSync, writeFileSync, realpathSync } from "node:fs";
3
3
  import { dirname, isAbsolute, resolve, sep } from "node:path";
4
4
  import { executeAceInternalTool, listAceInternalToolCatalog, } from "./ace-internal-tools.js";
5
- import { appendVericifyProcessPostSafe, deriveWorkspaceVericifyRunRef, } from "./vericify-bridge.js";
5
+ import { appendAutomaticVericifyProcessPostSafe, deriveWorkspaceVericifyRunRef, } from "./vericify-bridge.js";
6
6
  import { buildToolPlan, renderAceContext } from "./ace-context.js";
7
7
  import { appendRunLedgerEntrySafe } from "./run-ledger.js";
8
8
  import { appendStatusEventSafe } from "./status-events.js";
@@ -479,7 +479,7 @@ function isRetryableProviderError(error) {
479
479
  function formatToolFeedback(results) {
480
480
  return [
481
481
  "Tool execution results:",
482
- ...results.map((result) => `- ${result.tool}: ${result.ok ? "ok" : "error"} — ${result.summary}`),
482
+ ...results.map((result) => `- ${result.tool}: ${result.reason_code === "still_running" ? "still_running" : result.ok ? "ok" : "error"} — ${result.summary}`),
483
483
  "Continue with another JSON response.",
484
484
  ].join("\n");
485
485
  }
@@ -527,31 +527,67 @@ function withUsageTotals(result, usageTotals) {
527
527
  function contextOverflowSuspected(usage, numCtx) {
528
528
  return Boolean(usage && usage.prompt_tokens >= Math.floor(numCtx * 0.98));
529
529
  }
530
- const DEFAULT_TOOL_DEADLINE_MS = 120_000;
531
530
  function toolDeadlineMs() {
532
531
  const raw = Number(process.env.ACE_TOOL_DEADLINE_MS);
533
- return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : DEFAULT_TOOL_DEADLINE_MS;
532
+ return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : undefined;
534
533
  }
535
- /**
536
- * Per-tool-call deadline (E12). A hung validation/test subprocess must never
537
- * wedge a turn indefinitely — and under Promise.all one stuck call also holds
538
- * every parallel sibling's feedback hostage. On timeout we resolve a classified
539
- * `tool_timeout` failure that then feeds the WS-C doom-loop brake.
540
- */
534
+ const pendingToolOperations = new Map();
535
+ function canonicalToolInput(value) {
536
+ if (Array.isArray(value))
537
+ return value.map(canonicalToolInput);
538
+ if (value && typeof value === "object")
539
+ return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, entry]) => [key, canonicalToolInput(entry)]));
540
+ return value;
541
+ }
542
+ /** No implicit cutoff. An explicit deadline requests cancellation; unconfirmed
543
+ * cancellation is reported as still running. Identical retries collect the same
544
+ * operation instead of launching a duplicate side effect. */
541
545
  export async function executeToolWithDeadline(tool, args, sessionId, opts, deadlineMs, exec = executeAceInternalTool) {
546
+ const key = JSON.stringify([sessionId, opts.workspace_path, tool, canonicalToolInput(args)]);
547
+ let operation = pendingToolOperations.get(key);
548
+ const collecting = Boolean(operation);
549
+ if (!operation) {
550
+ const controller = new AbortController();
551
+ const forwardAbort = () => controller.abort(opts.signal?.reason);
552
+ if (opts.signal?.aborted)
553
+ forwardAbort();
554
+ else
555
+ opts.signal?.addEventListener("abort", forwardAbort, { once: true });
556
+ const created = { id: randomUUID(), controller, state: "running", promise: Promise.resolve() };
557
+ created.promise = Promise.resolve().then(() => exec(tool, args, sessionId, { ...opts, signal: controller.signal })).then((value) => { created.state = "done"; return value; }, (error) => { created.state = "failed"; throw error; }).finally(() => opts.signal?.removeEventListener("abort", forwardAbort));
558
+ // Keep eventual failures observed while an explicit caller has stopped waiting.
559
+ void created.promise.catch(() => undefined);
560
+ operation = created;
561
+ pendingToolOperations.set(key, operation);
562
+ }
542
563
  let timer;
543
- const timeout = new Promise((resolve) => {
544
- timer = setTimeout(() => resolve({ rawToolResult: undefined, timedOut: true }), deadlineMs);
545
- timer.unref?.();
546
- });
564
+ const pending = Symbol("still-running");
547
565
  try {
548
- return await Promise.race([
549
- exec(tool, args, sessionId, opts).then((rawToolResult) => ({
550
- rawToolResult,
566
+ const value = deadlineMs === undefined || deadlineMs <= 0
567
+ ? await operation.promise
568
+ : await Promise.race([
569
+ operation.promise,
570
+ new Promise((resolve) => { timer = setTimeout(() => resolve(pending), deadlineMs); timer.unref?.(); }),
571
+ ]);
572
+ if (value === pending) {
573
+ operation.controller.abort(new Error(`Explicit tool deadline elapsed after ${deadlineMs}ms`));
574
+ return {
551
575
  timedOut: false,
552
- })),
553
- timeout,
554
- ]);
576
+ stillRunning: true,
577
+ operationId: operation.id,
578
+ rawToolResult: {
579
+ isError: false,
580
+ structuredContent: { status: "still_running", operation_id: operation.id, cancellation_requested: true },
581
+ content: [{ type: "text", text: `Operation ${operation.id} is still running after its explicit deadline; cancellation was requested but is not yet confirmed. Repeat this same tool call to collect its result without starting duplicate work.` }],
582
+ },
583
+ };
584
+ }
585
+ pendingToolOperations.delete(key);
586
+ return { rawToolResult: value, timedOut: false, operationId: collecting ? operation.id : undefined };
587
+ }
588
+ catch (error) {
589
+ pendingToolOperations.delete(key);
590
+ throw error;
555
591
  }
556
592
  finally {
557
593
  if (timer)
@@ -727,7 +763,7 @@ export class ModelBridge {
727
763
  session_id: this.bridgeId,
728
764
  role: options.role,
729
765
  });
730
- await appendVericifyProcessPostSafe({
766
+ await appendAutomaticVericifyProcessPostSafe({
731
767
  run_id: refs.run_id,
732
768
  branch_id: refs.branch_id,
733
769
  lane_id: refs.lane_id,
@@ -871,7 +907,7 @@ export class ModelBridge {
871
907
  // intra-turn runaway is bounded separately by the num_predict output cap.
872
908
  const textLoopCounts = new Map();
873
909
  try {
874
- await appendVericifyProcessPostSafe({
910
+ await appendAutomaticVericifyProcessPostSafe({
875
911
  run_id: refs.run_id,
876
912
  branch_id: refs.branch_id,
877
913
  lane_id: refs.lane_id,
@@ -881,7 +917,7 @@ export class ModelBridge {
881
917
  tool_refs: aceContext.tools.map((tool) => tool.name),
882
918
  });
883
919
  noteProgress("process_post", { kind: "intent" });
884
- for (let turn = 1; turn <= options.maxTurns; turn += 1) {
920
+ for (let turn = 1; options.maxTurns === 0 || turn <= options.maxTurns; turn += 1) {
885
921
  const compressed = compressConversationHistory(messages, numCtx);
886
922
  if (compressed.compressed) {
887
923
  messages.splice(0, messages.length, ...compressed.messages);
@@ -936,7 +972,7 @@ export class ModelBridge {
936
972
  }
937
973
  if (textLoopCount >= 4) {
938
974
  const summary = "Bridge blocked after repeating an identical response four times without progress.";
939
- await appendVericifyProcessPostSafe({
975
+ await appendAutomaticVericifyProcessPostSafe({
940
976
  run_id: refs.run_id,
941
977
  branch_id: refs.branch_id,
942
978
  lane_id: refs.lane_id,
@@ -1020,7 +1056,7 @@ export class ModelBridge {
1020
1056
  const summary = rawResponse.trim() || "Bridge completed.";
1021
1057
  options.onOutput?.(summary);
1022
1058
  noteProgress("output", { status: "complete", fallback: "plain_text" });
1023
- await appendVericifyProcessPostSafe({
1059
+ await appendAutomaticVericifyProcessPostSafe({
1024
1060
  run_id: refs.run_id,
1025
1061
  branch_id: refs.branch_id,
1026
1062
  lane_id: refs.lane_id,
@@ -1077,7 +1113,7 @@ export class ModelBridge {
1077
1113
  status: "parse_error",
1078
1114
  role_contract_violation: isContractViolation,
1079
1115
  });
1080
- await appendVericifyProcessPostSafe({
1116
+ await appendAutomaticVericifyProcessPostSafe({
1081
1117
  run_id: refs.run_id,
1082
1118
  branch_id: refs.branch_id,
1083
1119
  lane_id: refs.lane_id,
@@ -1146,14 +1182,8 @@ export class ModelBridge {
1146
1182
  noteProgress("tool_start", { tool: toolCall.tool });
1147
1183
  const deadlineMs = toolDeadlineMs();
1148
1184
  const outcome = await executeToolWithDeadline(toolCall.tool, args, sessionId, { workspace_path: options.workspace }, deadlineMs);
1149
- const result = outcome.timedOut
1150
- ? {
1151
- tool: toolCall.tool,
1152
- ok: false,
1153
- isError: true,
1154
- summary: `tool_timeout: ${toolCall.tool} exceeded ${deadlineMs}ms deadline`,
1155
- reason_code: "tool_timeout",
1156
- }
1185
+ const result = outcome.stillRunning
1186
+ ? { tool: toolCall.tool, ok: false, isError: false, summary: summarizeToolText(outcome.rawToolResult), reason_code: "still_running" }
1157
1187
  : truncateToolResult({
1158
1188
  tool: toolCall.tool,
1159
1189
  ok: !Boolean(outcome.rawToolResult?.isError),
@@ -1161,7 +1191,7 @@ export class ModelBridge {
1161
1191
  summary: summarizeToolText(outcome.rawToolResult),
1162
1192
  }, options.workspace);
1163
1193
  options.onToolResult?.(toolCall.tool, result);
1164
- noteProgress("tool_finish", { tool: toolCall.tool, ok: result.ok });
1194
+ noteProgress(result.reason_code === "still_running" ? "thinking" : "tool_finish", { tool: toolCall.tool, ok: result.ok });
1165
1195
  toolResults.push(result);
1166
1196
  if (result.ok) {
1167
1197
  const pathArg = typeof args.path === "string"
@@ -1187,6 +1217,8 @@ export class ModelBridge {
1187
1217
  toolCalls: executed,
1188
1218
  });
1189
1219
  for (const { toolCall, result } of executedPairs) {
1220
+ if (result.reason_code === "still_running")
1221
+ continue;
1190
1222
  const loopKey = toolDoomLoopKey(toolCall, result);
1191
1223
  const count = (toolDoomLoopCounts.get(loopKey) ?? 0) + 1;
1192
1224
  toolDoomLoopCounts.set(loopKey, count);
@@ -1205,7 +1237,7 @@ export class ModelBridge {
1205
1237
  }
1206
1238
  if (count >= 4) {
1207
1239
  const summary = `Bridge blocked after repeated identical tool result for ${toolCall.tool}.`;
1208
- await appendVericifyProcessPostSafe({
1240
+ await appendAutomaticVericifyProcessPostSafe({
1209
1241
  run_id: refs.run_id,
1210
1242
  branch_id: refs.branch_id,
1211
1243
  lane_id: refs.lane_id,
@@ -1251,7 +1283,7 @@ export class ModelBridge {
1251
1283
  const message = readEnvelopeText(envelope.message) || rawResponse.trim();
1252
1284
  options.onOutput?.(message);
1253
1285
  noteProgress("output", { status: "message" });
1254
- await appendVericifyProcessPostSafe({
1286
+ await appendAutomaticVericifyProcessPostSafe({
1255
1287
  run_id: refs.run_id,
1256
1288
  branch_id: refs.branch_id,
1257
1289
  lane_id: refs.lane_id,
@@ -1293,7 +1325,7 @@ export class ModelBridge {
1293
1325
  const message = readEnvelopeText(envelope.message) || "Additional operator input required.";
1294
1326
  options.onOutput?.(message);
1295
1327
  noteProgress("output", { status: "need_input" });
1296
- await appendVericifyProcessPostSafe({
1328
+ await appendAutomaticVericifyProcessPostSafe({
1297
1329
  run_id: refs.run_id,
1298
1330
  branch_id: refs.branch_id,
1299
1331
  lane_id: refs.lane_id,
@@ -1348,7 +1380,7 @@ export class ModelBridge {
1348
1380
  const driftSummary = `[output_drift_violation] ${role}: ${driftViolation}`;
1349
1381
  options.onOutput?.(driftSummary);
1350
1382
  noteProgress("output", { status: "output_drift_violation" });
1351
- await appendVericifyProcessPostSafe({
1383
+ await appendAutomaticVericifyProcessPostSafe({
1352
1384
  run_id: refs.run_id,
1353
1385
  branch_id: refs.branch_id,
1354
1386
  lane_id: refs.lane_id,
@@ -1372,7 +1404,7 @@ export class ModelBridge {
1372
1404
  // Clean output — accept the completion.
1373
1405
  options.onOutput?.(summary);
1374
1406
  noteProgress("output", { status: "complete" });
1375
- await appendVericifyProcessPostSafe({
1407
+ await appendAutomaticVericifyProcessPostSafe({
1376
1408
  run_id: refs.run_id,
1377
1409
  branch_id: refs.branch_id,
1378
1410
  lane_id: refs.lane_id,
@@ -1402,7 +1434,7 @@ export class ModelBridge {
1402
1434
  }
1403
1435
  }
1404
1436
  const summary = "Bridge stopped after reaching max turns.";
1405
- await appendVericifyProcessPostSafe({
1437
+ await appendAutomaticVericifyProcessPostSafe({
1406
1438
  run_id: refs.run_id,
1407
1439
  branch_id: refs.branch_id,
1408
1440
  lane_id: refs.lane_id,
@@ -1,7 +1,48 @@
1
+ export { getReadyTaskSteps, collectUpstreamOutputs } from "./task-plan-context.js";
2
+ import { type DispatchJobsResult } from "./job-scheduler.js";
1
3
  import type { BridgeResult } from "./model-bridge.js";
2
4
  import type { LocalModelRuntimeRepository } from "./store/repositories/local-model-runtime-repository.js";
3
5
  export type TaskStepStatus = "planned" | "running" | "done" | "failed" | "skipped" | "blocked";
4
6
  export type TaskPlanStatus = "planning" | "executing" | "reviewing" | "blocked" | "done" | "failed";
7
+ export interface ObservedRun {
8
+ record_id: string;
9
+ attribution: "Linked" | "Inferred" | "Ambiguous" | "Unknown";
10
+ command: string;
11
+ cwd?: string;
12
+ exit_code: number | null;
13
+ outcome: "ok" | "failed" | "unknown";
14
+ at: string;
15
+ output_tail?: string;
16
+ command_norm: string;
17
+ paths?: string[];
18
+ tool_use_id?: string;
19
+ session_id?: string;
20
+ }
21
+ export interface StepAttempt {
22
+ attempt: number;
23
+ status: "done" | "blocked" | "failed";
24
+ at: string;
25
+ summary: string;
26
+ checks?: Array<{
27
+ command: string;
28
+ cwd: string;
29
+ ok: boolean;
30
+ }>;
31
+ failure_signature?: string;
32
+ failure_excerpt?: string;
33
+ scope_snapshot?: {
34
+ role: string;
35
+ paths: string[];
36
+ task_tags: string[];
37
+ };
38
+ served_context?: {
39
+ items: Array<Pick<import('./learning-serving.js').ServedLearningItem, 'id' | 'kind' | 'scope'>>;
40
+ rendered_bytes: number;
41
+ };
42
+ evidence_refs: string[];
43
+ served?: string[];
44
+ observed_runs?: ObservedRun[];
45
+ }
5
46
  export interface TaskStep {
6
47
  step_id: string;
7
48
  role: string;
@@ -24,6 +65,8 @@ export interface TaskStep {
24
65
  expected_artifacts?: Array<{
25
66
  path: string;
26
67
  required?: boolean;
68
+ /** Required when this step completes, but not a final deliverable. */
69
+ transient?: boolean;
27
70
  evidence_ref_kind?: "artifact" | "diff" | "hash" | "test" | "gate";
28
71
  }>;
29
72
  allowed_tools?: string[];
@@ -34,6 +77,72 @@ export interface TaskStep {
34
77
  reason: string;
35
78
  evidence_ref: string;
36
79
  };
80
+ assignee?: string;
81
+ attempt?: number;
82
+ started_at?: string;
83
+ last_ace_call_at?: string;
84
+ waiting_on?: string;
85
+ result_digest?: string;
86
+ result_response?: unknown;
87
+ attempts_log?: StepAttempt[];
88
+ served_learning?: import('./learning-serving.js').ServedLearning;
89
+ learning_feedback?: Array<{
90
+ id: string;
91
+ helpful: boolean;
92
+ }>;
93
+ loaded_learning?: Array<{
94
+ id: string;
95
+ at: string;
96
+ }>;
97
+ learning_kind?: "meta" | "graduation";
98
+ learning_source?: string;
99
+ learning_trigger?: string;
100
+ learning_inputs?: import('./learning-input-context.js').LearningInputRefs;
101
+ learning_scope?: string;
102
+ learning_opened_at?: string;
103
+ learning_payload?: import('./learned-artifacts.js').LearnedPayload;
104
+ learning_result?: import('./learned-artifacts.js').LearningAcceptance;
105
+ attempt_history?: Array<Pick<TaskStep, 'attempt' | 'assignee' | 'status' | 'result_summary' | 'evidence' | 'artifacts' | 'result_digest' | 'result_response'>>;
106
+ evidence?: Array<{
107
+ ref: string;
108
+ kind: "command" | "artifact" | "diff" | "note";
109
+ provenance: "host" | "ace";
110
+ detail?: string;
111
+ ok?: boolean;
112
+ }>;
113
+ artifacts?: string[];
114
+ todo_id?: string;
115
+ required_observation?: "action" | "command_pass";
116
+ observation?: "available" | "pending" | "unavailable";
117
+ observation_window?: {
118
+ workspace_id: string;
119
+ session_id?: string;
120
+ started_at: string;
121
+ ended_at?: string;
122
+ };
123
+ observation_refs?: Array<{
124
+ id: string;
125
+ attribution: "Linked" | "Inferred" | "Ambiguous" | "Unknown";
126
+ kind: string;
127
+ result?: string;
128
+ run?: ObservedRun;
129
+ }>;
130
+ /** Additive acceptance metadata; saved result_response and original window remain immutable. */
131
+ observation_check?: {
132
+ attempt: number;
133
+ status: "pending" | "passed" | "unmet" | "unsupported";
134
+ deadline_at: string;
135
+ wait_ms: number;
136
+ poll_ms: number;
137
+ reason_code?: string;
138
+ };
139
+ observation_query?: Record<string, unknown>;
140
+ observation_coverage?: Record<string, unknown>;
141
+ verification_failure?: {
142
+ command: string;
143
+ output: string;
144
+ ref: string;
145
+ };
37
146
  }
38
147
  export interface TaskPlan {
39
148
  plan_id: string;
@@ -137,20 +246,16 @@ export declare function createTaskPlan(input: {
137
246
  execution_mode?: TaskPlan["execution_mode"];
138
247
  vcx_cursor?: string;
139
248
  }): TaskPlan;
140
- export declare function getReadyTaskSteps(plan: TaskPlan): TaskStep[];
141
249
  export declare function markTaskStepStatus(plan: TaskPlan, stepId: string, status: TaskStepStatus, patch?: Partial<Omit<TaskStep, "step_id" | "status">>): TaskPlan;
142
250
  export declare function applyBridgeResultToStep(plan: TaskPlan, stepId: string, result: BridgeResult): TaskPlan;
143
251
  export declare function amendTaskPlan(plan: TaskPlan, amendment: TaskPlanAmendment): TaskPlan;
144
- export declare function collectUpstreamOutputs(plan: TaskPlan, step: TaskStep): {
145
- step_id: string;
146
- result_summary: string;
147
- evidence_refs: string[];
148
- }[];
149
252
  export declare function applyBridgeResultToStepWithTransition(plan: TaskPlan, stepId: string, result: BridgeResult, repo?: LocalModelRuntimeRepository): Promise<TaskPlan>;
150
253
  export declare function amendTaskPlanWithTransition(plan: TaskPlan, amendment: TaskPlanAmendment, repo?: LocalModelRuntimeRepository, reason?: string, reason_code?: string): Promise<TaskPlan>;
151
254
  export declare function promoteParallelGroupToScheduler(plan: TaskPlan, parallelGroup: string, owner?: string): Promise<{
152
255
  plan: TaskPlan;
153
256
  job_ids: string[];
257
+ dispatch?: DispatchJobsResult;
258
+ blocked_reason?: string;
154
259
  }>;
155
260
  export declare function superviseTaskPlan(inputPlan: TaskPlan, hooks: SupervisorHooks): Promise<SupervisorRunResult>;
156
261
  //# sourceMappingURL=orchestrator-supervisor.d.ts.map
@@ -1,8 +1,10 @@
1
+ import { getReadyTaskSteps, collectUpstreamOutputs } from "./task-plan-context.js";
2
+ export { getReadyTaskSteps, collectUpstreamOutputs } from "./task-plan-context.js";
1
3
  import { existsSync } from "node:fs";
2
4
  import { dirname } from "node:path";
3
5
  import { randomUUID } from "node:crypto";
4
6
  import { dispatchJobs, enqueueJob, getJobQueuePath } from "./job-scheduler.js";
5
- import { appendVericifyProcessPostSafe, deriveWorkspaceVericifyRunRef, } from "./vericify-bridge.js";
7
+ import { appendAutomaticVericifyProcessPostSafe, deriveWorkspaceVericifyRunRef, } from "./vericify-bridge.js";
6
8
  function parseNumericStepId(stepId) {
7
9
  const numeric = Number(stepId.replace(/^step-/, ""));
8
10
  return Number.isFinite(numeric) && numeric > 0 ? numeric : 0;
@@ -90,17 +92,6 @@ export function createTaskPlan(input) {
90
92
  vcx_cursor: input.vcx_cursor,
91
93
  });
92
94
  }
93
- export function getReadyTaskSteps(plan) {
94
- const completed = new Set(plan.steps
95
- .filter((step) => step.status === "done" || step.status === "skipped")
96
- .map((step) => step.step_id));
97
- return plan.steps.filter((step) => {
98
- if (step.status !== "planned")
99
- return false;
100
- const dependencies = step.depends_on ?? [];
101
- return dependencies.every((dependency) => completed.has(dependency));
102
- });
103
- }
104
95
  export function markTaskStepStatus(plan, stepId, status, patch = {}) {
105
96
  return withDerivedStatus({
106
97
  ...plan,
@@ -212,25 +203,6 @@ export function amendTaskPlan(plan, amendment) {
212
203
  vcx_cursor: amendment.vcx_cursor ?? plan.vcx_cursor,
213
204
  });
214
205
  }
215
- export function collectUpstreamOutputs(plan, step) {
216
- const dependencyOutputs = (step.depends_on ?? [])
217
- .map((depId) => plan.steps.find((s) => s.step_id === depId))
218
- .filter((dep) => dep?.status === "done")
219
- .map((dep) => ({
220
- step_id: dep.step_id,
221
- result_summary: dep.result_summary ?? "",
222
- evidence_refs: dep.evidence_refs ?? [],
223
- }));
224
- const carriedOutputs = (step.upstream_outputs ?? []).filter((output) => Boolean(output) &&
225
- typeof output.step_id === "string" &&
226
- typeof output.result_summary === "string" &&
227
- Array.isArray(output.evidence_refs));
228
- const merged = new Map();
229
- for (const output of [...dependencyOutputs, ...carriedOutputs]) {
230
- merged.set(output.step_id, output);
231
- }
232
- return [...merged.values()];
233
- }
234
206
  export async function applyBridgeResultToStepWithTransition(plan, stepId, result, repo) {
235
207
  const updated = applyBridgeResultToStep(plan, stepId, result);
236
208
  if (repo && plan.plan_id) {
@@ -281,7 +253,10 @@ export async function promoteParallelGroupToScheduler(plan, parallelGroup, owner
281
253
  const jobIds = [];
282
254
  const nextSteps = cloneSteps(plan.steps);
283
255
  for (const step of readySteps) {
256
+ // Stable identity also makes retrying the original plan safe after a refused tick.
257
+ const requestedJobId = step.job_id ?? `PLANJOB-${plan.plan_id}-${step.step_id}`;
284
258
  const enqueued = await enqueueJob({
259
+ job_id: requestedJobId,
285
260
  kind: step.role,
286
261
  payload: {
287
262
  task: step.task,
@@ -295,7 +270,11 @@ export async function promoteParallelGroupToScheduler(plan, parallelGroup, owner
295
270
  ref: dependency,
296
271
  })) ?? [],
297
272
  });
298
- const jobId = enqueued.ack?.job_id;
273
+ const existingJob = enqueued.nack?.reason_code === "duplicate_job_id"
274
+ ? enqueued.queue.jobs.find((job) => job.job_id === requestedJobId &&
275
+ job.payload.plan_id === plan.plan_id && job.payload.step_id === step.step_id)
276
+ : undefined;
277
+ const jobId = enqueued.ack?.job_id ?? existingJob?.job_id;
299
278
  if (!jobId)
300
279
  continue;
301
280
  jobIds.push(jobId);
@@ -303,36 +282,70 @@ export async function promoteParallelGroupToScheduler(plan, parallelGroup, owner
303
282
  if (stepIndex >= 0) {
304
283
  nextSteps[stepIndex] = {
305
284
  ...nextSteps[stepIndex],
306
- status: "running",
307
285
  job_id: jobId,
308
286
  };
309
287
  }
310
288
  }
289
+ let dispatch;
290
+ let blockedReason;
311
291
  if (jobIds.length > 0) {
312
292
  const refs = deriveWorkspaceVericifyRunRef({
313
293
  session_id: plan.plan_id,
314
294
  role: "orchestrator",
315
295
  });
316
- await dispatchJobs({ owner });
317
- await appendVericifyProcessPostSafe({
296
+ dispatch = await dispatchJobs({ owner });
297
+ blockedReason = dispatch.error;
298
+ const promotedIds = new Set(jobIds);
299
+ for (const step of nextSteps) {
300
+ if (!step.job_id || !promotedIds.has(step.job_id))
301
+ continue;
302
+ const job = dispatch.queue.jobs.find((candidate) => candidate.job_id === step.job_id);
303
+ // A recorded assignment is not proof of dispatch. Keep queued steps retryable.
304
+ switch (job?.status) {
305
+ case "running":
306
+ step.status = "running";
307
+ break;
308
+ case "done":
309
+ step.status = "done";
310
+ break;
311
+ case "failed_terminal":
312
+ step.status = "failed";
313
+ break;
314
+ case "canceled":
315
+ step.status = "skipped";
316
+ break;
317
+ case "unknown_recovery":
318
+ step.status = "blocked";
319
+ break;
320
+ default: step.status = "planned";
321
+ }
322
+ step.blocked_reason = step.status === "planned" || step.status === "blocked"
323
+ ? dispatch.error ?? job?.error
324
+ : undefined;
325
+ }
326
+ await appendAutomaticVericifyProcessPostSafe({
318
327
  run_id: refs.run_id,
319
328
  branch_id: refs.branch_id,
320
329
  lane_id: refs.lane_id,
321
330
  agent_id: "agent-orchestrator",
322
- kind: "progress",
323
- summary: `Promoted parallel group '${parallelGroup}' to scheduler: ${jobIds.join(", ")}`,
331
+ kind: blockedReason ? "blocker" : "progress",
332
+ summary: blockedReason
333
+ ? `Parallel group '${parallelGroup}' queued; ${blockedReason}`
334
+ : `Promoted parallel group '${parallelGroup}' to scheduler: ${jobIds.join(", ")}; running=${nextSteps.filter((step) => step.job_id && promotedIds.has(step.job_id) && step.status === "running").length}`,
324
335
  tool_refs: ["enqueue_job", "dispatch_jobs"],
325
336
  evidence_refs: jobIds,
326
337
  });
327
338
  }
339
+ const promotedPlan = withDerivedStatus({
340
+ ...plan,
341
+ execution_mode: "scheduled",
342
+ steps: nextSteps,
343
+ });
328
344
  return {
329
- plan: withDerivedStatus({
330
- ...plan,
331
- execution_mode: "scheduled",
332
- steps: nextSteps,
333
- status: "executing",
334
- }),
345
+ plan: blockedReason ? { ...promotedPlan, status: "blocked" } : promotedPlan,
335
346
  job_ids: jobIds,
347
+ dispatch,
348
+ blocked_reason: blockedReason,
336
349
  };
337
350
  }
338
351
  export async function superviseTaskPlan(inputPlan, hooks) {
@@ -368,6 +381,17 @@ export async function superviseTaskPlan(inputPlan, hooks) {
368
381
  const promoted = await promoteParallelGroupToScheduler(plan, readyGroup);
369
382
  jobIds.push(...promoted.job_ids);
370
383
  plan = promoted.plan;
384
+ if (promoted.blocked_reason) {
385
+ await hooks.emitStatusEvent?.({ status: "blocked", summary: promoted.blocked_reason });
386
+ await hooks.contextSnapshot?.(plan);
387
+ return {
388
+ plan,
389
+ handoff_ids: handoffIds,
390
+ job_ids: jobIds,
391
+ circuit_opened: false,
392
+ blocked_reason: promoted.blocked_reason,
393
+ };
394
+ }
371
395
  break;
372
396
  }
373
397
  }
@@ -8,7 +8,7 @@ import { listAceInternalToolCatalog } from "./ace-internal-tools.js";
8
8
  import { runLocalModelTask } from "./local-model-runtime.js";
9
9
  import { listRuntimeToolSpecs } from "./runtime-tool-specs.js";
10
10
  import { resolveWorkspaceRoot, safeRead, safeWriteAsync } from "./helpers.js";
11
- import { appendVericifyProcessPostSafe } from "./vericify-bridge.js";
11
+ import { appendAutomaticVericifyProcessPostSafe } from "./vericify-bridge.js";
12
12
  import { withLocalModelRuntimeRepository } from "./store/repositories/local-model-runtime-repository.js";
13
13
  // ── In-memory proposal cache (plan_id → ProposePlanResult) ────────────────
14
14
  const _proposalCache = new Map();
@@ -1051,8 +1051,8 @@ export async function validatePlan(input) {
1051
1051
  catch {
1052
1052
  // Non-fatal
1053
1053
  }
1054
- // Emit Vericify process post
1055
- await appendVericifyProcessPostSafe({
1054
+ // Optional legacy summary; native Vericify owns observation capture.
1055
+ await appendAutomaticVericifyProcessPostSafe({
1056
1056
  run_id: "workspace:current",
1057
1057
  agent_id: "ace-planner",
1058
1058
  kind: "plan_quality_assessment",
@@ -4,6 +4,7 @@ export interface CuratorConfig extends PlaybookConfig {
4
4
  quarantine_harmful_min?: number;
5
5
  quarantine_retire_after_days?: number;
6
6
  workspaceRoot?: string;
7
+ emitEvent?: boolean;
7
8
  }
8
9
  export interface CurateResult {
9
10
  added: number;
@@ -1,7 +1,7 @@
1
1
  import { calculateJaccard, getShingles } from "./semantic-hash.js";
2
2
  import { appendStatusEventSafe } from "./status-events.js";
3
3
  function scopeEqualOrBroader(candidate, incoming) {
4
- return Object.entries(candidate).every(([key, value]) => value === undefined || incoming[key] === value);
4
+ return Object.entries(candidate).every(([key, value]) => value === undefined || JSON.stringify(incoming[key]) === JSON.stringify(value));
5
5
  }
6
6
  function mergeEvidence(a, b) {
7
7
  return [...new Set([...a, ...b])].slice(0, 5);
@@ -43,7 +43,7 @@ export async function curate(repo, ops, config = {}) {
43
43
  }
44
44
  const incomingShingles = getShingles(op.content, 3);
45
45
  const match = repo
46
- .listBullets({ section: op.section, status: "active" })
46
+ .listBullets({ section: op.section, ...(op.status === "quarantined" ? {} : { status: "active" }) })
47
47
  .filter((bullet) => scopeEqualOrBroader(bullet.scope, op.scope))
48
48
  .map((bullet) => ({
49
49
  bullet,
@@ -55,6 +55,7 @@ export async function curate(repo, ops, config = {}) {
55
55
  ...match.bullet,
56
56
  observed_count: match.bullet.observed_count + 1,
57
57
  evidence_refs: mergeEvidence(match.bullet.evidence_refs, op.evidence_refs),
58
+ evidence_snapshots: [...new Map([...(match.bullet.evidence_snapshots ?? []), ...(op.evidence_snapshots ?? [])].map(e => [e.id, e])).values()],
58
59
  });
59
60
  result.merged += 1;
60
61
  }
@@ -69,13 +70,14 @@ export async function curate(repo, ops, config = {}) {
69
70
  }
70
71
  result.pruned += pruneOverflow(repo, config);
71
72
  const eventWorkspaceRoot = config.workspaceRoot ?? repo.root;
72
- await appendStatusEventSafe({
73
- event_type: "PLAYBOOK_CURATED",
74
- source_module: "capability-build",
75
- status: "done",
76
- summary: `Playbook curated: added=${result.added} merged=${result.merged} tagged=${result.tagged} quarantined=${result.quarantined} pruned=${result.pruned}`,
77
- payload: { ...result },
78
- }, eventWorkspaceRoot ? { workspaceRoot: eventWorkspaceRoot } : {});
73
+ if (config.emitEvent !== false && Object.values(result).some(n => n > 0))
74
+ await appendStatusEventSafe({
75
+ event_type: "PLAYBOOK_CURATED",
76
+ source_module: "capability-build",
77
+ status: "done",
78
+ summary: `Playbook curated: added=${result.added} merged=${result.merged} tagged=${result.tagged} quarantined=${result.quarantined} pruned=${result.pruned}`,
79
+ payload: { ...result },
80
+ }, eventWorkspaceRoot ? { workspaceRoot: eventWorkspaceRoot } : {});
79
81
  return result;
80
82
  }
81
83
  function pruneOverflow(repo, config) {
@@ -0,0 +1,8 @@
1
+ export interface ProcessIdentity {
2
+ pid: number;
3
+ started_at: string;
4
+ hostname: string;
5
+ }
6
+ export declare function readProcessIdentity(pid: number): ProcessIdentity | undefined;
7
+ export declare function probeProcess(identity: ProcessIdentity): 'alive' | 'dead' | 'unknown';
8
+ //# sourceMappingURL=process-identity.d.ts.map