@caupulican/pi-adaptative 0.81.41 → 0.81.42

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 (237) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/text.py +5 -1
  3. package/dist/bundled-resources/runtimes/pi-shell-engine/context.py +3 -3
  4. package/dist/bundled-resources/runtimes/pi-shell-engine/errors.py +8 -0
  5. package/dist/bundled-resources/runtimes/pi-shell-engine/exec.py +26 -3
  6. package/dist/bundled-resources/runtimes/pi-shell-engine/expand.py +3 -0
  7. package/dist/bundled-resources/runtimes/pi-shell-engine/main.py +4 -1
  8. package/dist/bundled-resources/runtimes/pi-shell-engine/state.py +2 -1
  9. package/dist/bundled-resources/runtimes/pi-shell-engine/tokens.py +2 -0
  10. package/dist/core/agent-paths.d.ts +32 -0
  11. package/dist/core/agent-paths.d.ts.map +1 -1
  12. package/dist/core/agent-paths.js +65 -0
  13. package/dist/core/agent-paths.js.map +1 -1
  14. package/dist/core/agent-session-contracts.d.ts +19 -1
  15. package/dist/core/agent-session-contracts.d.ts.map +1 -1
  16. package/dist/core/agent-session-contracts.js.map +1 -1
  17. package/dist/core/agent-session.d.ts.map +1 -1
  18. package/dist/core/agent-session.js +4 -9
  19. package/dist/core/agent-session.js.map +1 -1
  20. package/dist/core/autonomy/contracts.d.ts +2 -0
  21. package/dist/core/autonomy/contracts.d.ts.map +1 -1
  22. package/dist/core/autonomy/contracts.js.map +1 -1
  23. package/dist/core/autonomy/envelope-enforcement.d.ts.map +1 -1
  24. package/dist/core/autonomy/envelope-enforcement.js +4 -9
  25. package/dist/core/autonomy/envelope-enforcement.js.map +1 -1
  26. package/dist/core/autonomy/lane-tool-surface.d.ts +3 -1
  27. package/dist/core/autonomy/lane-tool-surface.d.ts.map +1 -1
  28. package/dist/core/autonomy/lane-tool-surface.js +17 -4
  29. package/dist/core/autonomy/lane-tool-surface.js.map +1 -1
  30. package/dist/core/autonomy/path-scope.d.ts +14 -0
  31. package/dist/core/autonomy/path-scope.d.ts.map +1 -1
  32. package/dist/core/autonomy/path-scope.js +37 -6
  33. package/dist/core/autonomy/path-scope.js.map +1 -1
  34. package/dist/core/background-lane-controller.d.ts +27 -1
  35. package/dist/core/background-lane-controller.d.ts.map +1 -1
  36. package/dist/core/background-lane-controller.js +32 -1
  37. package/dist/core/background-lane-controller.js.map +1 -1
  38. package/dist/core/delegation/managed-lane-controller.d.ts +11 -1
  39. package/dist/core/delegation/managed-lane-controller.d.ts.map +1 -1
  40. package/dist/core/delegation/managed-lane-controller.js +84 -41
  41. package/dist/core/delegation/managed-lane-controller.js.map +1 -1
  42. package/dist/core/delegation/session-worker-claim.d.ts.map +1 -1
  43. package/dist/core/delegation/session-worker-claim.js +4 -3
  44. package/dist/core/delegation/session-worker-claim.js.map +1 -1
  45. package/dist/core/delegation/worker-action-journal.d.ts +73 -0
  46. package/dist/core/delegation/worker-action-journal.d.ts.map +1 -0
  47. package/dist/core/delegation/worker-action-journal.js +297 -0
  48. package/dist/core/delegation/worker-action-journal.js.map +1 -0
  49. package/dist/core/delegation/worker-actions.d.ts +36 -1
  50. package/dist/core/delegation/worker-actions.d.ts.map +1 -1
  51. package/dist/core/delegation/worker-actions.js +171 -46
  52. package/dist/core/delegation/worker-actions.js.map +1 -1
  53. package/dist/core/delegation/worker-agent-control-coordinator.d.ts +75 -0
  54. package/dist/core/delegation/worker-agent-control-coordinator.d.ts.map +1 -0
  55. package/dist/core/delegation/worker-agent-control-coordinator.js +218 -0
  56. package/dist/core/delegation/worker-agent-control-coordinator.js.map +1 -0
  57. package/dist/core/delegation/worker-agent-control.d.ts +66 -0
  58. package/dist/core/delegation/worker-agent-control.d.ts.map +1 -0
  59. package/dist/core/delegation/worker-agent-control.js +163 -0
  60. package/dist/core/delegation/worker-agent-control.js.map +1 -0
  61. package/dist/core/delegation/worker-attempt-executor.d.ts +80 -0
  62. package/dist/core/delegation/worker-attempt-executor.d.ts.map +1 -0
  63. package/dist/core/delegation/worker-attempt-executor.js +343 -0
  64. package/dist/core/delegation/worker-attempt-executor.js.map +1 -0
  65. package/dist/core/delegation/worker-claim.d.ts +30 -0
  66. package/dist/core/delegation/worker-claim.d.ts.map +1 -1
  67. package/dist/core/delegation/worker-claim.js +205 -53
  68. package/dist/core/delegation/worker-claim.js.map +1 -1
  69. package/dist/core/delegation/worker-conversation-store.d.ts +116 -0
  70. package/dist/core/delegation/worker-conversation-store.d.ts.map +1 -0
  71. package/dist/core/delegation/worker-conversation-store.js +442 -0
  72. package/dist/core/delegation/worker-conversation-store.js.map +1 -0
  73. package/dist/core/delegation/worker-delegation-controller.d.ts +21 -5
  74. package/dist/core/delegation/worker-delegation-controller.d.ts.map +1 -1
  75. package/dist/core/delegation/worker-delegation-controller.js +386 -251
  76. package/dist/core/delegation/worker-delegation-controller.js.map +1 -1
  77. package/dist/core/delegation/worker-delegation-request.d.ts +14 -0
  78. package/dist/core/delegation/worker-delegation-request.d.ts.map +1 -1
  79. package/dist/core/delegation/worker-delegation-request.js.map +1 -1
  80. package/dist/core/delegation/worker-dispatch-scheduler.d.ts +4 -2
  81. package/dist/core/delegation/worker-dispatch-scheduler.d.ts.map +1 -1
  82. package/dist/core/delegation/worker-dispatch-scheduler.js +16 -6
  83. package/dist/core/delegation/worker-dispatch-scheduler.js.map +1 -1
  84. package/dist/core/delegation/worker-execution-policy.d.ts +2 -1
  85. package/dist/core/delegation/worker-execution-policy.d.ts.map +1 -1
  86. package/dist/core/delegation/worker-execution-policy.js +5 -7
  87. package/dist/core/delegation/worker-execution-policy.js.map +1 -1
  88. package/dist/core/delegation/worker-lifecycle.d.ts +44 -1
  89. package/dist/core/delegation/worker-lifecycle.d.ts.map +1 -1
  90. package/dist/core/delegation/worker-lifecycle.js +120 -0
  91. package/dist/core/delegation/worker-lifecycle.js.map +1 -1
  92. package/dist/core/delegation/worker-process-owner.d.ts +18 -0
  93. package/dist/core/delegation/worker-process-owner.d.ts.map +1 -0
  94. package/dist/core/delegation/worker-process-owner.js +54 -0
  95. package/dist/core/delegation/worker-process-owner.js.map +1 -0
  96. package/dist/core/delegation/worker-profile-resolver.d.ts +5 -1
  97. package/dist/core/delegation/worker-profile-resolver.d.ts.map +1 -1
  98. package/dist/core/delegation/worker-profile-resolver.js +8 -0
  99. package/dist/core/delegation/worker-profile-resolver.js.map +1 -1
  100. package/dist/core/delegation/worker-recovery-coordinator.d.ts +54 -0
  101. package/dist/core/delegation/worker-recovery-coordinator.d.ts.map +1 -0
  102. package/dist/core/delegation/worker-recovery-coordinator.js +181 -0
  103. package/dist/core/delegation/worker-recovery-coordinator.js.map +1 -0
  104. package/dist/core/delegation/worker-resource-catalog.d.ts +32 -0
  105. package/dist/core/delegation/worker-resource-catalog.d.ts.map +1 -0
  106. package/dist/core/delegation/worker-resource-catalog.js +116 -0
  107. package/dist/core/delegation/worker-resource-catalog.js.map +1 -0
  108. package/dist/core/delegation/worker-resource-materializer.d.ts +47 -0
  109. package/dist/core/delegation/worker-resource-materializer.d.ts.map +1 -0
  110. package/dist/core/delegation/worker-resource-materializer.js +137 -0
  111. package/dist/core/delegation/worker-resource-materializer.js.map +1 -0
  112. package/dist/core/delegation/worker-runner.d.ts +3 -1
  113. package/dist/core/delegation/worker-runner.d.ts.map +1 -1
  114. package/dist/core/delegation/worker-runner.js +189 -54
  115. package/dist/core/delegation/worker-runner.js.map +1 -1
  116. package/dist/core/delegation/worker-terminal-finalizer.d.ts +23 -0
  117. package/dist/core/delegation/worker-terminal-finalizer.d.ts.map +1 -0
  118. package/dist/core/delegation/worker-terminal-finalizer.js +12 -0
  119. package/dist/core/delegation/worker-terminal-finalizer.js.map +1 -0
  120. package/dist/core/delegation/worker-write-reservation-coordinator.d.ts +38 -0
  121. package/dist/core/delegation/worker-write-reservation-coordinator.d.ts.map +1 -0
  122. package/dist/core/delegation/worker-write-reservation-coordinator.js +107 -0
  123. package/dist/core/delegation/worker-write-reservation-coordinator.js.map +1 -0
  124. package/dist/core/delegation/worker-write-reservation.d.ts +90 -0
  125. package/dist/core/delegation/worker-write-reservation.d.ts.map +1 -0
  126. package/dist/core/delegation/worker-write-reservation.js +348 -0
  127. package/dist/core/delegation/worker-write-reservation.js.map +1 -0
  128. package/dist/core/models/model-endpoint.d.ts +3 -0
  129. package/dist/core/models/model-endpoint.d.ts.map +1 -0
  130. package/dist/core/models/model-endpoint.js +11 -0
  131. package/dist/core/models/model-endpoint.js.map +1 -0
  132. package/dist/core/orchestration/agent-resume.d.ts +2 -0
  133. package/dist/core/orchestration/agent-resume.d.ts.map +1 -1
  134. package/dist/core/orchestration/agent-resume.js +7 -0
  135. package/dist/core/orchestration/agent-resume.js.map +1 -1
  136. package/dist/core/orchestration/attempt-usage.d.ts +28 -0
  137. package/dist/core/orchestration/attempt-usage.d.ts.map +1 -0
  138. package/dist/core/orchestration/attempt-usage.js +168 -0
  139. package/dist/core/orchestration/attempt-usage.js.map +1 -0
  140. package/dist/core/orchestration/capability-gateway.d.ts +19 -1
  141. package/dist/core/orchestration/capability-gateway.d.ts.map +1 -1
  142. package/dist/core/orchestration/capability-gateway.js +128 -28
  143. package/dist/core/orchestration/capability-gateway.js.map +1 -1
  144. package/dist/core/orchestration/contracts.d.ts +35 -0
  145. package/dist/core/orchestration/contracts.d.ts.map +1 -1
  146. package/dist/core/orchestration/contracts.js +17 -0
  147. package/dist/core/orchestration/contracts.js.map +1 -1
  148. package/dist/core/orchestration/delegation-ledger.d.ts +17 -2
  149. package/dist/core/orchestration/delegation-ledger.d.ts.map +1 -1
  150. package/dist/core/orchestration/delegation-ledger.js +78 -3
  151. package/dist/core/orchestration/delegation-ledger.js.map +1 -1
  152. package/dist/core/orchestration/event-store.d.ts +1 -0
  153. package/dist/core/orchestration/event-store.d.ts.map +1 -1
  154. package/dist/core/orchestration/event-store.js +78 -38
  155. package/dist/core/orchestration/event-store.js.map +1 -1
  156. package/dist/core/orchestration/profile-registry.d.ts.map +1 -1
  157. package/dist/core/orchestration/profile-registry.js +81 -41
  158. package/dist/core/orchestration/profile-registry.js.map +1 -1
  159. package/dist/core/orchestration/profile-store.d.ts.map +1 -1
  160. package/dist/core/orchestration/profile-store.js +18 -4
  161. package/dist/core/orchestration/profile-store.js.map +1 -1
  162. package/dist/core/orchestration/risk-budget.d.ts +2 -0
  163. package/dist/core/orchestration/risk-budget.d.ts.map +1 -1
  164. package/dist/core/orchestration/risk-budget.js +26 -0
  165. package/dist/core/orchestration/risk-budget.js.map +1 -1
  166. package/dist/core/orchestration/task-runtime.d.ts +13 -1
  167. package/dist/core/orchestration/task-runtime.d.ts.map +1 -1
  168. package/dist/core/orchestration/task-runtime.js +127 -16
  169. package/dist/core/orchestration/task-runtime.js.map +1 -1
  170. package/dist/core/orchestration/worker-execution-contract.d.ts +3 -1
  171. package/dist/core/orchestration/worker-execution-contract.d.ts.map +1 -1
  172. package/dist/core/orchestration/worker-execution-contract.js +75 -23
  173. package/dist/core/orchestration/worker-execution-contract.js.map +1 -1
  174. package/dist/core/orchestration/worker-result-adapter.d.ts +9 -3
  175. package/dist/core/orchestration/worker-result-adapter.d.ts.map +1 -1
  176. package/dist/core/orchestration/worker-result-adapter.js +167 -14
  177. package/dist/core/orchestration/worker-result-adapter.js.map +1 -1
  178. package/dist/core/reflection-controller.d.ts +1 -1
  179. package/dist/core/reflection-controller.d.ts.map +1 -1
  180. package/dist/core/reflection-controller.js +90 -54
  181. package/dist/core/reflection-controller.js.map +1 -1
  182. package/dist/core/research/evidence-bundle.d.ts +20 -0
  183. package/dist/core/research/evidence-bundle.d.ts.map +1 -1
  184. package/dist/core/research/evidence-bundle.js +173 -69
  185. package/dist/core/research/evidence-bundle.js.map +1 -1
  186. package/dist/core/runtime-builder.d.ts +7 -0
  187. package/dist/core/runtime-builder.d.ts.map +1 -1
  188. package/dist/core/runtime-builder.js +15 -1
  189. package/dist/core/runtime-builder.js.map +1 -1
  190. package/dist/core/session-artifact-bundle.d.ts +35 -0
  191. package/dist/core/session-artifact-bundle.d.ts.map +1 -0
  192. package/dist/core/session-artifact-bundle.js +61 -0
  193. package/dist/core/session-artifact-bundle.js.map +1 -0
  194. package/dist/core/tools/delegate-status.d.ts +6 -1
  195. package/dist/core/tools/delegate-status.d.ts.map +1 -1
  196. package/dist/core/tools/delegate-status.js +100 -18
  197. package/dist/core/tools/delegate-status.js.map +1 -1
  198. package/dist/core/tools/delegate.d.ts +9 -1
  199. package/dist/core/tools/delegate.d.ts.map +1 -1
  200. package/dist/core/tools/delegate.js +323 -61
  201. package/dist/core/tools/delegate.js.map +1 -1
  202. package/dist/core/tools/run-process.d.ts +1 -1
  203. package/dist/core/tools/run-process.d.ts.map +1 -1
  204. package/dist/core/tools/run-process.js +69 -9
  205. package/dist/core/tools/run-process.js.map +1 -1
  206. package/dist/core/tools/shell-contract-router.d.ts.map +1 -1
  207. package/dist/core/tools/shell-contract-router.js +12 -5
  208. package/dist/core/tools/shell-contract-router.js.map +1 -1
  209. package/dist/core/util/bounded-file.d.ts +15 -0
  210. package/dist/core/util/bounded-file.d.ts.map +1 -0
  211. package/dist/core/util/bounded-file.js +120 -0
  212. package/dist/core/util/bounded-file.js.map +1 -0
  213. package/dist/core/util/bounded-jsonl.d.ts.map +1 -1
  214. package/dist/core/util/bounded-jsonl.js +119 -9
  215. package/dist/core/util/bounded-jsonl.js.map +1 -1
  216. package/dist/core/util/bounded-value.d.ts +3 -0
  217. package/dist/core/util/bounded-value.d.ts.map +1 -0
  218. package/dist/core/util/bounded-value.js +12 -0
  219. package/dist/core/util/bounded-value.js.map +1 -0
  220. package/dist/core/util/value-guards.d.ts +2 -0
  221. package/dist/core/util/value-guards.d.ts.map +1 -1
  222. package/dist/core/util/value-guards.js +5 -0
  223. package/dist/core/util/value-guards.js.map +1 -1
  224. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  225. package/dist/modes/interactive/components/session-selector.js +27 -44
  226. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  227. package/docs/tool-repair.md +3 -0
  228. package/docs/windows.md +4 -2
  229. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  230. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  231. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  232. package/examples/extensions/sandbox/package-lock.json +2 -2
  233. package/examples/extensions/sandbox/package.json +1 -1
  234. package/examples/extensions/with-deps/package-lock.json +2 -2
  235. package/examples/extensions/with-deps/package.json +1 -1
  236. package/npm-shrinkwrap.json +12 -12
  237. package/package.json +4 -4
@@ -1,43 +1,67 @@
1
- import path from "node:path";
1
+ import { randomUUID } from "node:crypto";
2
2
  import { getPrivateLaneDeniedPaths } from "../autonomy/lane-private-paths.js";
3
3
  import { createLaneToolSurface } from "../autonomy/lane-tool-surface.js";
4
- import { safeRealpathSync } from "../autonomy/path-scope.js";
5
4
  import { appendLaneRecordSnapshot } from "../autonomy/session-lane-record.js";
6
- import { composeSubagentSystemPrompt } from "../autonomy/subagent-prompt.js";
7
5
  import { AUTONOMY_TELEMETRY_EVENT_TYPES } from "../autonomy/telemetry-events.js";
8
6
  import { deriveModelCapabilityProfile } from "../model-capability.js";
7
+ import { isLoopbackModelEndpoint } from "../models/model-endpoint.js";
8
+ import { providerUsageFromAttemptUsage } from "../orchestration/attempt-usage.js";
9
9
  import { createWorkerExecutionContract, verifierWorkerExecutionContract, } from "../orchestration/worker-execution-contract.js";
10
- import { createWorkerResultContract } from "../orchestration/worker-result-adapter.js";
11
10
  import { registerInFlightWork } from "../reload-blockers.js";
12
11
  import { applyWorkerActions } from "./worker-actions.js";
12
+ import { WorkerAgentControlCoordinator } from "./worker-agent-control-coordinator.js";
13
+ import { createWorkerAttemptExecutor } from "./worker-attempt-executor.js";
14
+ import { WorkerConversationStore, } from "./worker-conversation-store.js";
13
15
  import { WorkerDispatchScheduler } from "./worker-dispatch-scheduler.js";
14
16
  import { buildWorkerExecutionPlan, compileWorkerExecutionGrant, narrowWorkerExecutionPlan, workerExecutionAuthorityFromPlan, } from "./worker-execution-policy.js";
17
+ import { createLocalWorkerProcessOwnerId } from "./worker-process-owner.js";
15
18
  import { WorkerProfileResolver } from "./worker-profile-resolver.js";
16
- import { runWorker } from "./worker-runner.js";
19
+ import { WorkerRecoveryCoordinator } from "./worker-recovery-coordinator.js";
20
+ import { selectWorkerResourcePointers } from "./worker-resource-catalog.js";
21
+ import { materializeWorkerResourceBundle } from "./worker-resource-materializer.js";
22
+ import { finalizeWorkerClaim } from "./worker-terminal-finalizer.js";
23
+ import { WorkerWriteReservationCoordinator } from "./worker-write-reservation-coordinator.js";
17
24
  export function isLocalExecutionModel(model) {
18
25
  if (model.provider === "ollama" || model.provider === "transformers" || model.provider === "llama-cpp") {
19
26
  return true;
20
27
  }
21
- try {
22
- const hostname = new URL(model.baseUrl).hostname.toLowerCase();
23
- return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
24
- }
25
- catch {
26
- return false;
27
- }
28
+ return isLoopbackModelEndpoint(model.baseUrl);
29
+ }
30
+ function workerConversationRetentionPolicy(model, settingsManager) {
31
+ const settings = settingsManager.getCompactionSettings();
32
+ const contextWindow = Math.floor(model.contextWindow ?? 0);
33
+ if (!settings.enabled || contextWindow < 4)
34
+ return undefined;
35
+ const reserveTokens = Math.min(settings.reserveTokens, Math.floor(contextWindow * 0.25));
36
+ const reserveTrigger = contextWindow - reserveTokens;
37
+ const fractionalTrigger = settings.triggerPercent > 0 && settings.triggerPercent < 1
38
+ ? Math.floor(contextWindow * settings.triggerPercent)
39
+ : reserveTrigger;
40
+ const modelTrigger = model.autoCompactionTriggerTokens && model.autoCompactionTriggerTokens > 0
41
+ ? Math.floor(model.autoCompactionTriggerTokens)
42
+ : reserveTrigger;
43
+ const maxContextTokens = Math.min(reserveTrigger, fractionalTrigger, modelTrigger);
44
+ if (maxContextTokens < 2)
45
+ return undefined;
46
+ return {
47
+ maxContextTokens,
48
+ keepRecentTokens: Math.max(1, Math.min(settings.keepRecentTokens, Math.floor(contextWindow * 0.5), maxContextTokens - 1)),
49
+ };
28
50
  }
29
51
  export class WorkerDelegationController {
30
52
  deps;
31
53
  workerAbort = new AbortController();
32
54
  lifecycle;
33
55
  profileResolver;
34
- queueRecovered = false;
35
- recoveringDurableState = false;
36
- verificationRecoveryFailures = new Map();
56
+ recovery;
37
57
  notifications;
38
58
  scheduler;
39
59
  laneAbortControllers = new Map();
60
+ /** Sole logical-agent control/mailbox owner; execution only calls its narrow delivery hooks. */
61
+ agentControl;
40
62
  publishedTerminalAttemptIds = new Set();
63
+ conversations = new WorkerConversationStore();
64
+ writeReservations;
41
65
  inFlightLedgers = new Map();
42
66
  constructor(deps, notifications, lifecycle) {
43
67
  this.deps = deps;
@@ -51,6 +75,7 @@ export class WorkerDelegationController {
51
75
  run: (request, record) => this.runOnce(request, undefined, record),
52
76
  cancel: (laneId, reasonCode) => {
53
77
  try {
78
+ this.writeReservations.release(laneId);
54
79
  const terminal = this.getWorkerLifecycle().cancel(laneId, reasonCode);
55
80
  if (terminal)
56
81
  this.publishTerminalRecord(terminal);
@@ -61,6 +86,45 @@ export class WorkerDelegationController {
61
86
  },
62
87
  warn: (message) => this.safeWarn(message),
63
88
  });
89
+ this.recovery = new WorkerRecoveryCoordinator({
90
+ lifecycle: this.lifecycle,
91
+ scheduler: this.scheduler,
92
+ recoverWriteReservations: () => this.writeReservations.recoverProvenStale(),
93
+ publishTerminalRecord: (record) => this.publishTerminalRecord(record),
94
+ dispatchVerification: (recovery) => this.dispatchRecoveredVerification(recovery),
95
+ warn: (message) => this.safeWarn(message),
96
+ });
97
+ this.agentControl = new WorkerAgentControlCoordinator({
98
+ agentDir: this.deps.getAgentDir(),
99
+ parentSessionId: this.deps.getSessionId(),
100
+ processOwnerId: createLocalWorkerProcessOwnerId(process.pid, randomUUID()),
101
+ isControlAvailable: () => this.deps.isDelegateToolActive(),
102
+ getLifecycle: () => this.getWorkerLifecycle(),
103
+ recoveredRequest: (attempt) => this.recovery.recoveredRequest(attempt),
104
+ run: (request, record) => this.runOnce(request, undefined, record),
105
+ scheduler: this.scheduler,
106
+ statusChanged: () => this.notifications.statusChanged(),
107
+ abortLane: (laneId, reasonCode) => this.laneAbortControllers.get(laneId)?.abort(reasonCode),
108
+ cancelLane: (laneId, reasonCode) => {
109
+ this.scheduler.dropQueued(laneId);
110
+ this.writeReservations.release(laneId);
111
+ const terminal = this.getWorkerLifecycle().cancel(laneId, reasonCode);
112
+ if (terminal)
113
+ this.publishTerminalRecord(terminal);
114
+ return terminal;
115
+ },
116
+ });
117
+ this.writeReservations = new WorkerWriteReservationCoordinator({
118
+ agentDir: this.deps.getAgentDir(),
119
+ getCwd: () => this.deps.getCwd(),
120
+ getParentSessionId: () => this.deps.getSessionId(),
121
+ ownerId: this.agentControl.getProcessOwnerId(),
122
+ drainQueuedWorkers: () => {
123
+ if (!this.deps.isDisposed())
124
+ this.scheduler.drain(true);
125
+ },
126
+ warn: (message) => this.safeWarn(message),
127
+ });
64
128
  }
65
129
  safeWarn(message) {
66
130
  try {
@@ -70,6 +134,14 @@ export class WorkerDelegationController {
70
134
  // Disposal and recovery diagnostics must never throw.
71
135
  }
72
136
  }
137
+ /** Narrow model-facing control port. It owns control state; this controller only composes it. */
138
+ getAgentControl() {
139
+ return this.agentControl;
140
+ }
141
+ /** The coordinator's process identity is also the durable execution lease owner. */
142
+ getAgentControlProcessOwnerId() {
143
+ return this.agentControl.getProcessOwnerId();
144
+ }
73
145
  recordTerminal(record) {
74
146
  if (record.status === "queued" || record.status === "running")
75
147
  return;
@@ -86,7 +158,7 @@ export class WorkerDelegationController {
86
158
  }
87
159
  getRecords() {
88
160
  if (this.deps.isDelegateToolActive?.())
89
- this.recoverDurableQueue();
161
+ this.recovery.recover();
90
162
  return this.lifecycle.getRecords();
91
163
  }
92
164
  /** Process-local worker records for the shared terminal notifier; never triggers durable recovery. */
@@ -98,10 +170,18 @@ export class WorkerDelegationController {
98
170
  }
99
171
  abort() {
100
172
  this.workerAbort.abort();
173
+ // Bound attempts have an authoritative transcript and agent identity. A normal owner-session
174
+ // shutdown is an execution interruption, not an explicit worker cancellation: fence it into
175
+ // suspended state before the abort continuation can observe the signal.
176
+ const suspendedAttemptIds = new Set(this.lifecycle.suspendBoundInProcessAttemptsForRestart(this.agentControl.getProcessOwnerId()));
101
177
  for (const record of this.lifecycle.getRecords()) {
102
178
  if (record.status !== "queued" && record.status !== "running")
103
179
  continue;
104
180
  const ledger = this.inFlightLedgers.get(record.laneId);
181
+ if (ledger && suspendedAttemptIds.has(ledger.handle.attemptId)) {
182
+ this.inFlightLedgers.delete(record.laneId);
183
+ continue;
184
+ }
105
185
  if (!ledger) {
106
186
  try {
107
187
  const canceled = this.lifecycle.cancel(record.laneId, "session_disposed");
@@ -116,7 +196,8 @@ export class WorkerDelegationController {
116
196
  }
117
197
  this.inFlightLedgers.delete(record.laneId);
118
198
  try {
119
- const spend = ledger.getSpend();
199
+ const usage = ledger.getUsage();
200
+ const reportedUsage = providerUsageFromAttemptUsage(usage);
120
201
  const reportId = `worker:${this.deps.getSessionId()}:${record.laneId}`;
121
202
  const claim = {
122
203
  requestId: ledger.request.id,
@@ -126,31 +207,24 @@ export class WorkerDelegationController {
126
207
  usageReportId: reportId,
127
208
  createdAt: new Date().toISOString(),
128
209
  };
129
- const canceled = this.getWorkerLifecycle().finish(createWorkerResultContract({
210
+ const canceled = finalizeWorkerClaim(this.getWorkerLifecycle(), {
130
211
  handle: ledger.handle,
131
212
  claim,
132
213
  accepted: false,
133
- costUsd: spend?.cost.total ?? 0,
214
+ costUsd: usage.costUsd,
134
215
  cwd: this.deps.getCwd(),
135
- ...(spend
136
- ? {
137
- inputTokens: spend.input,
138
- outputTokens: spend.output,
139
- totalTokens: spend.totalTokens,
140
- }
141
- : {}),
142
- wallClockMs: Date.now() - ledger.startedAt,
143
- toolCalls: ledger.getToolCalls(),
216
+ inputTokens: usage.inputTokens,
217
+ outputTokens: usage.outputTokens,
218
+ totalTokens: reportedUsage.totalTokens,
219
+ wallClockMs: usage.activeWallClockMs,
220
+ toolCalls: usage.toolCalls,
144
221
  reasonCode: "session_disposed",
145
- }));
222
+ }).record;
146
223
  this.publishTerminalRecord(canceled);
147
- // Bounded honesty: spend may be incomplete (it lands only when the isolated completion
148
- // returns, which a mid-flight abort preempts) — record what `getSpend()` knows. Same
149
- // deterministic reportId scheme as the normal path, so a later duplicate report (there
150
- // is none in practice here, since the lane is now terminal) stays idempotent.
224
+ // The terminal result and owner usage report share the same fenced cumulative snapshot.
151
225
  this.deps.saveWorkerClaimSnapshot(claim, ledger.request);
152
- if (spend && (spend.cost.total > 0 || spend.totalTokens > 0)) {
153
- this.deps.addSpawnedUsage(spend, { label: "worker-delegation", reportId });
226
+ if (reportedUsage.cost.total > 0 || reportedUsage.totalTokens > 0) {
227
+ this.deps.addSpawnedUsage(reportedUsage, { label: "worker-delegation", reportId });
154
228
  }
155
229
  }
156
230
  catch (error) {
@@ -158,6 +232,7 @@ export class WorkerDelegationController {
158
232
  }
159
233
  }
160
234
  this.scheduler.cancelQueued();
235
+ this.writeReservations.dispose();
161
236
  }
162
237
  getWorkerLifecycle() {
163
238
  return this.lifecycle;
@@ -169,6 +244,7 @@ export class WorkerDelegationController {
169
244
  agentDir: this.deps.getAgentDir(),
170
245
  cwd: this.deps.getCwd(),
171
246
  getSettingsManager: () => this.deps.getSettingsManager(),
247
+ getResourceLoader: () => this.deps.getResourceLoader(),
172
248
  getModelRegistry: () => this.deps.getModelRegistry(),
173
249
  isModelExhausted: (model) => this.deps.isModelExhausted(model),
174
250
  getActiveOrchestrationProfile: () => this.deps.getActiveOrchestrationProfile?.(),
@@ -191,66 +267,23 @@ export class WorkerDelegationController {
191
267
  this.publishTerminalRecord(record);
192
268
  }
193
269
  }
194
- recoverDurableQueue() {
195
- if (this.recoveringDurableState)
196
- return;
197
- this.recoveringDurableState = true;
198
- const lifecycle = this.getWorkerLifecycle();
199
- try {
200
- if (!this.queueRecovered) {
201
- for (const { attempt, record, verificationOfTaskId } of lifecycle.recoverQueued()) {
202
- const request = {
203
- instructions: attempt.dispatch.instructions,
204
- profileId: attempt.dispatch.profileId,
205
- ...(verificationOfTaskId ? { verificationOfTaskId } : {}),
206
- };
207
- this.scheduler.enqueue(record, request, true, verificationOfTaskId !== undefined);
208
- }
209
- this.queueRecovered = true;
210
- }
211
- for (const recovery of lifecycle.getPendingVerificationRecoveries()) {
212
- if (recovery.action === "reconcile") {
213
- try {
214
- this.publishTerminalRecord(lifecycle.reconcileVerification(recovery));
215
- this.verificationRecoveryFailures.delete(recovery.subjectTaskId);
216
- }
217
- catch (error) {
218
- this.safeWarn(`Failed to reconcile recovered verification for ${recovery.subjectTaskId}: ${error instanceof Error ? error.message : String(error)}`);
219
- }
220
- continue;
221
- }
222
- const legacyImplementation = recovery.verifierExecutionContract
223
- ? undefined
224
- : this.resolveWorkerAdmission({
225
- instructions: recovery.summary,
226
- profileId: recovery.implementationProfileId,
227
- });
228
- const verifierProfileId = recovery.verifierExecutionContract?.worker.profile.profileId ??
229
- (legacyImplementation?.ok ? legacyImplementation.verifierShipment?.profile.profileId : undefined);
230
- const started = verifierProfileId
231
- ? this.startInternal(this.buildVerifierRequest({
232
- subjectTaskId: recovery.subjectTaskId,
233
- verifierProfileId,
234
- summary: recovery.summary,
235
- artifactUris: recovery.artifactUris,
236
- }), recovery.verifierExecutionContract)
237
- : { started: false, skipReason: "independent_verifier_unavailable" };
238
- if (started.started) {
239
- this.verificationRecoveryFailures.delete(recovery.subjectTaskId);
240
- continue;
241
- }
242
- if (this.verificationRecoveryFailures.get(recovery.subjectTaskId) !== started.skipReason) {
243
- this.verificationRecoveryFailures.set(recovery.subjectTaskId, started.skipReason);
244
- this.safeWarn(`Recovered verification for ${recovery.subjectTaskId} did not start: ${started.skipReason}`);
245
- }
246
- }
247
- for (const notification of lifecycle.getPendingTerminalNotifications()) {
248
- this.notifications.recordTerminal(notification.record, notification.notificationId);
249
- }
250
- }
251
- finally {
252
- this.recoveringDurableState = false;
253
- }
270
+ dispatchRecoveredVerification(recovery) {
271
+ const legacyImplementation = recovery.verifierExecutionContract
272
+ ? undefined
273
+ : this.resolveWorkerAdmission({
274
+ instructions: recovery.summary,
275
+ profileId: recovery.implementationProfileId,
276
+ });
277
+ const verifierProfileId = recovery.verifierExecutionContract?.worker.profile.profileId ??
278
+ (legacyImplementation?.ok ? legacyImplementation.verifierShipment?.profile.profileId : undefined);
279
+ return verifierProfileId
280
+ ? this.startInternal(this.buildVerifierRequest({
281
+ subjectTaskId: recovery.subjectTaskId,
282
+ verifierProfileId,
283
+ summary: recovery.summary,
284
+ artifactUris: recovery.artifactUris,
285
+ }), recovery.verifierExecutionContract)
286
+ : { started: false, skipReason: "independent_verifier_unavailable" };
254
287
  }
255
288
  laneCapabilityProfile(model) {
256
289
  return deriveModelCapabilityProfile({
@@ -318,6 +351,16 @@ export class WorkerDelegationController {
318
351
  if (!this.laneCapabilityProfile(shipment.model).backgroundLanesEnabled) {
319
352
  return { ok: false, skipReason: "model_delegation_unsupported" };
320
353
  }
354
+ // The model-facing delegate surface cannot name profile resources. An absent or empty
355
+ // runtime selection therefore means "use the owner-admitted profile set", not "drop it".
356
+ // Non-empty task metadata is an exact narrowing and remains fail-closed for unknown or
357
+ // duplicate ids before any durable task or provider request is created.
358
+ const requestedResourcePointerIds = request.taskContext?.resourcePointerIds ?? [];
359
+ const selectedResources = selectWorkerResourcePointers(shipment.resourcePointers, requestedResourcePointerIds.length > 0
360
+ ? requestedResourcePointerIds
361
+ : shipment.resourcePointers.map((pointer) => pointer.id));
362
+ if (!selectedResources.ok)
363
+ return { ok: false, skipReason: selectedResources.reason };
321
364
  const currentExecutionPlan = this.buildWorkerExecutionPlan(shipment.profile, settings);
322
365
  const executionPlan = pinnedContract
323
366
  ? narrowWorkerExecutionPlan(pinnedContract.worker.authority, currentExecutionPlan)
@@ -343,6 +386,7 @@ export class WorkerDelegationController {
343
386
  settings,
344
387
  shipment,
345
388
  ...(verifierShipment ? { verifierShipment } : {}),
389
+ resourcePointerIds: selectedResources.pointers.map((pointer) => pointer.id),
346
390
  executionContract,
347
391
  executionPlan,
348
392
  };
@@ -384,6 +428,18 @@ export class WorkerDelegationController {
384
428
  if (attemptId)
385
429
  this.publishedTerminalAttemptIds.add(attemptId);
386
430
  }
431
+ /**
432
+ * A pre-execution denial still owns a prepared durable lane. Publish its terminal projection at
433
+ * this boundary: immediate callers have no scheduler promise to observe and must not wait for a
434
+ * future recovery read to receive the handoff. Repeated late cancellation paths are inert via
435
+ * the attempt-id publication fence above.
436
+ */
437
+ cancelAndPublish(lifecycle, laneId, reasonCode) {
438
+ const record = lifecycle.cancel(laneId, reasonCode);
439
+ if (record)
440
+ this.publishTerminalRecord(record);
441
+ return record;
442
+ }
387
443
  hasWorkerCapacity(settings, profile) {
388
444
  const lifecycle = this.getWorkerLifecycle();
389
445
  if (lifecycle.getRunningCount() >= settings.maxConcurrent)
@@ -395,9 +451,15 @@ export class WorkerDelegationController {
395
451
  const admission = this.resolveWorkerAdmission(request, contract);
396
452
  if (!admission.ok)
397
453
  return { action: "cancel", reasonCode: admission.skipReason };
398
- return this.hasWorkerCapacity(admission.settings, admission.shipment.profile)
399
- ? { action: "start" }
400
- : { action: "wait" };
454
+ if (!this.hasWorkerCapacity(admission.settings, admission.shipment.profile))
455
+ return { action: "wait", reason: "capacity" };
456
+ const attempt = this.getWorkerLifecycle().getActiveAttempt(record.laneId);
457
+ if (!attempt)
458
+ return { action: "cancel", reasonCode: "orchestration_attempt_missing" };
459
+ const reservation = this.writeReservations.acquire(record.laneId, attempt, admission.executionPlan);
460
+ if (reservation.kind === "denied")
461
+ return { action: "cancel", reasonCode: reservation.reasonCode };
462
+ return reservation.kind === "granted" ? { action: "start" } : { action: "wait", reason: "write_reservation" };
401
463
  }
402
464
  buildWorkerExecutionPlan(profile, settings) {
403
465
  return buildWorkerExecutionPlan({
@@ -426,6 +488,12 @@ export class WorkerDelegationController {
426
488
  executionContract: admission.executionContract,
427
489
  requiredCapabilities: admission.executionPlan.requiredCapabilities,
428
490
  ...(request.verificationOfTaskId ? { verificationOfTaskId: request.verificationOfTaskId } : {}),
491
+ taskContext: {
492
+ requirementIds: request.taskContext?.requirementIds ?? [],
493
+ dependsOnTaskIds: request.taskContext?.dependsOnTaskIds ?? [],
494
+ acceptanceCriterionIds: request.taskContext?.acceptanceCriterionIds ?? [],
495
+ resourcePointerIds: admission.resourcePointerIds,
496
+ },
429
497
  ...(goal ? { goal } : {}),
430
498
  });
431
499
  return { executionPlan: admission.executionPlan, lifecycle, ...prepared };
@@ -438,7 +506,7 @@ export class WorkerDelegationController {
438
506
  if (!admission.ok)
439
507
  return { started: false, skipReason: admission.skipReason };
440
508
  if (!request.verificationOfTaskId)
441
- this.recoverDurableQueue();
509
+ this.recovery.recover();
442
510
  const { settings, shipment } = admission;
443
511
  const foreground = this.deps.getModel();
444
512
  const contendsWithLocalForeground = foreground !== undefined && isLocalExecutionModel(foreground) && isLocalExecutionModel(shipment.model);
@@ -485,13 +553,14 @@ export class WorkerDelegationController {
485
553
  if (!admission.ok)
486
554
  return { started: false, skipReason: admission.skipReason };
487
555
  if (!request.verificationOfTaskId)
488
- this.recoverDurableQueue();
556
+ this.recovery.recover();
489
557
  const { instructions, settings, verifierShipment } = admission;
490
558
  const { model, modelBinding, profile: orchestrationProfile, soul } = admission.shipment;
491
559
  if (!this.hasWorkerCapacity(settings, orchestrationProfile)) {
492
560
  return { started: false, skipReason: "worker_delegation_already_running" };
493
561
  }
494
562
  const laneCapability = this.laneCapabilityProfile(model);
563
+ const retentionPolicy = workerConversationRetentionPolicy(model, this.deps.getSettingsManager());
495
564
  const prepared = this.prepareWorkerAttempt(request, admission, existingRecord);
496
565
  const { executionPlan, lifecycle } = prepared;
497
566
  if (!prepared.attempt)
@@ -499,26 +568,145 @@ export class WorkerDelegationController {
499
568
  const durableTask = lifecycle.getTask(prepared.record.laneId);
500
569
  if (!durableTask)
501
570
  return { started: false, skipReason: "orchestration_task_missing" };
502
- const compiled = compileWorkerExecutionGrant({
503
- target: {
504
- objectiveId: durableTask.task.objectiveId,
505
- taskId: prepared.attempt.taskId,
506
- attemptId: prepared.attempt.attemptId,
507
- },
508
- profile: orchestrationProfile,
509
- plan: executionPlan,
510
- });
511
- if (!compiled.ok) {
512
- lifecycle.cancel(prepared.record.laneId, compiled.reasonCodes.join(","));
513
- return { started: false, skipReason: `execution_policy_denied:${compiled.reasonCodes.join(",")}` };
571
+ const immutableWorker = prepared.attempt.dispatch.executionContract?.worker;
572
+ if (!immutableWorker)
573
+ return { started: false, skipReason: "orchestration_execution_contract_missing" };
574
+ const selectedResources = selectWorkerResourcePointers(immutableWorker.resourcePointers, prepared.attempt.dispatch.resourcePointerIds.length > 0
575
+ ? prepared.attempt.dispatch.resourcePointerIds
576
+ : admission.resourcePointerIds);
577
+ if (!selectedResources.ok) {
578
+ this.cancelAndPublish(lifecycle, prepared.record.laneId, selectedResources.reason);
579
+ return { started: false, skipReason: selectedResources.reason };
580
+ }
581
+ let grant;
582
+ let workerResourceSystemPrompt;
583
+ if (prepared.attempt.grant) {
584
+ if (!this.recovery.durableGrantIsStillPermitted(prepared.attempt.grant, executionPlan, selectedResources.pointers)) {
585
+ this.cancelAndPublish(lifecycle, prepared.record.laneId, "recovered_grant_revoked");
586
+ return { started: false, skipReason: "recovered_grant_revoked" };
587
+ }
588
+ grant = prepared.attempt.grant;
589
+ const materialized = materializeWorkerResourceBundle(grant.resources);
590
+ if (!materialized.ok) {
591
+ const reason = `worker_resource_materialization_${materialized.code}`;
592
+ this.cancelAndPublish(lifecycle, prepared.record.laneId, reason);
593
+ return { started: false, skipReason: reason };
594
+ }
595
+ workerResourceSystemPrompt = materialized.systemPrompt;
596
+ }
597
+ else {
598
+ const materialized = materializeWorkerResourceBundle(selectedResources.pointers);
599
+ if (!materialized.ok) {
600
+ const reason = `worker_resource_materialization_${materialized.code}`;
601
+ this.cancelAndPublish(lifecycle, prepared.record.laneId, reason);
602
+ return { started: false, skipReason: reason };
603
+ }
604
+ const compiled = compileWorkerExecutionGrant({
605
+ target: {
606
+ objectiveId: durableTask.task.objectiveId,
607
+ taskId: prepared.attempt.taskId,
608
+ attemptId: prepared.attempt.attemptId,
609
+ },
610
+ profile: orchestrationProfile,
611
+ plan: executionPlan,
612
+ resources: materialized.pointers,
613
+ });
614
+ if (!compiled.ok) {
615
+ this.cancelAndPublish(lifecycle, prepared.record.laneId, compiled.reasonCodes.join(","));
616
+ return { started: false, skipReason: `execution_policy_denied:${compiled.reasonCodes.join(",")}` };
617
+ }
618
+ lifecycle.bindGrant(prepared.attempt.attemptId, compiled.grant);
619
+ grant = compiled.grant;
620
+ workerResourceSystemPrompt = materialized.systemPrompt;
621
+ }
622
+ const immutableProfile = immutableWorker.profile;
623
+ // Follow-up tasks deliberately receive unique task ids while retaining the original logical
624
+ // agent id in dispatch metadata. Never derive a new agent identity from a follow-up task id.
625
+ const agentId = prepared.attempt.agentId ?? prepared.attempt.dispatch.logicalLaneId ?? prepared.record.laneId;
626
+ const registeredAgent = lifecycle.getAgent(agentId);
627
+ if (prepared.attempt.agentId && !registeredAgent) {
628
+ this.cancelAndPublish(lifecycle, prepared.record.laneId, "orchestration_agent_missing");
629
+ return { started: false, skipReason: "orchestration_agent_missing" };
630
+ }
631
+ let conversation;
632
+ try {
633
+ if (registeredAgent) {
634
+ // Recovery must trust only the registered resume context; reconstructing one here could
635
+ // silently redirect a logical worker to a different transcript or resource scope.
636
+ conversation = this.conversations.open({
637
+ agentDir: this.deps.getAgentDir(),
638
+ resumeContext: registeredAgent.resumeContext,
639
+ expectedLogicalAgentId: registeredAgent.agentId,
640
+ });
641
+ if (prepared.attempt.status === "suspended")
642
+ this.recovery.repairInterruptedToolResults(conversation);
643
+ }
644
+ else {
645
+ conversation = this.conversations.ensure({
646
+ agentDir: this.deps.getAgentDir(),
647
+ parentSessionId: this.deps.getSessionId(),
648
+ logicalAgentId: agentId,
649
+ cwd: this.deps.getCwd(),
650
+ orchestrationProfileId: immutableProfile.profileId,
651
+ modelRef: `${prepared.attempt.dispatch.executionContract.worker.modelBinding.provider}/${prepared.attempt.dispatch.executionContract.worker.modelBinding.modelId}`,
652
+ resourceProfileNames: immutableProfile.resourceProfileNames,
653
+ contextPointers: grant.resources,
654
+ });
655
+ lifecycle.ensureAgent({
656
+ agentId,
657
+ role: immutableProfile.role,
658
+ resumeContext: conversation.getResumeContext(),
659
+ });
660
+ }
661
+ }
662
+ catch (error) {
663
+ if (prepared.attempt.status !== "suspended") {
664
+ this.cancelAndPublish(lifecycle, prepared.record.laneId, "worker_conversation_unavailable");
665
+ }
666
+ this.safeWarn(`Worker conversation setup failed: ${error instanceof Error ? error.message : String(error)}`);
667
+ return { started: false, skipReason: "worker_conversation_unavailable" };
668
+ }
669
+ const reservation = this.writeReservations.acquire(prepared.record.laneId, prepared.attempt, executionPlan);
670
+ if (reservation.kind === "denied") {
671
+ this.cancelAndPublish(lifecycle, prepared.record.laneId, reservation.reasonCode);
672
+ return { started: false, skipReason: reservation.reasonCode };
673
+ }
674
+ if (reservation.kind === "blocked") {
675
+ this.scheduler.enqueue(prepared.record, { ...request, profileId: admission.shipment.profile.profileId });
676
+ this.notifications.statusChanged();
677
+ onStarted?.(prepared.record);
678
+ return { started: true, record: prepared.record };
679
+ }
680
+ let durableHandle;
681
+ try {
682
+ durableHandle =
683
+ registeredAgent && prepared.attempt.status === "suspended"
684
+ ? lifecycle.resumeAgent(prepared.record.laneId, registeredAgent.agentId, immutableProfile.leaseTtlMs, this.agentControl.getProcessOwnerId())
685
+ : lifecycle.startAgent(prepared.record.laneId, agentId, immutableProfile.leaseTtlMs, this.agentControl.getProcessOwnerId());
686
+ }
687
+ catch (error) {
688
+ this.writeReservations.release(prepared.record.laneId);
689
+ if (prepared.attempt.status !== "suspended")
690
+ this.cancelAndPublish(lifecycle, prepared.record.laneId, "worker_start_unavailable");
691
+ this.safeWarn(`Worker start failed: ${error instanceof Error ? error.message : String(error)}`);
692
+ return { started: false, skipReason: "worker_start_unavailable" };
514
693
  }
515
- lifecycle.bindGrant(prepared.attempt.attemptId, compiled.grant);
516
- const durableHandle = lifecycle.start(prepared.record.laneId, orchestrationProfile.leaseTtlMs);
517
694
  const startedRecord = lifecycle.getRecord(prepared.record.laneId);
518
- if (!startedRecord)
695
+ if (!startedRecord) {
696
+ this.writeReservations.release(prepared.record.laneId);
519
697
  return { started: false, skipReason: "orchestration_projection_missing" };
698
+ }
699
+ if (this.writeReservations.hasFenceMismatch(startedRecord.laneId, durableHandle.attemptId, durableHandle.fencingToken)) {
700
+ this.writeReservations.release(startedRecord.laneId);
701
+ const record = this.cancelAndPublish(lifecycle, startedRecord.laneId, "write_reservation_fence_mismatch");
702
+ this.safeWarn("Worker write reservation fence did not match the durable attempt lease.");
703
+ return { started: false, skipReason: record?.reasonCode ?? "write_reservation_fence_mismatch" };
704
+ }
705
+ const recoveredTerminal = prepared.attempt.status === "suspended" ? this.recovery.recoveredTerminalCompletion(conversation) : undefined;
706
+ const checkpointUsage = lifecycle.getAttemptUsage(startedRecord.laneId);
707
+ const initialUsage = this.recovery.initialUsage(conversation, checkpointUsage);
520
708
  onStarted?.(startedRecord);
521
- const maxUsd = compiled.grant.budget.maxCostUsd;
709
+ const maxUsd = grant.budget.maxCostUsd;
522
710
  const executionPolicy = orchestrationProfile.executionPolicy;
523
711
  const toolSurface = createLaneToolSurface({
524
712
  cwd: this.deps.getCwd(),
@@ -526,9 +714,10 @@ export class WorkerDelegationController {
526
714
  writeEnabled: executionPlan.writeEnabled,
527
715
  writePaths: executionPlan.writePaths,
528
716
  ...(executionPlan.processEnabled && executionPolicy ? { executionPolicy } : {}),
529
- processMaxWallClockMs: compiled.grant.budget.maxWallClockMs ?? 0,
530
- grant: compiled.grant,
717
+ processMaxWallClockMs: grant.budget.maxWallClockMs ?? 0,
718
+ grant,
531
719
  toolManifests: executionPlan.toolManifests,
720
+ initialUsage,
532
721
  });
533
722
  const writeGranted = executionPlan.writeEnabled &&
534
723
  toolSurface.gateway !== undefined &&
@@ -548,9 +737,9 @@ export class WorkerDelegationController {
548
737
  profileId: orchestrationProfile.profileId,
549
738
  // filesystem.write requires BOTH the opt-in AND an explicit non-empty path scope —
550
739
  // an unscoped write grant is refused here, not discovered at validation time.
551
- capabilities: [...compiled.grant.capabilities],
552
- ...(writeGranted ? { allowedPaths: [...compiled.grant.writePaths] } : {}),
553
- deniedPaths: [...compiled.grant.deniedPaths],
740
+ capabilities: [...grant.capabilities],
741
+ ...(writeGranted ? { allowedPaths: [...grant.writePaths] } : {}),
742
+ deniedPaths: [...grant.deniedPaths],
554
743
  allowedTools: [...toolSurface.allowedTools],
555
744
  deniedTools: [...toolSurface.deniedTools],
556
745
  ...(maxUsd !== undefined ? { maxEstimatedUsd: maxUsd } : {}),
@@ -579,120 +768,55 @@ export class WorkerDelegationController {
579
768
  // registerInFlightWork is a pure sync map op (cannot throw), so placing it as the last
580
769
  // statement before `try` still guarantees the matching finally always runs.
581
770
  const deregisterInFlight = registerInFlightWork(this.deps.getAgentDir(), "lane", `worker:${startedRecord.laneId}`);
582
- const durableStartedAt = Date.now();
771
+ const executor = createWorkerAttemptExecutor({
772
+ request: workerRequest,
773
+ grant,
774
+ executionPlan,
775
+ toolSurface,
776
+ conversation,
777
+ lifecycle,
778
+ laneId: startedRecord.laneId,
779
+ agentId,
780
+ durableHandle,
781
+ parentSessionId: this.deps.getSessionId(),
782
+ agentDir: this.deps.getAgentDir(),
783
+ cwd: this.deps.getCwd(),
784
+ model,
785
+ thinkingLevel: modelBinding.thinkingLevel,
786
+ laneCapability,
787
+ ...(soul ? { soul } : {}),
788
+ workerResourceSystemPrompt,
789
+ initialUsage,
790
+ hasPersistedUsageCheckpoint: checkpointUsage !== undefined,
791
+ usageReportId,
792
+ processCapable: executionPlan.processEnabled,
793
+ ...(request.verificationOfTaskId ? { verificationSubjectTaskId: request.verificationOfTaskId } : {}),
794
+ ...(recoveredTerminal ? { recoveredTerminal } : {}),
795
+ ...(retentionPolicy ? { retentionPolicy } : {}),
796
+ signal: workerSignal,
797
+ runIsolatedCompletion: (options) => this.deps.runIsolatedCompletion(options),
798
+ agentControl: this.agentControl,
799
+ applyActions: workerRequest.envelope.capabilities.includes("filesystem.write")
800
+ ? (actions, actionJournal) => applyWorkerActions({
801
+ actions,
802
+ gateway: toolSurface.gateway,
803
+ toolManifests: executionPlan.toolManifests,
804
+ cwd: this.deps.getCwd(),
805
+ ...(actionJournal ? { actionJournal } : {}),
806
+ })
807
+ : undefined,
808
+ warn: (message) => this.safeWarn(message),
809
+ });
583
810
  try {
584
- let spentUsage;
585
- const toolChangedFiles = new Set();
586
- const toolIssues = new Set();
587
- // Register the live mutation ledger BEFORE the suspend point below so a synchronous
588
- // disposal cutoff (`abortInFlightLanes`) can read a race-free snapshot of whatever this
589
- // worker has already applied — the worker is suspended at the `await runWorker(...)` below
590
- // whenever abort runs, and the abort signal stops further tool calls. Deleted in the
591
- // `finally` on every exit path (normal completion, throw, or already consumed by abort).
811
+ // Register before the first execution await: disposal sees the live mutable ledger.
592
812
  this.inFlightLedgers.set(startedRecord.laneId, {
593
- changedFiles: toolChangedFiles,
594
- getSpend: () => spentUsage,
595
- getToolCalls: () => toolSurface.gateway?.getUsage().toolCalls ?? 0,
813
+ changedFiles: executor.ledger.changedFiles,
814
+ getUsage: executor.ledger.getUsage,
596
815
  request: workerRequest,
597
816
  handle: durableHandle,
598
- startedAt: durableStartedAt,
599
- });
600
- const maxToolCalls = compiled.grant.budget.maxToolCalls ?? 0;
601
- const rawOutcome = await runWorker({
602
- request: workerRequest,
603
- maxUsd,
604
- maxWallClockMs: compiled.grant.budget.maxWallClockMs ?? 0,
605
- usageReportId,
606
- getChangedFiles: () => [...toolChangedFiles],
607
- signal: workerSignal,
608
- // Parent validation must use the same relative-path baseline the runner reports in.
609
- cwd: this.deps.getCwd(),
610
- processCapable: executionPlan.processEnabled,
611
- ...(request.verificationOfTaskId ? { verificationSubjectTaskId: request.verificationOfTaskId } : {}),
612
- // Write lane: runner-side action application through the envelope path scope.
613
- applyActions: workerRequest.envelope.capabilities.includes("filesystem.write")
614
- ? (actions) => {
615
- return applyWorkerActions({
616
- actions,
617
- gateway: toolSurface.gateway,
618
- toolManifests: executionPlan.toolManifests,
619
- cwd: this.deps.getCwd(),
620
- });
621
- }
622
- : undefined,
623
- complete: async ({ systemPrompt, userPrompt, signal }) => {
624
- const completion = await this.deps.runIsolatedCompletion({
625
- // Level-0 core and owner-authored profile remain authoritative. The delegating
626
- // model supplies task instructions only; it cannot replace the worker role prompt.
627
- systemPrompt: composeSubagentSystemPrompt({
628
- soul,
629
- rolePrompt: systemPrompt,
630
- }),
631
- messages: [{ role: "user", content: [{ type: "text", text: userPrompt }], timestamp: Date.now() }],
632
- model,
633
- thinkingLevel: modelBinding.thinkingLevel,
634
- maxTokens: Math.min(laneCapability.laneMaxOutputTokens, compiled.grant.budget.maxTokens ?? Number.POSITIVE_INFINITY),
635
- tools: toolSurface.tools,
636
- maxTurns: Math.max(1, Math.min(6, maxToolCalls + 1)),
637
- finalTextPrompt: "The tool-turn budget is exhausted. Do not call more tools. Return the required worker-claim JSON envelope now using only evidence already gathered. If the investigation is incomplete, say so in the summary or blockers instead of omitting the envelope.",
638
- beforeToolCall: async (context, toolSignal) => {
639
- const decision = await toolSurface.beforeToolCall(context, toolSignal);
640
- if (decision?.block) {
641
- toolIssues.add(`${context.toolCall.name} blocked: ${decision.reason ?? "capability denied"}`);
642
- }
643
- return decision;
644
- },
645
- afterToolCall: async ({ toolCall, args, isError }) => {
646
- // This hook runs only for a validated, gate-approved tool that actually entered
647
- // execution. Record a direct mutation target before inspecting `isError`: write/edit
648
- // may have changed disk and then observed cancellation, timeout, or a late I/O error.
649
- // Pre-gate/profile/path refusals never reach afterToolCall, so they remain unreported.
650
- if (toolCall.name === "write" || toolCall.name === "edit") {
651
- if (args && typeof args === "object" && !Array.isArray(args)) {
652
- const rawPath = args.path;
653
- if (typeof rawPath === "string" && rawPath.length > 0) {
654
- const absolutePath = path.isAbsolute(rawPath)
655
- ? path.resolve(rawPath)
656
- : path.resolve(this.deps.getCwd(), rawPath);
657
- let canonicalPath = absolutePath;
658
- try {
659
- canonicalPath = safeRealpathSync(absolutePath);
660
- }
661
- catch {
662
- // Execution was attempted; preserve conservative accounting with the lexical path.
663
- }
664
- toolChangedFiles.add(path.relative(this.deps.getCwd(), canonicalPath).split(path.sep).join("/"));
665
- }
666
- }
667
- }
668
- if (isError) {
669
- toolIssues.add(`${toolCall.name} failed during isolated execution`);
670
- return undefined;
671
- }
672
- return undefined;
673
- },
674
- signal,
675
- // Core/soul/role are all static per configuration — the provider can cache the prefix.
676
- cacheRetention: "short",
677
- // Stable per-lane synthetic affinity key so repeat worker-delegation calls route to
678
- // the same cache-warm backend without carrying the real session id.
679
- laneKind: "worker",
680
- });
681
- spentUsage = completion.usage;
682
- toolSurface.gateway?.recordUsage({
683
- inputTokens: completion.usage.input,
684
- outputTokens: completion.usage.output,
685
- costUsd: completion.usage.cost.total,
686
- });
687
- return {
688
- text: completion.text,
689
- costUsd: completion.usage.cost.total,
690
- stopReason: String(completion.stopReason),
691
- changedFiles: [...toolChangedFiles],
692
- blockers: [...toolIssues],
693
- };
694
- },
695
817
  });
818
+ const executionResult = await executor.run();
819
+ const rawOutcome = executionResult.rawOutcome;
696
820
  const verificationRequired = orchestrationProfile.requireIndependentVerification &&
697
821
  orchestrationProfile.role !== "verifier" &&
698
822
  rawOutcome.claim.status === "completed";
@@ -729,33 +853,32 @@ export class WorkerDelegationController {
729
853
  const verificationSubject = request.verificationOfTaskId
730
854
  ? lifecycle.getTask(request.verificationOfTaskId)
731
855
  : undefined;
732
- let record = lifecycle.finish(createWorkerResultContract({
856
+ const finalUsage = executionResult.usage;
857
+ const reportedUsage = providerUsageFromAttemptUsage(finalUsage);
858
+ let record = finalizeWorkerClaim(lifecycle, {
733
859
  handle: durableHandle,
734
860
  claim: outcome.claim,
735
861
  accepted: outcome.accepted,
736
- costUsd: outcome.costUsd,
862
+ costUsd: finalUsage.costUsd,
737
863
  reasonCode: outcome.reasonCode,
738
864
  cwd: this.deps.getCwd(),
739
- ...(spentUsage
740
- ? {
741
- inputTokens: spentUsage.input,
742
- outputTokens: spentUsage.output,
743
- totalTokens: spentUsage.totalTokens,
744
- }
745
- : {}),
746
- wallClockMs: Date.now() - durableStartedAt,
747
- toolCalls: toolSurface.gateway?.getUsage().toolCalls ?? 0,
865
+ inputTokens: finalUsage.inputTokens,
866
+ outputTokens: finalUsage.outputTokens,
867
+ totalTokens: reportedUsage.totalTokens,
868
+ wallClockMs: finalUsage.activeWallClockMs,
869
+ toolCalls: finalUsage.toolCalls,
748
870
  verificationRequired,
749
871
  verificationCriterionIds: verificationSubject?.task.acceptanceCriterionIds,
750
- }), { notify: !verificationRequired });
872
+ notify: !verificationRequired,
873
+ }).record;
751
874
  try {
752
875
  this.deps.saveWorkerClaimSnapshot(outcome.claim, workerRequest);
753
876
  }
754
877
  catch (error) {
755
878
  this.safeWarn(`Failed to persist worker claim ${startedRecord.laneId}: ${error instanceof Error ? error.message : String(error)}`);
756
879
  }
757
- if (spentUsage && (spentUsage.cost.total > 0 || spentUsage.totalTokens > 0)) {
758
- this.deps.addSpawnedUsage(spentUsage, { label: "worker-delegation", reportId: usageReportId });
880
+ if (reportedUsage.cost.total > 0 || reportedUsage.totalTokens > 0) {
881
+ this.deps.addSpawnedUsage(reportedUsage, { label: "worker-delegation", reportId: usageReportId });
759
882
  }
760
883
  const terminalRecords = [record];
761
884
  if (request.verificationOfTaskId) {
@@ -809,6 +932,11 @@ export class WorkerDelegationController {
809
932
  }
810
933
  catch (error) {
811
934
  const durableState = lifecycle.ledger.runtime.getSnapshot().attempts[durableHandle.attemptId];
935
+ if (durableState?.status === "suspended") {
936
+ // Disposal/reload fences agent-bound work before its aborted completion unwinds. Do not
937
+ // convert that resumable interruption into a terminal claim or cancellation.
938
+ return { started: true, record: lifecycle.getRecord(startedRecord.laneId) };
939
+ }
812
940
  if (durableState?.status === "cancelled" && (laneAbortController.signal.aborted || this.deps.isDisposed())) {
813
941
  return { started: true, record: lifecycle.getRecord(startedRecord.laneId) };
814
942
  }
@@ -821,16 +949,21 @@ export class WorkerDelegationController {
821
949
  createdAt: new Date().toISOString(),
822
950
  };
823
951
  try {
824
- lifecycle.finish(createWorkerResultContract({
952
+ const failureUsage = executor.checkpointUsage("Persisted cumulative usage while recording worker failure.");
953
+ const reportedUsage = providerUsageFromAttemptUsage(failureUsage);
954
+ finalizeWorkerClaim(lifecycle, {
825
955
  handle: durableHandle,
826
956
  claim: failureClaim,
827
957
  accepted: false,
828
- costUsd: 0,
958
+ costUsd: failureUsage.costUsd,
829
959
  cwd: this.deps.getCwd(),
830
- wallClockMs: Date.now() - durableStartedAt,
831
- toolCalls: toolSurface.gateway?.getUsage().toolCalls ?? 0,
960
+ inputTokens: failureUsage.inputTokens,
961
+ outputTokens: failureUsage.outputTokens,
962
+ totalTokens: reportedUsage.totalTokens,
963
+ wallClockMs: failureUsage.activeWallClockMs,
964
+ toolCalls: failureUsage.toolCalls,
832
965
  reasonCode: "worker_delegation_error",
833
- }));
966
+ });
834
967
  }
835
968
  catch (persistError) {
836
969
  this.safeWarn(`Failed to persist durable worker failure ${startedRecord.laneId}: ${persistError instanceof Error ? persistError.message : String(persistError)}`);
@@ -838,7 +971,7 @@ export class WorkerDelegationController {
838
971
  }
839
972
  let record = lifecycle.getRecord(startedRecord.laneId);
840
973
  if (record?.status === "queued" || record?.status === "running") {
841
- record = lifecycle.cancel(startedRecord.laneId, "worker_delegation_error");
974
+ record = this.cancelAndPublish(lifecycle, startedRecord.laneId, "worker_delegation_error");
842
975
  }
843
976
  if (record && !this.deps.isDisposed())
844
977
  this.publishTerminalRecord(record);
@@ -847,14 +980,16 @@ export class WorkerDelegationController {
847
980
  return { started: true, record };
848
981
  }
849
982
  finally {
983
+ this.writeReservations.release(startedRecord.laneId, durableHandle.attemptId, durableHandle.fencingToken);
850
984
  this.inFlightLedgers.delete(startedRecord.laneId);
851
985
  this.laneAbortControllers.delete(startedRecord.laneId);
986
+ this.agentControl.signalStateChanged();
852
987
  deregisterInFlight();
853
988
  }
854
989
  }
855
990
  /** Start every capacity-eligible queued worker at the owner session's foreground-idle boundary. */
856
991
  drain() {
857
- this.recoverDurableQueue();
992
+ this.recovery.recover();
858
993
  this.scheduler.drain();
859
994
  }
860
995
  }