@fitlab-ai/agent-infra 0.11.11 → 0.11.13

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 (154) hide show
  1. package/dist/bin/internal-cli.js +2 -2
  2. package/dist/lib/agent-clients/adapter.js +7 -1
  3. package/dist/lib/agent-clients/adapters/claude-code.js +5 -1
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +78 -63
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/binding.js +114 -0
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +447 -0
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-client.js +130 -0
  8. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-operation.js +131 -0
  9. package/dist/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.js +2 -2
  10. package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +48 -1
  11. package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +124 -62
  12. package/dist/lib/{internal/codex-sandbox-controller.js → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.js} +3 -3
  13. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -5
  14. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +211 -213
  15. package/dist/lib/agent-clients/adapters/codex-orchestration.js +468 -0
  16. package/dist/lib/agent-clients/adapters/codex.js +40 -1
  17. package/dist/lib/agent-clients/adapters/index.js +2 -10
  18. package/dist/lib/agent-clients/tokens.js +3 -2
  19. package/dist/lib/internal/agent-client.js +132 -72
  20. package/dist/lib/internal/cli-route-inventory.js +2 -2
  21. package/dist/lib/internal/sandbox-control.js +1 -81
  22. package/dist/lib/internal/task-finalization.js +26 -19
  23. package/dist/lib/internal/task-lifecycle.js +1 -1
  24. package/dist/lib/internal/task-operation-registry.js +1 -1
  25. package/dist/lib/internal/task-validate.js +1 -1
  26. package/dist/lib/platform/github-client.js +33 -3
  27. package/dist/lib/platform/issues.js +18 -6
  28. package/dist/lib/runtime/agent-runtime.js +6 -29
  29. package/dist/lib/sandbox/commands/control-status.js +1 -0
  30. package/dist/lib/sandbox/commands/create.js +7 -2
  31. package/dist/lib/sandbox/commands/ls.js +4 -5
  32. package/dist/lib/sandbox/commands/rm.js +1 -1
  33. package/dist/lib/sandbox/control/agent-operation.js +34 -0
  34. package/dist/lib/sandbox/control/client.js +14 -201
  35. package/dist/lib/sandbox/control/executor.js +12 -125
  36. package/dist/lib/sandbox/control/lifecycle.js +66 -4
  37. package/dist/lib/sandbox/control/protocol.js +15 -54
  38. package/dist/lib/sandbox/control/server.js +216 -281
  39. package/dist/lib/sandbox/control/task-view.js +0 -13
  40. package/dist/lib/sandbox/mounts.js +9 -1
  41. package/dist/lib/sandbox/recovery.js +31 -10
  42. package/dist/lib/sandbox/removal.js +28 -5
  43. package/dist/lib/sandbox/workspace-identity.js +18 -6
  44. package/dist/lib/sandbox/workspace-view.js +56 -19
  45. package/dist/lib/task/commands/ls.js +3 -3
  46. package/dist/lib/task/control-authority.js +56 -264
  47. package/dist/lib/task/control-recovery.js +2 -3
  48. package/dist/lib/task/create.js +2 -2
  49. package/dist/lib/task/delegation-receipts.js +74 -161
  50. package/dist/lib/task/events.js +88 -44
  51. package/dist/lib/task/finalization.js +35 -6
  52. package/dist/lib/task/lifecycle.js +274 -115
  53. package/dist/lib/task/orchestration.js +201 -194
  54. package/dist/lib/task/runtime-paths.js +10 -0
  55. package/dist/lib/task/short-id.js +37 -27
  56. package/dist/lib/task/verification-engine.js +32 -26
  57. package/dist/lib/task/workspace-snapshot.js +1 -1
  58. package/dist/lifecycle-build-manifest.json +3 -3
  59. package/lib/agent-clients/adapter.ts +39 -0
  60. package/lib/agent-clients/adapters/claude-code.ts +5 -1
  61. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +105 -80
  62. package/lib/agent-clients/adapters/codex-lifecycle/binding.ts +147 -0
  63. package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +510 -0
  64. package/lib/agent-clients/adapters/codex-lifecycle/controller-client.ts +158 -0
  65. package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +1 -1
  66. package/lib/agent-clients/adapters/codex-lifecycle/controller-operation.ts +151 -0
  67. package/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.ts +4 -4
  68. package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +43 -0
  69. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +8 -5
  70. package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +190 -65
  71. package/lib/{internal/codex-sandbox-controller.ts → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.ts} +2 -2
  72. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -5
  73. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +270 -224
  74. package/lib/agent-clients/adapters/codex-orchestration.ts +632 -0
  75. package/lib/agent-clients/adapters/codex.ts +44 -1
  76. package/lib/agent-clients/adapters/index.ts +7 -20
  77. package/lib/agent-clients/tokens.ts +4 -3
  78. package/lib/internal/agent-client.ts +142 -73
  79. package/lib/internal/cli-route-inventory.ts +2 -2
  80. package/lib/internal/sandbox-control.ts +0 -97
  81. package/lib/internal/task-finalization.ts +30 -18
  82. package/lib/internal/task-lifecycle.ts +1 -1
  83. package/lib/internal/task-operation-registry.ts +1 -1
  84. package/lib/internal/task-validate.ts +1 -1
  85. package/lib/platform/github-client.ts +34 -3
  86. package/lib/platform/issues.ts +18 -6
  87. package/lib/runtime/agent-runtime.ts +6 -41
  88. package/lib/sandbox/commands/control-status.ts +1 -0
  89. package/lib/sandbox/commands/create.ts +7 -1
  90. package/lib/sandbox/commands/ls.ts +4 -5
  91. package/lib/sandbox/commands/rm.ts +1 -1
  92. package/lib/sandbox/control/agent-operation.ts +49 -0
  93. package/lib/sandbox/control/client.ts +20 -254
  94. package/lib/sandbox/control/executor.ts +13 -136
  95. package/lib/sandbox/control/lifecycle.ts +67 -4
  96. package/lib/sandbox/control/protocol.ts +22 -66
  97. package/lib/sandbox/control/server.ts +21 -95
  98. package/lib/sandbox/control/task-view.ts +0 -19
  99. package/lib/sandbox/mounts.ts +10 -1
  100. package/lib/sandbox/recovery.ts +38 -17
  101. package/lib/sandbox/removal.ts +33 -7
  102. package/lib/sandbox/workspace-identity.ts +16 -6
  103. package/lib/sandbox/workspace-view.ts +50 -18
  104. package/lib/task/commands/ls.ts +3 -3
  105. package/lib/task/control-authority.ts +66 -378
  106. package/lib/task/control-recovery.ts +3 -4
  107. package/lib/task/create.ts +3 -3
  108. package/lib/task/delegation-receipts.ts +87 -231
  109. package/lib/task/events.ts +100 -42
  110. package/lib/task/finalization.ts +37 -7
  111. package/lib/task/lifecycle.ts +257 -109
  112. package/lib/task/orchestration.ts +260 -237
  113. package/lib/task/runtime-paths.ts +12 -0
  114. package/lib/task/short-id.ts +33 -40
  115. package/lib/task/verification-engine.ts +30 -25
  116. package/lib/task/workspace-snapshot.ts +1 -1
  117. package/package.json +1 -1
  118. package/templates/.agents/hooks/lifecycle-delegation.js +1 -25
  119. package/templates/.agents/rules/README.en.md +2 -1
  120. package/templates/.agents/rules/README.zh-CN.md +2 -1
  121. package/templates/.agents/rules/next-step-output.en.md +6 -19
  122. package/templates/.agents/rules/next-step-output.zh-CN.md +6 -19
  123. package/templates/.agents/rules/strategy-pattern.en.md +25 -0
  124. package/templates/.agents/rules/strategy-pattern.zh-CN.md +25 -0
  125. package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -1
  126. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -1
  127. package/templates/.agents/skills/check-task/SKILL.en.md +1 -1
  128. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +1 -1
  129. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  130. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
  131. package/templates/.agents/skills/code-task/reference/code-rules.en.md +4 -0
  132. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +4 -0
  133. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +0 -1
  134. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +0 -1
  135. package/templates/.agents/skills/complete-task/SKILL.en.md +5 -29
  136. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +5 -26
  137. package/templates/.agents/skills/plan-task/SKILL.en.md +7 -4
  138. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +7 -4
  139. package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
  140. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
  141. package/templates/.agents/skills/review-code/SKILL.en.md +1 -1
  142. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +1 -1
  143. package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
  144. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
  145. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
  146. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
  147. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  148. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  149. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +0 -546
  150. package/dist/lib/internal/codex-lifecycle.js +0 -385
  151. package/dist/lib/task/codex-orchestration.js +0 -206
  152. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +0 -776
  153. package/lib/internal/codex-lifecycle.ts +0 -390
  154. package/lib/task/codex-orchestration.ts +0 -276
@@ -8,7 +8,7 @@ import { validateCurrentTaskContract } from './current-contract.ts';
8
8
  import { parseReviewSummary } from './review-artifacts.ts';
9
9
  import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.ts';
10
10
  import type { LedgerDocument } from './ledger.ts';
11
- import { resolveTaskRef } from './resolve-ref.ts';
11
+ import { resolveTaskRef, TASK_ID_RE } from './resolve-ref.ts';
12
12
  import {
13
13
  activateDelegation,
14
14
  abortActivatedDelegation,
@@ -23,14 +23,13 @@ import {
23
23
  sealDelegation
24
24
  } from './delegation-receipts.ts';
25
25
  import type {
26
- DelegationLifecycleProvenance,
27
26
  DelegationReceipt,
28
27
  DelegationRole,
29
28
  DelegationStage
30
29
  } from './delegation-receipts.ts';
31
30
  import { normalizeAgentClients } from '../agent-clients/config.ts';
32
- import { resolveAgentRuntimeStoreRoot } from '../runtime/agent-runtime.ts';
33
31
  import {
32
+ getAgentClientAdapter,
34
33
  getAgentClientCapability,
35
34
  getAgentClientDelegationEvidence,
36
35
  getAgentClientModelSelection
@@ -50,8 +49,6 @@ import type { RepositorySnapshot } from './workspace-snapshot.ts';
50
49
  import type { WorkspaceSnapshotContext } from './workspace-snapshot.ts';
51
50
  import { assertGitRepositoryBinding } from '../git/worktree-identity.ts';
52
51
  import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
53
- import { hasActiveCodexLifecycleEvidence } from '../agent-clients/adapters/codex-lifecycle/store.ts';
54
- import type { CodexCapabilityProvenanceDetail } from '../agent-clients/adapters/codex-lifecycle/capability-store.ts';
55
52
  import { extractReviewBaseline, extractReviewDiffBase, extractReviewTargetHead, extractReviewedHead } from './review-fingerprint.ts';
56
53
  import { resolveDeliveryTarget, resolveDiffBase, resolveTargetHead } from './delivery-target.ts';
57
54
  import { buildLifecycleFacts, canStart, recommendNext } from './capabilities.ts';
@@ -66,24 +63,23 @@ type ModelPolicySource = Readonly<{
66
63
  client: AgentClientId;
67
64
  resolvedAt: string;
68
65
  }>;
69
- type ClaudeCodeCapabilityRecovery = Readonly<{
66
+ type ClientCapabilityRecovery = Readonly<{
70
67
  code: 'CLIENT_CAPABILITY_ENABLED';
71
68
  recoveredAt: string;
72
69
  previousStatus: 'paused';
73
70
  previousPause: Readonly<{ code: 'ORCHESTRATION_CLIENT_UNSUPPORTED'; message: string; recoverable: boolean }>;
74
- client: 'claude-code';
71
+ client: AgentClientId;
75
72
  guards: Readonly<{
76
73
  stepCount: 0;
77
74
  nextStage: null;
78
75
  baselineEmpty: true;
79
76
  receiptCount: 0;
80
77
  pendingDelegation: false;
81
- commitAuthorizationUnused: true;
82
78
  completionEvidenceAbsent: true;
83
79
  }>;
84
80
  resultingStatus: 'running';
85
81
  }>;
86
- type OrchestrationRecovery = ClaudeCodeCapabilityRecovery;
82
+ type OrchestrationRecovery = ClientCapabilityRecovery;
87
83
  type CleanCompletionEvidence = Readonly<{
88
84
  kind: 'reviewed-head-clean';
89
85
  observedAt: string;
@@ -108,7 +104,6 @@ type OrchestrationRun = Readonly<{
108
104
  pendingDelegation: DelegationReceipt | null;
109
105
  receipts: readonly DelegationReceipt[];
110
106
  pause: Readonly<{ code: string; message: string; recoverable: boolean }> | null;
111
- commitAuthorization: Readonly<{ issuedAt: string | null; consumedAt: string | null }>;
112
107
  completionEvidence: CleanCompletionEvidence | null;
113
108
  createdAt: string;
114
109
  updatedAt: string;
@@ -128,6 +123,7 @@ type OrchestrationResult = Readonly<{
128
123
  taskId: string | null;
129
124
  run: OrchestrationRun | null;
130
125
  next: OrchestrationNext | null;
126
+ adapterContext?: string;
131
127
  warnings?: readonly Readonly<{ code: string; message: string; action: string }>[];
132
128
  error: Readonly<{
133
129
  code: string;
@@ -135,7 +131,7 @@ type OrchestrationResult = Readonly<{
135
131
  client?: AgentClientId;
136
132
  missingFields?: readonly string[];
137
133
  modelSelectionContext?: ReturnType<typeof getAgentClientModelSelection>;
138
- detail?: CodexCapabilityProvenanceDetail;
134
+ detail?: unknown;
139
135
  }> | null;
140
136
  }>;
141
137
  type OrchestrationStageIdentity = Readonly<{
@@ -155,6 +151,7 @@ type OrchestrationCompletionPlanResult = Readonly<{
155
151
  }>;
156
152
  type OrchestrationOptions = {
157
153
  repoRoot?: string;
154
+ taskId?: string;
158
155
  gitWorktreeRoot?: string;
159
156
  diagnosticLog?: OrchestrationDiagnosticLogger;
160
157
  id?: () => string;
@@ -191,7 +188,7 @@ function supportsLifecycleDelegation(client: AgentClientId): boolean {
191
188
  }
192
189
 
193
190
  function orchestrationPath(taskDir: string): string {
194
- return path.join(taskDir, 'orchestration.json');
191
+ return path.join(taskDir, '.runtime', 'orchestration.json');
195
192
  }
196
193
 
197
194
  const ORCHESTRATION_STATE_INVALID_MESSAGE = 'orchestration.json does not match the current runtime structure; the file was left unchanged; rebuild the sandbox or manually repair the state before retrying';
@@ -250,7 +247,7 @@ function isModelPolicy(value: unknown): value is OrchestrationModelPolicy {
250
247
 
251
248
  const RECOVERY_GUARD_KEYS = [
252
249
  'stepCount', 'nextStage', 'baselineEmpty', 'receiptCount', 'pendingDelegation',
253
- 'commitAuthorizationUnused', 'completionEvidenceAbsent'
250
+ 'completionEvidenceAbsent'
254
251
  ] as const;
255
252
 
256
253
  function isRecovery(value: unknown): value is OrchestrationRecovery {
@@ -263,14 +260,13 @@ function isRecovery(value: unknown): value is OrchestrationRecovery {
263
260
  && value.previousStatus === 'paused'
264
261
  && isPause(value.previousPause)
265
262
  && value.previousPause.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED'
266
- && value.client === 'claude-code'
263
+ && isAgentClientId(value.client)
267
264
  && hasExactKeys(guards, RECOVERY_GUARD_KEYS)
268
265
  && guards.stepCount === 0
269
266
  && guards.nextStage === null
270
267
  && guards.baselineEmpty === true
271
268
  && guards.receiptCount === 0
272
269
  && guards.pendingDelegation === false
273
- && guards.commitAuthorizationUnused === true
274
270
  && guards.completionEvidenceAbsent === true
275
271
  && value.resultingStatus === 'running';
276
272
  }
@@ -292,15 +288,14 @@ export function isCompletionEvidence(value: unknown): value is CleanCompletionEv
292
288
  const ORCHESTRATION_RUN_KEYS = [
293
289
  'taskId', 'runId', 'status', 'nextStage', 'stepCount', 'maxSteps', 'modelPolicy',
294
290
  'modelPolicySource', 'recoveryHistory', 'baseline', 'pendingDelegation', 'receipts',
295
- 'pause', 'commitAuthorization', 'completionEvidence', 'createdAt', 'updatedAt'
291
+ 'pause', 'completionEvidence', 'createdAt', 'updatedAt'
296
292
  ] as const;
297
293
 
298
- function orchestrationStateReasonCodes(value: unknown, expectedTaskId?: string): string[] {
294
+ function orchestrationStateReasonCodes(value: unknown): string[] {
299
295
  if (!hasExactKeys(value, ORCHESTRATION_RUN_KEYS)) return ['top-level-keys'];
300
296
  const record = value;
301
297
  const reasons: string[] = [];
302
298
  if (!exactText(record.taskId)) reasons.push('taskId');
303
- else if (expectedTaskId !== undefined && record.taskId !== expectedTaskId) reasons.push('taskId-mismatch');
304
299
  if (!exactText(record.runId)) reasons.push('runId');
305
300
  if (!['running', 'paused', 'completed'].includes(record.status as string)) reasons.push('status');
306
301
  if (!(record.nextStage === null || ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code', 'commit'].includes(record.nextStage as string))) {
@@ -315,19 +310,13 @@ function orchestrationStateReasonCodes(value: unknown, expectedTaskId?: string):
315
310
  if (!(record.pendingDelegation === null || isDelegationReceipt(record.pendingDelegation))) reasons.push('pendingDelegation');
316
311
  if (!Array.isArray(record.receipts) || !record.receipts.every(isDelegationReceipt)) reasons.push('receipts');
317
312
  if (!(record.pause === null || isPause(record.pause))) reasons.push('pause');
318
- if (!hasExactKeys(record.commitAuthorization, ['issuedAt', 'consumedAt'])) {
319
- reasons.push('commitAuthorization');
320
- } else {
321
- if (!nullableText(record.commitAuthorization.issuedAt)) reasons.push('commitAuthorization.issuedAt');
322
- if (!nullableText(record.commitAuthorization.consumedAt)) reasons.push('commitAuthorization.consumedAt');
323
- }
324
313
  if (!(record.completionEvidence === null || isCompletionEvidence(record.completionEvidence))) reasons.push('completionEvidence');
325
314
  if (!exactText(record.createdAt)) reasons.push('createdAt');
326
315
  if (!exactText(record.updatedAt)) reasons.push('updatedAt');
327
316
  if (Array.isArray(record.receipts) && record.receipts.every(isDelegationReceipt)
328
317
  && (record.pendingDelegation === null || isDelegationReceipt(record.pendingDelegation))) {
329
318
  const receipts = [...record.receipts, ...(record.pendingDelegation ? [record.pendingDelegation] : [])];
330
- if (receipts.some((receipt) => receipt.taskId !== record.taskId || receipt.runId !== record.runId)) {
319
+ if (receipts.some((receipt) => receipt.runId !== record.runId)) {
331
320
  reasons.push('receipt-identity');
332
321
  }
333
322
  }
@@ -375,7 +364,7 @@ function fileObservation(file: string): OrchestrationDiagnosticFields {
375
364
  }
376
365
 
377
366
  function parseOrchestrationRun(value: unknown, expectedTaskId?: string): OrchestrationRun {
378
- const reasonCodes = orchestrationStateReasonCodes(value, expectedTaskId);
367
+ const reasonCodes = orchestrationStateReasonCodes(value);
379
368
  if (reasonCodes.length > 0) throw new OrchestrationStateError(expectedTaskId ?? null, reasonCodes);
380
369
  return value as OrchestrationRun;
381
370
  }
@@ -420,7 +409,100 @@ function readRun(taskDir: string, options: Pick<OrchestrationOptions, 'diagnosti
420
409
  }
421
410
  }
422
411
 
412
+ function findDelegation(run: OrchestrationRun, receiptId: string): Readonly<{
413
+ receipt: DelegationReceipt;
414
+ location: 'pending' | 'history';
415
+ }> | null {
416
+ if (run.pendingDelegation?.id === receiptId) return { receipt: run.pendingDelegation, location: 'pending' };
417
+ const receipt = run.receipts.find((entry) => entry.id === receiptId);
418
+ return receipt ? { receipt, location: 'history' } : null;
419
+ }
420
+
421
+ function readDelegationAdapterEvidence(
422
+ taskRef: string,
423
+ receiptId: string,
424
+ adapterId: AgentClientId,
425
+ options: OrchestrationOptions = {}
426
+ ): unknown {
427
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
428
+ if (!resolved.ok) throw new Error(`${resolved.code}: ${resolved.message}`);
429
+ const run = readRun(resolved.taskDir, options);
430
+ const found = run && findDelegation(run, receiptId);
431
+ if (!found || found.receipt.client !== adapterId) return null;
432
+ return found.receipt.adapterEvidence?.[adapterId] ?? null;
433
+ }
434
+
435
+ function mutateDelegationAdapterEvidence(
436
+ taskRef: string,
437
+ receiptId: string,
438
+ adapterId: AgentClientId,
439
+ mutate: (current: unknown) => unknown,
440
+ options: OrchestrationOptions = {}
441
+ ): unknown {
442
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
443
+ if (!resolved.ok) throw new Error(`${resolved.code}: ${resolved.message}`);
444
+ return mutateDelegationAdapterEvidenceAtResolved(resolved, receiptId, adapterId, mutate, options, false);
445
+ }
446
+
447
+ function mutateDelegationAdapterEvidenceWithinTaskLock(
448
+ taskRef: string,
449
+ receiptId: string,
450
+ adapterId: AgentClientId,
451
+ mutate: (current: unknown) => unknown,
452
+ options: OrchestrationOptions = {}
453
+ ): unknown {
454
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
455
+ if (!resolved.ok) throw new Error(`${resolved.code}: ${resolved.message}`);
456
+ return mutateDelegationAdapterEvidenceAtResolved(resolved, receiptId, adapterId, mutate, options, true);
457
+ }
458
+
459
+ function mutateDelegationAdapterEvidenceAtResolved(
460
+ resolved: Extract<ReturnType<typeof resolveTaskRef>, { ok: true }>,
461
+ receiptId: string,
462
+ adapterId: AgentClientId,
463
+ mutate: (current: unknown) => unknown,
464
+ options: OrchestrationOptions,
465
+ taskLockHeld: boolean
466
+ ): unknown {
467
+ const update = () => {
468
+ try {
469
+ const run = readRun(resolved.taskDir, options);
470
+ const found = run && findDelegation(run, receiptId);
471
+ if (!run || !found || found.receipt.client !== adapterId) {
472
+ throw new Error('ORCHESTRATION_DELEGATION_MISSING: adapter evidence has no matching task receipt');
473
+ }
474
+ const current = found.receipt.adapterEvidence?.[adapterId] ?? null;
475
+ const nextState = mutate(current);
476
+ const serialized = JSON.stringify(nextState);
477
+ if (serialized === undefined) throw new Error('Agent adapter evidence must be JSON serializable');
478
+ if (serialized === JSON.stringify(current)) return JSON.parse(serialized) as unknown;
479
+ const adapterEvidence = Object.freeze({
480
+ ...(found.receipt.adapterEvidence ?? {}),
481
+ [adapterId]: JSON.parse(serialized) as unknown
482
+ });
483
+ const nextReceipt = Object.freeze({ ...found.receipt, adapterEvidence });
484
+ const updated = found.location === 'pending'
485
+ ? withUpdatedRun(run, { pendingDelegation: nextReceipt }, options.now)
486
+ : withUpdatedRun(run, {
487
+ receipts: Object.freeze(run.receipts.map((receipt) => receipt.id === receiptId ? nextReceipt : receipt))
488
+ }, options.now);
489
+ saveRun(resolved.taskDir, updated);
490
+ return JSON.parse(serialized) as unknown;
491
+ } catch (error) {
492
+ mutationError = error;
493
+ return null;
494
+ }
495
+ };
496
+ let mutationError: unknown;
497
+ const result = taskLockHeld
498
+ ? update()
499
+ : withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `agent-adapter.${adapterId}.evidence`, update);
500
+ if (mutationError) throw mutationError;
501
+ return result;
502
+ }
503
+
423
504
  function atomicWrite(file: string, value: unknown): void {
505
+ fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
424
506
  const temp = `${file}.${process.pid}.${randomUUID()}.tmp`;
425
507
  fs.writeFileSync(temp, `${JSON.stringify(value, null, 2)}\n`, { encoding: 'utf8', flag: 'wx' });
426
508
  fs.renameSync(temp, file);
@@ -483,17 +565,16 @@ function resolveProjectPolicy(repoRoot: string, client: AgentClientId): Orchestr
483
565
  return normalizeAgentClients(raw).state[client].orchestration;
484
566
  }
485
567
 
486
- function canRecoverClaudeCodeUnsupportedPause(run: OrchestrationRun, taskDir: string): boolean {
568
+ function canRecoverUnsupportedClientPause(run: OrchestrationRun): boolean {
487
569
  return run.status === 'paused'
488
570
  && run.pause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED'
489
- && run.modelPolicySource?.client === 'claude-code'
571
+ && isAgentClientId(run.modelPolicySource?.client)
572
+ && supportsLifecycleDelegation(run.modelPolicySource.client)
490
573
  && run.stepCount === 0
491
574
  && run.nextStage === null
492
575
  && run.baseline === ''
493
576
  && run.pendingDelegation === null
494
577
  && run.receipts.length === 0
495
- && run.commitAuthorization?.issuedAt === null
496
- && run.commitAuthorization?.consumedAt === null
497
578
  && run.completionEvidence == null
498
579
  && run.recoveryHistory.length === 0;
499
580
  }
@@ -525,24 +606,22 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
525
606
  }
526
607
  }
527
608
  if (existing.status === 'paused' && existing.pause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED') {
528
- if (existing.modelPolicySource.client !== 'claude-code'
529
- || !canRecoverClaudeCodeUnsupportedPause(existing, resolved.taskDir)) {
609
+ if (!canRecoverUnsupportedClientPause(existing)) {
530
610
  return { status: 'paused', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
531
611
  }
532
612
  const now = (options.now ?? (() => new Date().toISOString()))();
533
- const recovery: ClaudeCodeCapabilityRecovery = {
613
+ const recovery: ClientCapabilityRecovery = {
534
614
  code: 'CLIENT_CAPABILITY_ENABLED',
535
615
  recoveredAt: now,
536
616
  previousStatus: 'paused',
537
- previousPause: existing.pause as ClaudeCodeCapabilityRecovery['previousPause'],
538
- client: 'claude-code',
617
+ previousPause: existing.pause as ClientCapabilityRecovery['previousPause'],
618
+ client: existing.modelPolicySource.client,
539
619
  guards: {
540
620
  stepCount: 0,
541
621
  nextStage: null,
542
622
  baselineEmpty: true,
543
623
  receiptCount: 0,
544
624
  pendingDelegation: false,
545
- commitAuthorizationUnused: true,
546
625
  completionEvidenceAbsent: true
547
626
  },
548
627
  resultingStatus: 'running'
@@ -595,7 +674,6 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
595
674
  pendingDelegation: null,
596
675
  receipts: [],
597
676
  pause: null,
598
- commitAuthorization: { issuedAt: null, consumedAt: null },
599
677
  completionEvidence: null,
600
678
  createdAt: now,
601
679
  updatedAt: now
@@ -828,13 +906,58 @@ function statusOrchestration(taskRef: string, options: OrchestrationOptions = {}
828
906
  return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
829
907
  }
830
908
 
909
+ function validatePreparedModelPolicy(
910
+ taskId: string,
911
+ run: OrchestrationRun,
912
+ next: NonNullable<OrchestrationResult['next']>,
913
+ input: Readonly<{ requestedModel?: string; requestedReasoningEffort?: string }>
914
+ ): OrchestrationResult | null {
915
+ if (!validModel(input.requestedModel)) {
916
+ return failed('ORCHESTRATION_REQUESTED_MODEL_REQUIRED', 'prepare requires the exact requested model identity', taskId);
917
+ }
918
+ if (!validModel(input.requestedReasoningEffort)) {
919
+ return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_REQUIRED', 'prepare requires the exact requested reasoning effort', taskId);
920
+ }
921
+ const expectedPolicy = run.modelPolicy[next.role];
922
+ if (input.requestedModel !== expectedPolicy.model) {
923
+ return failed('ORCHESTRATION_REQUESTED_MODEL_MISMATCH', `requested model does not match the persisted ${next.role} model`, taskId);
924
+ }
925
+ if (input.requestedReasoningEffort !== expectedPolicy.reasoningEffort) {
926
+ return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_MISMATCH', `requested reasoning effort does not match the persisted ${next.role} policy`, taskId);
927
+ }
928
+ return null;
929
+ }
930
+
931
+ function captureTaskSnapshot(taskId: string, repoRoot: string, options: OrchestrationOptions): string {
932
+ return (options.captureWorkspace ?? captureWorkspaceSnapshot)({
933
+ gitRoot: gitRootFor(repoRoot, options),
934
+ stateRoot: repoRoot,
935
+ taskId
936
+ });
937
+ }
938
+
939
+ function validateDelegationRun(
940
+ taskRef: string,
941
+ taskId: string,
942
+ run: OrchestrationRun | null,
943
+ options: OrchestrationOptions
944
+ ): OrchestrationResult | null {
945
+ if (!run || run.status !== 'running') {
946
+ return failed('ORCHESTRATION_RUN_NOT_RUNNING', 'a running orchestration is required', taskId);
947
+ }
948
+ if (run.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_BUSY', 'the run already has a pending delegation', taskId);
949
+ if (run.stepCount >= run.maxSteps) {
950
+ return pauseOrchestration(taskRef, 'ORCHESTRATION_MAX_STEPS', 'maximum orchestration steps reached', true, options);
951
+ }
952
+ return null;
953
+ }
954
+
831
955
  function prepareOrchestrationDelegationUnlocked(
832
956
  taskRef: string,
833
957
  input: Readonly<{
834
958
  client: AgentClientId;
835
959
  requestedModel?: string;
836
960
  requestedReasoningEffort?: string;
837
- lifecycleProvenance?: DelegationLifecycleProvenance;
838
961
  }>,
839
962
  options: OrchestrationOptions = {}
840
963
  ): OrchestrationResult {
@@ -852,54 +975,34 @@ function prepareOrchestrationDelegationUnlocked(
852
975
  requestedReasoningEffort: input.requestedReasoningEffort ?? null
853
976
  });
854
977
  const run = readRun(resolved.taskDir, options);
855
- if (!run || run.status !== 'running') return failed('ORCHESTRATION_RUN_NOT_RUNNING', 'a running orchestration is required', resolved.taskId);
856
- if (run.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_BUSY', 'the run already has a pending delegation', resolved.taskId);
857
- const repositoryPending = matchingDelegations(() => true, options);
858
- if (repositoryPending.length > 0) {
859
- return failed('ORCHESTRATION_DELEGATION_BUSY', 'the repository already has a pending lifecycle delegation', resolved.taskId);
860
- }
861
- if (run.stepCount >= run.maxSteps) return pauseOrchestration(taskRef, 'ORCHESTRATION_MAX_STEPS', 'maximum orchestration steps reached', true, options);
978
+ const runFailure = validateDelegationRun(taskRef, resolved.taskId, run, options);
979
+ if (runFailure) return runFailure;
980
+ const activeRun = run!;
862
981
  const routed = routeOrchestration(taskRef, options);
863
982
  if (!routed.next) return routed;
864
983
  const next = routed.next;
865
- if (!validModel(input.requestedModel)) {
866
- return failed('ORCHESTRATION_REQUESTED_MODEL_REQUIRED', 'prepare requires the exact requested model identity', resolved.taskId);
867
- }
868
- if (!validModel(input.requestedReasoningEffort)) {
869
- return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_REQUIRED', 'prepare requires the exact requested reasoning effort', resolved.taskId);
870
- }
871
- const expectedPolicy = run.modelPolicy[next.role];
872
- if (input.requestedModel !== expectedPolicy.model) {
873
- return failed('ORCHESTRATION_REQUESTED_MODEL_MISMATCH', `requested model does not match the persisted ${next.role} model`, resolved.taskId);
874
- }
875
- if (input.requestedReasoningEffort !== expectedPolicy.reasoningEffort) {
876
- return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_MISMATCH', `requested reasoning effort does not match the persisted ${next.role} policy`, resolved.taskId);
877
- }
984
+ const policyFailure = validatePreparedModelPolicy(resolved.taskId, activeRun, next, input);
985
+ if (policyFailure) return policyFailure;
878
986
  let beforeFingerprint: string;
879
987
  try {
880
- beforeFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)({
881
- gitRoot: gitRootFor(resolved.repoRoot, options),
882
- stateRoot: resolved.repoRoot,
883
- taskId: resolved.taskId
884
- });
988
+ beforeFingerprint = captureTaskSnapshot(resolved.taskId, resolved.repoRoot, options);
885
989
  } catch (error) {
886
990
  return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
887
991
  }
888
992
  const receipt = prepareDelegation({
889
993
  taskId: resolved.taskId,
890
- runId: run.runId,
994
+ runId: activeRun.runId,
891
995
  role: next.role,
892
996
  stage: next.stage,
893
997
  round: next.round,
894
998
  artifact: next.artifact,
895
999
  client: input.client,
896
- requestedModel: input.requestedModel,
897
- requestedReasoningEffort: input.requestedReasoningEffort,
1000
+ requestedModel: input.requestedModel!,
1001
+ requestedReasoningEffort: input.requestedReasoningEffort!,
898
1002
  workspaceSnapshotScope: 'task',
899
- lifecycleProvenance: input.lifecycleProvenance ?? null,
900
1003
  beforeFingerprint
901
1004
  }, { id: options.id, now: options.now, monotonicNow: options.monotonicNow });
902
- const updated = withUpdatedRun(run, {
1005
+ const updated = withUpdatedRun(activeRun, {
903
1006
  nextStage: next.stage,
904
1007
  pendingDelegation: receipt
905
1008
  });
@@ -913,7 +1016,6 @@ function prepareOrchestrationDelegation(
913
1016
  client: AgentClientId;
914
1017
  requestedModel?: string;
915
1018
  requestedReasoningEffort?: string;
916
- lifecycleProvenance?: DelegationLifecycleProvenance;
917
1019
  }>,
918
1020
  options: OrchestrationOptions = {}
919
1021
  ): OrchestrationResult {
@@ -922,14 +1024,9 @@ function prepareOrchestrationDelegation(
922
1024
  try {
923
1025
  return withTaskExecutionLock(
924
1026
  resolved.repoRoot,
925
- '__repository__',
926
- 'task-orchestration.prepare.repository',
927
- () => withTaskExecutionLock(
928
- resolved.repoRoot,
929
- resolved.taskId,
930
- 'task-orchestration.prepare.task',
931
- () => prepareOrchestrationDelegationUnlocked(taskRef, input, options)
932
- )
1027
+ resolved.taskId,
1028
+ 'task-orchestration.prepare.task',
1029
+ () => prepareOrchestrationDelegationUnlocked(taskRef, input, options)
933
1030
  );
934
1031
  } catch (error) {
935
1032
  if (error instanceof TaskExecutionLockError) return failed(error.code, error.message, resolved.taskId);
@@ -967,14 +1064,9 @@ function dispatchOrchestrationDelegation(
967
1064
  try {
968
1065
  return withTaskExecutionLock(
969
1066
  resolved.repoRoot,
970
- '__repository__',
971
- 'task-orchestration.dispatch.repository',
972
- () => withTaskExecutionLock(
973
- resolved.repoRoot,
974
- resolved.taskId,
975
- 'task-orchestration.dispatch.task',
976
- () => dispatchOrchestrationDelegationUnlocked(taskRef, options)
977
- )
1067
+ resolved.taskId,
1068
+ 'task-orchestration.dispatch.task',
1069
+ () => dispatchOrchestrationDelegationUnlocked(taskRef, options)
978
1070
  );
979
1071
  } catch (error) {
980
1072
  if (error instanceof TaskExecutionLockError) return failed(error.code, error.message, resolved.taskId);
@@ -982,49 +1074,28 @@ function dispatchOrchestrationDelegation(
982
1074
  }
983
1075
  }
984
1076
 
1077
+ type MatchingDelegationsResult =
1078
+ | Readonly<{ ok: true; matches: readonly { taskId: string; run: OrchestrationRun }[] }>
1079
+ | Readonly<{ ok: false; error: OrchestrationResult }>;
1080
+
985
1081
  function matchingDelegations(
986
1082
  predicate: (receipt: DelegationReceipt) => boolean,
987
1083
  options: OrchestrationOptions
988
- ): Array<{ taskId: string; run: OrchestrationRun }> {
1084
+ ): MatchingDelegationsResult {
1085
+ const taskId: unknown = options.taskId;
1086
+ if (taskId === undefined || taskId === '') {
1087
+ return { ok: false, error: failed('ORCHESTRATION_TASK_ID_REQUIRED', 'taskId is required for lifecycle matching') };
1088
+ }
1089
+ if (typeof taskId !== 'string' || !TASK_ID_RE.test(taskId)) {
1090
+ return { ok: false, error: failed('ORCHESTRATION_TASK_ID_INVALID', 'taskId must be a canonical TASK-id') };
1091
+ }
989
1092
  const repoRoot = options.repoRoot ?? process.cwd();
990
- const activeRoot = path.join(repoRoot, '.agents', 'workspace', 'active');
991
- if (!fs.existsSync(activeRoot)) return [];
992
- emitDiagnostic(options, 'orchestration-active-scan-start', {
993
- activeRoot
994
- });
995
- const matches = fs.readdirSync(activeRoot, { withFileTypes: true })
996
- .filter((entry) => entry.isDirectory())
997
- .flatMap((entry) => {
998
- const candidateTaskId = entry.name;
999
- const candidateTaskDir = path.join(activeRoot, candidateTaskId);
1000
- emitDiagnostic(options, 'orchestration-active-scan-candidate', {
1001
- candidateTaskId,
1002
- candidateTaskDir,
1003
- orchestrationPath: orchestrationPath(candidateTaskDir)
1004
- });
1005
- let run: OrchestrationRun | null;
1006
- try {
1007
- run = readRun(candidateTaskDir, options);
1008
- } catch (error) {
1009
- emitDiagnostic(options, 'orchestration-active-scan-candidate-failed', {
1010
- candidateTaskId,
1011
- candidateTaskDir,
1012
- errorType: error instanceof Error ? error.name : typeof error,
1013
- reasonCodes: error instanceof OrchestrationStateError ? error.reasonCodes.join(',') : null
1014
- });
1015
- throw error;
1016
- }
1017
- if (!run || run.status !== 'running' || !run.pendingDelegation) return [];
1018
- const receipt = run.pendingDelegation;
1019
- return predicate(receipt)
1020
- ? [{ taskId: candidateTaskId, run }]
1021
- : [];
1022
- });
1023
- emitDiagnostic(options, 'orchestration-active-scan-finished', {
1024
- activeRoot,
1025
- matchingDelegations: matches.length
1026
- });
1027
- return matches;
1093
+ const taskDir = path.join(repoRoot, '.agents', 'workspace', 'active', taskId);
1094
+ const run = readRun(taskDir, options);
1095
+ const matches = run && run.status === 'running' && run.pendingDelegation && predicate(run.pendingDelegation)
1096
+ ? [{ taskId, run }]
1097
+ : [];
1098
+ return { ok: true, matches };
1028
1099
  }
1029
1100
 
1030
1101
  function uniqueMatchingDelegation(
@@ -1032,12 +1103,13 @@ function uniqueMatchingDelegation(
1032
1103
  predicate: (receipt: DelegationReceipt) => boolean,
1033
1104
  options: OrchestrationOptions
1034
1105
  ): { taskId: string; run: OrchestrationRun } | OrchestrationResult {
1035
- const matches = matchingDelegations(
1106
+ const lookup = matchingDelegations(
1036
1107
  (receipt) => receipt.client === client && predicate(receipt),
1037
1108
  options
1038
1109
  );
1110
+ if (!lookup.ok) return lookup.error;
1111
+ const matches = lookup.matches;
1039
1112
  if (matches.length === 0) return failed('ORCHESTRATION_DELEGATION_MISSING', 'no matching lifecycle delegation exists');
1040
- if (matches.length > 1) return failed('ORCHESTRATION_DELEGATION_AMBIGUOUS', 'multiple lifecycle delegations match the native hook event');
1041
1113
  return matches[0]!;
1042
1114
  }
1043
1115
 
@@ -1046,13 +1118,14 @@ function hasActivatableOrchestrationDelegation(
1046
1118
  childId: string,
1047
1119
  options: OrchestrationOptions = {}
1048
1120
  ): boolean {
1049
- return matchingDelegations(
1121
+ const lookup = matchingDelegations(
1050
1122
  (receipt) => receipt.client === client && (
1051
1123
  receipt.status === 'prepared'
1052
1124
  || (receipt.childId === childId && ['activated', 'stage-completed'].includes(receipt.status))
1053
1125
  ),
1054
1126
  options
1055
- ).length > 0;
1127
+ );
1128
+ return lookup.ok && lookup.matches.length > 0;
1056
1129
  }
1057
1130
 
1058
1131
  function hasSealableOrchestrationDelegation(
@@ -1060,12 +1133,13 @@ function hasSealableOrchestrationDelegation(
1060
1133
  childId: string,
1061
1134
  options: OrchestrationOptions = {}
1062
1135
  ): boolean {
1063
- return matchingDelegations(
1136
+ const lookup = matchingDelegations(
1064
1137
  (receipt) => receipt.client === client
1065
1138
  && receipt.childId === childId
1066
1139
  && ['stage-completed', 'sealed'].includes(receipt.status),
1067
1140
  options
1068
- ).length > 0;
1141
+ );
1142
+ return lookup.ok && lookup.matches.length > 0;
1069
1143
  }
1070
1144
 
1071
1145
  function activateMatchingOrchestrationDelegation(
@@ -1095,7 +1169,8 @@ function activateMatchingOrchestrationDelegation(
1095
1169
  && (receipt.actualReasoningEffort ?? null) === foldBlankToNull(event.actualReasoningEffort)
1096
1170
  && receipt.modelFallbackReason === (event.modelFallbackReason ?? null)
1097
1171
  && receipt.reasoningEffortFallbackReason === (event.reasoningEffortFallbackReason ?? null)
1098
- && receipt.hostEvidence?.hookDefinitionHash === event.hostEvidence?.hookDefinitionHash;
1172
+ && (event.clientEvidence === undefined
1173
+ || JSON.stringify(receipt.adapterEvidence?.[client]) === JSON.stringify(event.clientEvidence));
1099
1174
  if (!sameEvidence) {
1100
1175
  return pauseOrchestration(replay.taskId, 'DELEGATION_REPLAY_CONFLICT', 'replayed native start evidence conflicts with the active receipt', true, options);
1101
1176
  }
@@ -1128,7 +1203,8 @@ function sealMatchingOrchestrationDelegation(
1128
1203
  modelFallbackReason?: string;
1129
1204
  reasoningEffortFallbackReason?: string;
1130
1205
  }>,
1131
- options: OrchestrationOptions = {}
1206
+ options: OrchestrationOptions = {},
1207
+ beforeSeal?: () => OrchestrationResult | null
1132
1208
  ): OrchestrationResult {
1133
1209
  const role = managedDelegationRole(event.nativeAgent);
1134
1210
  if (!role) return failed('DELEGATION_IGNORED', `subagent '${event.nativeAgent}' is not lifecycle-managed`);
@@ -1161,7 +1237,7 @@ function sealMatchingOrchestrationDelegation(
1161
1237
  actualReasoningEffort: event.actualReasoningEffort,
1162
1238
  modelFallbackReason: event.modelFallbackReason,
1163
1239
  reasoningEffortFallbackReason: event.reasoningEffortFallbackReason
1164
- }, options);
1240
+ }, { runId: receipt.runId, receiptId: receipt.id }, options, beforeSeal);
1165
1241
  } catch (error) {
1166
1242
  return pauseOrchestration(
1167
1243
  matched.taskId,
@@ -1173,68 +1249,6 @@ function sealMatchingOrchestrationDelegation(
1173
1249
  }
1174
1250
  }
1175
1251
 
1176
- function sealMatchingOrchestrationDelegationWithHostEvidence(
1177
- client: AgentClientId,
1178
- event: Readonly<{ nativeAgent: string; childId: string }>,
1179
- consumeEvidence: (receipt: DelegationReceipt) => Readonly<{
1180
- stopRevision: number;
1181
- consumer: string;
1182
- consumedAt: string;
1183
- }>,
1184
- options: OrchestrationOptions = {}
1185
- ): OrchestrationResult {
1186
- const role = managedDelegationRole(event.nativeAgent);
1187
- if (!role) return failed('DELEGATION_IGNORED', `subagent '${event.nativeAgent}' is not lifecycle-managed`);
1188
- const matched = uniqueMatchingDelegation(
1189
- client,
1190
- (receipt) => (
1191
- (receipt.status === 'stage-completed' || receipt.status === 'sealed')
1192
- && receipt.childId === event.childId
1193
- ),
1194
- options
1195
- );
1196
- if ('status' in matched) return matched;
1197
- const receipt = matched.run.pendingDelegation!;
1198
- if (receipt.status === 'sealed') {
1199
- return reconcileMatchingOrchestrationDelegation(client, event.childId, options);
1200
- }
1201
- if (receipt.role !== role) {
1202
- return pauseOrchestration(matched.taskId, 'DELEGATION_ROLE_MISMATCH', `managed role ${role} does not match ${receipt.role}`, true, options);
1203
- }
1204
- const repoRoot = options.repoRoot ?? process.cwd();
1205
- try {
1206
- const gitRoot = gitRootFor(repoRoot, options);
1207
- const snapshotTaskId = receipt.workspaceSnapshotScope === 'task' ? receipt.taskId : null;
1208
- const afterFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)({
1209
- gitRoot,
1210
- stateRoot: repoRoot,
1211
- taskId: snapshotTaskId
1212
- });
1213
- const changedPaths = (options.diffWorkspace ?? diffWorkspaceSnapshots)(gitRoot, receipt.beforeFingerprint, afterFingerprint);
1214
- const baseEvent = { childId: event.childId, exitCode: 0, afterFingerprint, changedPaths };
1215
- const validated = sealDelegation(receipt, baseEvent, { now: options.now, requireHostEvidence: false });
1216
- if (!validated.ok) {
1217
- return pauseOrchestration(matched.taskId, validated.code, validated.message, true, options);
1218
- }
1219
- const hostEvidence = consumeEvidence(receipt);
1220
- const sealed = sealDelegation(receipt, { ...baseEvent, hostEvidence }, { now: options.now });
1221
- if (!sealed.ok) return pauseOrchestration(matched.taskId, sealed.code, sealed.message, true, options);
1222
- const resolved = resolveTaskRef(matched.taskId, { repoRoot });
1223
- if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
1224
- const updated = withUpdatedRun(matched.run, { pendingDelegation: sealed.receipt }, options.now);
1225
- saveRun(resolved.taskDir, updated);
1226
- return { status: 'running', changed: true, taskId: matched.taskId, run: updated, next: null, error: null };
1227
- } catch (error) {
1228
- return pauseOrchestration(
1229
- matched.taskId,
1230
- 'ORCHESTRATION_CODEX_EVIDENCE_FAILED',
1231
- error instanceof Error ? error.message : String(error),
1232
- true,
1233
- options
1234
- );
1235
- }
1236
- }
1237
-
1238
1252
  function reconcileMatchingOrchestrationDelegation(
1239
1253
  client: AgentClientId,
1240
1254
  childId: string,
@@ -1247,15 +1261,11 @@ function reconcileMatchingOrchestrationDelegation(
1247
1261
  );
1248
1262
  if ('status' in matched) return matched;
1249
1263
  const receipt = matched.run.pendingDelegation!;
1250
- if (
1251
- receipt.status !== 'sealed'
1252
- || receipt.hostEvidence?.consumer !== receipt.id
1253
- || receipt.hostEvidence.consumedAt === null
1254
- ) {
1264
+ if (receipt.status !== 'sealed' || receipt.childId !== childId) {
1255
1265
  return pauseOrchestration(
1256
1266
  matched.taskId,
1257
- 'ORCHESTRATION_CODEX_RECONCILIATION_FAILED',
1258
- 'Codex native spawn did not produce one sealed receipt with consumed host evidence',
1267
+ 'ORCHESTRATION_DELEGATION_RECONCILIATION_FAILED',
1268
+ 'native child did not produce one sealed delegation receipt',
1259
1269
  true,
1260
1270
  options
1261
1271
  );
@@ -1275,9 +1285,21 @@ function activateOrchestrationDelegation(
1275
1285
  if (run.status !== 'running') {
1276
1286
  return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
1277
1287
  }
1288
+ const validateAdapterEvidence = getAgentClientAdapter(run.pendingDelegation.client)
1289
+ .orchestrationAdapter?.validateActivationEvidence;
1290
+ if (validateAdapterEvidence && !validateAdapterEvidence(event.clientEvidence, event.parentId)) {
1291
+ return pauseOrchestration(
1292
+ taskRef,
1293
+ 'DELEGATION_ADAPTER_EVIDENCE_REQUIRED',
1294
+ 'native activation lacks valid evidence from the agent adapter',
1295
+ true,
1296
+ options
1297
+ );
1298
+ }
1278
1299
  const result = activateDelegation(run.pendingDelegation, event, {
1279
1300
  now: options.now,
1280
- monotonicNow: options.monotonicNow
1301
+ monotonicNow: options.monotonicNow,
1302
+ evidencePolicy: getAgentClientDelegationEvidence(run.pendingDelegation.client)
1281
1303
  });
1282
1304
  if (!result.ok) {
1283
1305
  if (result.code === 'DELEGATION_IGNORED') return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
@@ -1380,22 +1402,12 @@ function recoverPreparedOrchestrationDelegation(
1380
1402
  ) {
1381
1403
  return failed('ORCHESTRATION_PREPARED_RECOVERY_EARLY', 'prepared receipt activation deadline has not elapsed', resolved.taskId);
1382
1404
  }
1383
- const activeLifecycleEvidence = options.hasActiveLifecycleEvidence?.(receipt)
1384
- ?? (
1385
- receipt.client === 'codex'
1386
- && receipt.lifecycleProvenance
1387
- && hasActiveCodexLifecycleEvidence(
1388
- resolveAgentRuntimeStoreRoot({ repoRoot: resolved.repoRoot, store: 'lifecycle' }),
1389
- {
1390
- nativeAgent: `agent-infra-lifecycle-${receipt.role}`,
1391
- hookDefinitionHash: receipt.lifecycleProvenance.hookDefinitionHash
1392
- }
1393
- )
1394
- );
1395
- if (activeLifecycleEvidence) {
1405
+ const activeAdapterEvidence = options.hasActiveLifecycleEvidence?.(receipt)
1406
+ ?? Object.keys(receipt.adapterEvidence ?? {}).length > 0;
1407
+ if (activeAdapterEvidence) {
1396
1408
  return failed(
1397
1409
  'ORCHESTRATION_PREPARED_RECOVERY_ACTIVE_EVIDENCE',
1398
- 'active Codex lifecycle evidence blocks prepared receipt recovery',
1410
+ 'active agent adapter evidence blocks prepared receipt recovery',
1399
1411
  resolved.taskId
1400
1412
  );
1401
1413
  }
@@ -1436,18 +1448,14 @@ type ActivatedRecoveryEvent = Readonly<{
1436
1448
  artifact: string;
1437
1449
  startedAgent: string;
1438
1450
  childId: string;
1439
- stopRevision: number;
1440
- consumer: string;
1441
- consumedAt: string;
1451
+ clientEvidence?: unknown;
1442
1452
  }>;
1443
1453
 
1444
1454
  function recoveryReceiptMatches(
1445
1455
  receipt: DelegationReceipt,
1446
- taskId: string,
1447
1456
  event: ActivatedRecoveryEvent
1448
1457
  ): boolean {
1449
- return receipt.taskId === taskId
1450
- && receipt.stage === event.stage
1458
+ return receipt.stage === event.stage
1451
1459
  && receipt.round === event.round
1452
1460
  && receipt.artifact === event.artifact
1453
1461
  && receipt.childId === event.childId
@@ -1469,7 +1477,7 @@ function recoverActivatedOrchestrationDelegationUnderLock(
1469
1477
  ];
1470
1478
  const receipt = matches.find((candidate) => candidate.id === event.receiptId);
1471
1479
  if (!receipt) return failed('ORCHESTRATION_DELEGATION_MISSING', 'recovery receipt does not exist', resolved.taskId);
1472
- if (!recoveryReceiptMatches(receipt, resolved.taskId, event)) {
1480
+ if (!recoveryReceiptMatches(receipt, event)) {
1473
1481
  return failed('ORCHESTRATION_PROVENANCE_MISMATCH', 'recovery selector does not match the delegation receipt', resolved.taskId);
1474
1482
  }
1475
1483
  if (receipt.status === 'aborted' && run.pendingDelegation === null) {
@@ -1486,9 +1494,7 @@ function recoverActivatedOrchestrationDelegationUnderLock(
1486
1494
  }
1487
1495
  const aborted = abortActivatedDelegation(receipt, {
1488
1496
  childId: event.childId,
1489
- stopRevision: event.stopRevision,
1490
- consumer: event.consumer,
1491
- consumedAt: event.consumedAt
1497
+ clientEvidence: event.clientEvidence
1492
1498
  });
1493
1499
  if (!aborted.ok) return failed(aborted.code, aborted.message, resolved.taskId);
1494
1500
  const updated = withUpdatedRun(run, {
@@ -1585,17 +1591,32 @@ function commitOrchestrationStageCompletion(plan: OrchestrationStageCompletion):
1585
1591
  function sealOrchestrationDelegation(
1586
1592
  taskRef: string,
1587
1593
  event: Parameters<typeof sealDelegation>[1],
1588
- options: OrchestrationOptions = {}
1594
+ expected?: Readonly<{ runId: string; receiptId: string }>,
1595
+ options: OrchestrationOptions = {},
1596
+ beforeSeal?: () => OrchestrationResult | null
1589
1597
  ): OrchestrationResult {
1590
- const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1598
+ const orchestrationOptions = options;
1599
+ const resolved = resolveTaskRef(taskRef, { repoRoot: orchestrationOptions.repoRoot });
1591
1600
  if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
1592
- const run = readRun(resolved.taskDir, options);
1593
- if (!run?.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
1594
- const result = sealDelegation(run.pendingDelegation, event, { now: options.now });
1595
- if (!result.ok) return pauseOrchestration(taskRef, result.code, result.message, true, options);
1596
- const updated = withUpdatedRun(run, { pendingDelegation: result.receipt });
1597
- saveRun(resolved.taskDir, updated);
1598
- return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
1601
+ return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-orchestration.seal', () => {
1602
+ const run = readRun(resolved.taskDir, orchestrationOptions);
1603
+ if (!run?.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
1604
+ if (expected && (run.runId !== expected.runId || run.pendingDelegation.id !== expected.receiptId)) {
1605
+ return failed('ORCHESTRATION_DELEGATION_CHANGED', 'pending delegation changed while collecting completion evidence', resolved.taskId);
1606
+ }
1607
+ const preSealFailure = beforeSeal?.();
1608
+ if (preSealFailure) return preSealFailure;
1609
+ const current = beforeSeal ? readRun(resolved.taskDir, orchestrationOptions) : run;
1610
+ if (!current?.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
1611
+ if (expected && (current.runId !== expected.runId || current.pendingDelegation.id !== expected.receiptId)) {
1612
+ return failed('ORCHESTRATION_DELEGATION_CHANGED', 'pending delegation changed before it was sealed', resolved.taskId);
1613
+ }
1614
+ const result = sealDelegation(current.pendingDelegation, event, { now: orchestrationOptions.now });
1615
+ if (!result.ok) return pauseOrchestration(taskRef, result.code, result.message, true, orchestrationOptions);
1616
+ const updated = withUpdatedRun(current, { pendingDelegation: result.receipt });
1617
+ saveRun(resolved.taskDir, updated);
1618
+ return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
1619
+ });
1599
1620
  }
1600
1621
 
1601
1622
  function advanceOrchestration(taskRef: string, options: OrchestrationOptions = {}): OrchestrationResult {
@@ -1649,6 +1670,9 @@ export {
1649
1670
  hasSealableOrchestrationDelegation,
1650
1671
  OrchestrationStateError,
1651
1672
  orchestrationPath,
1673
+ readDelegationAdapterEvidence,
1674
+ mutateDelegationAdapterEvidence,
1675
+ mutateDelegationAdapterEvidenceWithinTaskLock,
1652
1676
  pauseMatchingOrchestrationDelegation,
1653
1677
  pauseOrchestration,
1654
1678
  planOrchestrationStageCompletion,
@@ -1659,7 +1683,6 @@ export {
1659
1683
  recoverActivatedOrchestrationDelegationUnderLock,
1660
1684
  routeOrchestration,
1661
1685
  sealMatchingOrchestrationDelegation,
1662
- sealMatchingOrchestrationDelegationWithHostEvidence,
1663
1686
  sealOrchestrationDelegation,
1664
1687
  statusOrchestration,
1665
1688
  };