@fitlab-ai/agent-infra 0.9.6 → 0.9.8

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 (178) hide show
  1. package/dist/bin/internal-cli.js +10 -0
  2. package/dist/lib/agent-clients/adapters/claude-code.js +3 -3
  3. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +47 -5
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +162 -0
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +245 -0
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +398 -0
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +23 -6
  8. package/dist/lib/decide.js +68 -14
  9. package/dist/lib/defaults.json +10 -2
  10. package/dist/lib/git/workflow.js +30 -3
  11. package/dist/lib/init.js +1 -1
  12. package/dist/lib/internal/codex-lifecycle.js +69 -1
  13. package/dist/lib/internal/codex-sandbox-controller.js +106 -0
  14. package/dist/lib/internal/release-workflow.js +1 -3
  15. package/dist/lib/internal/task-activity.js +57 -7
  16. package/dist/lib/internal/task-event.js +57 -4
  17. package/dist/lib/internal/task-ledger.js +59 -5
  18. package/dist/lib/internal/task-lifecycle.js +59 -8
  19. package/dist/lib/internal/task-orchestration.js +48 -8
  20. package/dist/lib/internal/task-override.js +131 -0
  21. package/dist/lib/internal/task-review.js +64 -6
  22. package/dist/lib/internal/task-verify.js +65 -3
  23. package/dist/lib/internal/task-warning.js +57 -4
  24. package/dist/lib/platform/context.js +6 -4
  25. package/dist/lib/platform/github-client.js +55 -2
  26. package/dist/lib/platform/pr-summary.js +5 -3
  27. package/dist/lib/process-data/checkpoint.js +319 -0
  28. package/dist/lib/process-data/index.js +128 -60
  29. package/dist/lib/process-data/materialize.js +84 -0
  30. package/dist/lib/process-data/normalize.js +34 -1
  31. package/dist/lib/process-data/reconcile.js +55 -1
  32. package/dist/lib/process-data/sources.js +422 -1
  33. package/dist/lib/process-data/store.js +341 -11
  34. package/dist/lib/runtime/agent-runtime.js +56 -0
  35. package/dist/lib/sandbox/commands/create.js +394 -264
  36. package/dist/lib/sandbox/commands/rm.js +64 -15
  37. package/dist/lib/sandbox/commands/start.js +4 -3
  38. package/dist/lib/sandbox/control/container-identity.js +64 -0
  39. package/dist/lib/sandbox/control/executor.js +23 -9
  40. package/dist/lib/sandbox/control/lifecycle.js +590 -61
  41. package/dist/lib/sandbox/control/protocol.js +7 -0
  42. package/dist/lib/sandbox/control/server.js +202 -26
  43. package/dist/lib/sandbox/control/state.js +51 -25
  44. package/dist/lib/sandbox/control/timing.js +8 -0
  45. package/dist/lib/sandbox/engine.js +14 -7
  46. package/dist/lib/sandbox/mounts.js +7 -0
  47. package/dist/lib/sandbox/recovery.js +135 -50
  48. package/dist/lib/sandbox/workspace-view.js +59 -20
  49. package/dist/lib/server/daemon.js +1 -1
  50. package/dist/lib/server/process-control.js +11 -1
  51. package/dist/lib/server/process-state.js +107 -20
  52. package/dist/lib/task/activity-intent.js +4 -1
  53. package/dist/lib/task/artifact-lifecycle.js +82 -11
  54. package/dist/lib/task/artifact-receipts.js +128 -0
  55. package/dist/lib/task/codex-orchestration.js +81 -6
  56. package/dist/lib/task/commands/validate.js +1 -1
  57. package/dist/lib/task/create.js +10 -2
  58. package/dist/lib/task/decision-intents.js +4 -2
  59. package/dist/lib/task/delegation-receipts.js +120 -17
  60. package/dist/lib/task/events.js +148 -7
  61. package/dist/lib/task/guard-override.js +31 -0
  62. package/dist/lib/task/human-override.js +1240 -0
  63. package/dist/lib/task/ledger-intents.js +6 -3
  64. package/dist/lib/task/ledger.js +13 -1
  65. package/dist/lib/task/lifecycle.js +69 -14
  66. package/dist/lib/task/orchestration.js +293 -18
  67. package/dist/lib/task/review-finalization.js +3 -2
  68. package/dist/lib/task/sections.js +5 -1
  69. package/dist/lib/task/task-execution-lock.js +5 -4
  70. package/dist/lib/task/verification-engine.js +80 -22
  71. package/dist/lib/task/verification.js +13 -1
  72. package/dist/lib/task/workflow-warning-intents.js +4 -2
  73. package/dist/lib/task/write.js +2 -1
  74. package/dist/lifecycle-build-manifest.json +6 -0
  75. package/lib/agent-clients/adapters/claude-code.ts +3 -3
  76. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +62 -4
  77. package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +227 -0
  78. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +322 -0
  79. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +26 -0
  80. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +480 -0
  81. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +35 -5
  82. package/lib/decide.ts +59 -14
  83. package/lib/defaults.json +10 -2
  84. package/lib/git/workflow.ts +25 -2
  85. package/lib/init.ts +1 -1
  86. package/lib/internal/codex-lifecycle.ts +68 -1
  87. package/lib/internal/codex-sandbox-controller.ts +109 -0
  88. package/lib/internal/release-workflow.ts +1 -3
  89. package/lib/internal/task-activity.ts +46 -6
  90. package/lib/internal/task-event.ts +53 -4
  91. package/lib/internal/task-ledger.ts +48 -4
  92. package/lib/internal/task-lifecycle.ts +56 -12
  93. package/lib/internal/task-orchestration.ts +43 -8
  94. package/lib/internal/task-override.ts +138 -0
  95. package/lib/internal/task-review.ts +51 -6
  96. package/lib/internal/task-verify.ts +52 -3
  97. package/lib/internal/task-warning.ts +44 -3
  98. package/lib/platform/context.ts +8 -5
  99. package/lib/platform/github-client.ts +61 -3
  100. package/lib/platform/pr-summary.ts +10 -3
  101. package/lib/process-data/checkpoint.ts +335 -0
  102. package/lib/process-data/index.ts +112 -60
  103. package/lib/process-data/materialize.ts +83 -0
  104. package/lib/process-data/normalize.ts +29 -1
  105. package/lib/process-data/reconcile.ts +64 -2
  106. package/lib/process-data/sources.ts +449 -2
  107. package/lib/process-data/store.ts +304 -11
  108. package/lib/process-data/types.ts +124 -2
  109. package/lib/runtime/agent-runtime.ts +76 -0
  110. package/lib/sandbox/commands/create.ts +139 -6
  111. package/lib/sandbox/commands/rm.ts +64 -14
  112. package/lib/sandbox/commands/start.ts +4 -3
  113. package/lib/sandbox/config.ts +2 -1
  114. package/lib/sandbox/control/container-identity.ts +93 -0
  115. package/lib/sandbox/control/executor.ts +24 -12
  116. package/lib/sandbox/control/lifecycle.ts +685 -67
  117. package/lib/sandbox/control/protocol.ts +35 -6
  118. package/lib/sandbox/control/server.ts +184 -24
  119. package/lib/sandbox/control/state.ts +67 -33
  120. package/lib/sandbox/control/timing.ts +12 -0
  121. package/lib/sandbox/engine.ts +20 -9
  122. package/lib/sandbox/mounts.ts +8 -0
  123. package/lib/sandbox/recovery.ts +159 -49
  124. package/lib/sandbox/workspace-view.ts +73 -22
  125. package/lib/server/daemon.ts +1 -1
  126. package/lib/server/process-control.ts +13 -3
  127. package/lib/server/process-state.ts +129 -27
  128. package/lib/task/activity-intent.ts +4 -2
  129. package/lib/task/artifact-lifecycle.ts +73 -10
  130. package/lib/task/artifact-receipts.ts +161 -0
  131. package/lib/task/codex-orchestration.ts +91 -5
  132. package/lib/task/commands/validate.ts +1 -1
  133. package/lib/task/create.ts +11 -2
  134. package/lib/task/decision-intents.ts +4 -2
  135. package/lib/task/delegation-receipts.ts +205 -25
  136. package/lib/task/events.ts +132 -6
  137. package/lib/task/guard-override.ts +50 -0
  138. package/lib/task/human-override.ts +1503 -0
  139. package/lib/task/ledger-intents.ts +6 -3
  140. package/lib/task/ledger.ts +16 -1
  141. package/lib/task/lifecycle.ts +71 -14
  142. package/lib/task/orchestration.ts +443 -22
  143. package/lib/task/review-finalization.ts +6 -2
  144. package/lib/task/sections.ts +6 -2
  145. package/lib/task/task-execution-lock.ts +11 -9
  146. package/lib/task/verification-engine.ts +93 -32
  147. package/lib/task/verification.ts +15 -1
  148. package/lib/task/workflow-warning-intents.ts +4 -2
  149. package/lib/task/write.ts +4 -1
  150. package/package.json +1 -1
  151. package/templates/.agents/README.en.md +7 -0
  152. package/templates/.agents/README.zh-CN.md +5 -0
  153. package/templates/.agents/hooks/lifecycle-delegation.js +33 -4
  154. package/templates/.agents/rules/lifecycle-orchestration.en.md +10 -3
  155. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +10 -3
  156. package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
  157. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +1 -1
  158. package/templates/.agents/rules/review-handshake.en.md +3 -1
  159. package/templates/.agents/rules/review-handshake.zh-CN.md +3 -1
  160. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +3 -1
  161. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +3 -1
  162. package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
  163. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
  164. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +6 -6
  165. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +6 -6
  166. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +14 -3
  167. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +14 -3
  168. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +67 -0
  169. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +67 -0
  170. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +25 -0
  171. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +25 -0
  172. package/templates/.agents/skills/run-task/SKILL.en.md +9 -7
  173. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -7
  174. package/templates/.agents/skills/run-task/reference/host-validation.en.md +5 -1
  175. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +5 -1
  176. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
  177. package/templates/.agents/workspace/README.en.md +0 -26
  178. package/templates/.agents/workspace/README.zh-CN.md +0 -26
@@ -11,6 +11,8 @@ import { resolveTaskRef } from './resolve-ref.ts';
11
11
  import { validateLifecycleExecution } from './lifecycle-execution.ts';
12
12
  import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
13
13
  import type { ResolveTaskRefErrorCode } from './resolve-ref.ts';
14
+ import { allowsManualOverride } from './guard-override.ts';
15
+ import type { ManualOverrideCapability } from './guard-override.ts';
14
16
 
15
17
  type ReviewFinalizationErrorCode =
16
18
  | ResolveTaskRefErrorCode
@@ -62,6 +64,8 @@ type ReviewFinalizationOptions = {
62
64
  repoRoot?: string;
63
65
  randomSuffix?: () => string;
64
66
  fileSystem?: Partial<ReviewFileSystem>;
67
+ manualOverride?: ManualOverrideCapability;
68
+ lockAlreadyHeld?: boolean;
65
69
  };
66
70
 
67
71
  const STAGES: Record<ReviewStage, { family: 'review-analysis' | 'review-plan' | 'review-code'; action: string }> = {
@@ -127,7 +131,7 @@ function finalizeReviewSummaryUnlocked(
127
131
  }
128
132
  const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
129
133
  if (!resolved.ok) return failed(request, resolved.code, resolved.message, resolved.taskId);
130
- if (resolved.state !== 'active') {
134
+ if (resolved.state !== 'active' && !allowsManualOverride(options.manualOverride, 'review-finalization', 'TASK_STATE_MISMATCH')) {
131
135
  return failed(request, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
132
136
  }
133
137
  const parsedArtifact = parseArtifactName(request.artifact);
@@ -275,7 +279,7 @@ function finalizeReviewSummary(
275
279
  request: ReviewFinalizationRequest,
276
280
  options: ReviewFinalizationOptions = {}
277
281
  ): ReviewFinalizationResult {
278
- if (request.dryRun) return finalizeReviewSummaryUnlocked(request, options);
282
+ if (request.dryRun || options.lockAlreadyHeld) return finalizeReviewSummaryUnlocked(request, options);
279
283
  const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
280
284
  if (!resolved.ok) return finalizeReviewSummaryUnlocked(request, options);
281
285
  try {
@@ -73,7 +73,7 @@ class DocumentMutationError extends Error {
73
73
 
74
74
  function parseTable(
75
75
  content: string,
76
- input: { sectionAliases: readonly string[]; columns: readonly string[] }
76
+ input: { sectionAliases: readonly string[]; columns: readonly string[]; keyColumn?: string }
77
77
  ): ParsedTable | null {
78
78
  validateAliases(input.sectionAliases, 'table section aliases');
79
79
  if (
@@ -83,6 +83,10 @@ function parseTable(
83
83
  ) {
84
84
  throw new DocumentMutationError('MUTATION_INVALID', 'table columns are invalid');
85
85
  }
86
+ const keyColumn = input.keyColumn ?? input.columns[0]!;
87
+ if (!input.columns.includes(keyColumn)) {
88
+ throw new DocumentMutationError('MUTATION_INVALID', 'table key column is invalid');
89
+ }
86
90
  const sections = matchingSections(content, input.sectionAliases);
87
91
  if (sections.length === 0) return null;
88
92
  if (sections.length > 1) {
@@ -125,7 +129,7 @@ function parseTable(
125
129
  column,
126
130
  decodeCell(parsed.cells[columnIndex]!).trim()
127
131
  ]));
128
- const key = values[input.columns[0]!]!;
132
+ const key = values[keyColumn]!;
129
133
  if (seen.has(key)) {
130
134
  throw new DocumentMutationError('TABLE_DUPLICATE_KEY', `duplicate table key '${key}'`);
131
135
  }
@@ -4,6 +4,7 @@ import path from 'node:path';
4
4
  import { createHash, randomUUID } from 'node:crypto';
5
5
 
6
6
  import { getProcessStartTime, processIdentityMatches } from '../server/process-state.ts';
7
+ import type { ProcessIdentity } from '../server/process-state.ts';
7
8
 
8
9
  type TaskExecutionLockErrorCode =
9
10
  | 'ORCHESTRATION_LOCK_BUSY'
@@ -11,9 +12,9 @@ type TaskExecutionLockErrorCode =
11
12
  | 'ORCHESTRATION_LOCK_FAILED';
12
13
 
13
14
  type TaskExecutionLockOwner = Readonly<{
14
- version: 1;
15
+ version: 2;
15
16
  pid: number;
16
- startTime: string;
17
+ startTime: number;
17
18
  token: string;
18
19
  owner: string;
19
20
  canonicalRepoRoot: string;
@@ -28,8 +29,8 @@ type TaskExecutionLockOptions = Readonly<{
28
29
  lockRoot?: string;
29
30
  token?: () => string;
30
31
  now?: () => string;
31
- getStartTime?: (pid: number) => string | null;
32
- identityMatches?: (record: { version: 1; pid: number; startTime: string }) => boolean;
32
+ getStartTime?: (pid: number) => number | null;
33
+ identityMatches?: (identity: ProcessIdentity) => boolean;
33
34
  linkSync?: (existingPath: string, newPath: string) => void;
34
35
  }>;
35
36
 
@@ -84,11 +85,12 @@ function parseOwner(raw: string, key: string): TaskExecutionLockOwner {
84
85
  const value = JSON.parse(raw) as Partial<TaskExecutionLockOwner> | null;
85
86
  if (
86
87
  value !== null
87
- && value.version === 1
88
+ && value.version === 2
88
89
  && Number.isSafeInteger(value.pid)
89
90
  && (value.pid ?? 0) > 0
90
- && typeof value.startTime === 'string'
91
- && value.startTime.length > 0
91
+ && typeof value.startTime === 'number'
92
+ && Number.isSafeInteger(value.startTime)
93
+ && value.startTime >= 0
92
94
  && typeof value.token === 'string'
93
95
  && value.token.length > 0
94
96
  && typeof value.owner === 'string'
@@ -164,7 +166,7 @@ function reclaimStaleLock(
164
166
  function cleanStaleCandidates(
165
167
  lockRoot: string,
166
168
  key: string,
167
- identityMatches: (record: { version: 1; pid: number; startTime: string }) => boolean
169
+ identityMatches: (identity: ProcessIdentity) => boolean
168
170
  ): void {
169
171
  const prefix = `${key}.candidate.`;
170
172
  for (const name of fs.readdirSync(lockRoot)) {
@@ -211,7 +213,7 @@ function withTaskExecutionLock<T>(
211
213
  );
212
214
  }
213
215
  const owner: TaskExecutionLockOwner = {
214
- version: 1,
216
+ version: 2,
215
217
  pid: process.pid,
216
218
  startTime,
217
219
  token,
@@ -23,6 +23,7 @@ import { resolveReviewedHeadRelation } from "../platform/merged-pr-equivalence.t
23
23
  import { resolveLocalReviewedCommitRelation } from "../git/reviewed-commit-equivalence.ts";
24
24
  import { parseTypedTaskFrontmatter } from "./frontmatter.ts";
25
25
  import { parseLedger, summarizeLedgerStage, validateLedgerRows } from "./ledger.ts";
26
+ import type { LedgerRow } from "./ledger.ts";
26
27
  import { equalCounts, parseReviewSummary } from "./review-artifacts.ts";
27
28
  import { loadVerificationConfig } from "./verification-config.ts";
28
29
  import { snapshotReview } from "../git/review-snapshot.ts";
@@ -173,7 +174,7 @@ function checkOrchestrationState({ taskDir }: any): any {
173
174
  if (isFailClosedLegacyPause(run)) {
174
175
  return passResult('orchestration-state', 'Legacy orchestration run is safely paused');
175
176
  }
176
- if (run.schemaVersion !== 2 || !['paused', 'completed'].includes(run.status)) {
177
+ if (![2, 3].includes(run.schemaVersion) || !['paused', 'completed'].includes(run.status)) {
177
178
  return failResult('orchestration-state', `Expected paused or completed run, received '${run.status}'`);
178
179
  }
179
180
  if (run.status === 'paused' && (!run.pause?.code || !run.pause?.message)) {
@@ -290,7 +291,22 @@ function checkOrchestrationEvidence({ taskDir }: any): any {
290
291
  && guards?.commitAuthorizationUnused === true
291
292
  && guards?.completionEvidenceAbsent === true
292
293
  && guards?.commitIntentAbsent === true;
293
- if (!validModelRecovery && !validCapabilityRecovery) {
294
+ const validClaudeCodeRecovery = recovery.code === 'CLIENT_CAPABILITY_ENABLED_NO_MIGRATION'
295
+ && recovery.previousSchemaVersion === 3
296
+ && recovery.previousStatus === 'paused'
297
+ && recovery.previousPause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED'
298
+ && recovery.client === 'claude-code'
299
+ && recovery.resultingStatus === 'running'
300
+ && exactText(recovery.recoveredAt)
301
+ && guards?.stepCount === 0
302
+ && guards?.nextStage === null
303
+ && guards?.baselineEmpty === true
304
+ && guards?.receiptCount === 0
305
+ && guards?.pendingDelegation === false
306
+ && guards?.commitAuthorizationUnused === true
307
+ && guards?.completionEvidenceAbsent === true
308
+ && guards?.commitIntentAbsent === true;
309
+ if (!validModelRecovery && !validCapabilityRecovery && !validClaudeCodeRecovery) {
294
310
  return failResult('orchestration-evidence', 'Run recovery history contains invalid provenance');
295
311
  }
296
312
  }
@@ -321,29 +337,40 @@ function checkOrchestrationEvidence({ taskDir }: any): any {
321
337
  }
322
338
  const activated = ['activated', 'stage-completed', 'sealed', 'consumed'].includes(receipt.status);
323
339
  if (activated) {
324
- if (!exactText(receipt.actualModel)) {
325
- return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has no host-observed actual model`);
340
+ const isClaudeCode = receipt.client === 'claude-code';
341
+ if (!isClaudeCode) {
342
+ if (!exactText(receipt.actualModel)) {
343
+ return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has no host-observed actual model`);
344
+ }
345
+ if (!exactText(receipt.actualReasoningEffort)) {
346
+ return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has no host-observed actual reasoning effort`);
347
+ }
326
348
  }
327
- if (!exactText(receipt.actualReasoningEffort)) {
328
- return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has no host-observed actual reasoning effort`);
349
+ // parentId/childId 校验对所有 client 保持统一(不新增分支);spawnMode 检查按 client 判断
350
+ if (!exactText(receipt.parentId) || !exactText(receipt.childId) || receipt.parentId === receipt.childId) {
351
+ return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has invalid delegation identity`);
329
352
  }
330
- if (!exactText(receipt.parentId) || !exactText(receipt.childId) || receipt.parentId === receipt.childId || receipt.spawnMode !== 'fresh') {
353
+ if (!isClaudeCode && receipt.spawnMode !== 'fresh') {
331
354
  return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has invalid fresh delegation identity`);
332
355
  }
333
- if (receipt.actualModel !== receipt.requestedModel && !exactText(receipt.modelFallbackReason)) {
334
- return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' model fallback is not justified`);
335
- }
336
- if (receipt.actualModel === receipt.requestedModel && receipt.modelFallbackReason !== null) {
337
- return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has an unrelated model fallback reason`);
356
+ if (receipt.actualModel != null && receipt.actualModel !== receipt.requestedModel) {
357
+ if (!isClaudeCode && !exactText(receipt.modelFallbackReason)) {
358
+ return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' model fallback is not justified`);
359
+ }
338
360
  }
339
361
  if (
340
- receipt.actualReasoningEffort !== receipt.requestedReasoningEffort
341
- && !exactText(receipt.reasoningEffortFallbackReason)
362
+ (receipt.actualModel == null || receipt.actualModel === receipt.requestedModel)
363
+ && receipt.modelFallbackReason !== null
342
364
  ) {
343
- return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' reasoning-effort fallback is not justified`);
365
+ return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has an unrelated model fallback reason`);
366
+ }
367
+ if (receipt.actualReasoningEffort != null && receipt.actualReasoningEffort !== receipt.requestedReasoningEffort) {
368
+ if (!isClaudeCode && !exactText(receipt.reasoningEffortFallbackReason)) {
369
+ return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' reasoning-effort fallback is not justified`);
370
+ }
344
371
  }
345
372
  if (
346
- receipt.actualReasoningEffort === receipt.requestedReasoningEffort
373
+ (receipt.actualReasoningEffort == null || receipt.actualReasoningEffort === receipt.requestedReasoningEffort)
347
374
  && receipt.reasoningEffortFallbackReason !== null
348
375
  ) {
349
376
  return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has an unrelated reasoning-effort fallback reason`);
@@ -901,7 +928,7 @@ function checkReviewLedger({ taskDir, config }: any): any {
901
928
  }
902
929
 
903
930
  const stageScope = Array.isArray(config.stage_scope) ? config.stage_scope : null;
904
- const maxRounds = Number(resolveReviewSetting(config, "max_handshake_rounds", DEFAULT_MAX_HANDSHAKE_ROUNDS));
931
+ const maxRounds = Number(resolveReviewSetting(config, "maxHandshakeRounds", DEFAULT_MAX_HANDSHAKE_ROUNDS));
905
932
  const problems = [];
906
933
  let inScopeCount = 0;
907
934
 
@@ -1070,11 +1097,13 @@ function checkPostReviewCommit({ taskDir, config }: any): any {
1070
1097
  return blockedResult("post-review-commit", relation.message, relation.code);
1071
1098
  }
1072
1099
  if (relation.status === "strict") {
1073
- return failResult(
1074
- "post-review-commit",
1075
- "Merged PR evidence did not establish squash merge equivalence",
1076
- "PR_MERGE_IDENTITY_INVALID"
1077
- );
1100
+ return resolvePostReviewIdentityFailure(task.content, {
1101
+ code: "PR_MERGE_IDENTITY_INVALID",
1102
+ message: "Merged PR evidence did not establish squash merge equivalence"
1103
+ });
1104
+ }
1105
+ if (relation.code === "PR_MERGE_IDENTITY_INVALID") {
1106
+ return resolvePostReviewIdentityFailure(task.content, relation);
1078
1107
  }
1079
1108
  return failResult("post-review-commit", relation.message, relation.code);
1080
1109
  }
@@ -1144,11 +1173,11 @@ function checkPostReviewCommit({ taskDir, config }: any): any {
1144
1173
  }
1145
1174
 
1146
1175
  const exemption = resolvePostReviewExemption(task.content);
1147
- if (!exemption.ok) return failResult("post-review-commit", exemption.message);
1148
- if (exemption.exempt) {
1176
+ if (!exemption.ok) return failResult("post-review-commit", exemption.message, exemption.code);
1177
+ if (exemption.exemptions.length > 0) {
1149
1178
  return passResult(
1150
1179
  "post-review-commit",
1151
- `${commits.length} post-review commit(s) covered by a human-decided exemption`
1180
+ `${commits.length} post-review commit(s) covered by a human-decided exemption: ${formatPostReviewExemptions(exemption.exemptions)}`
1152
1181
  );
1153
1182
  }
1154
1183
 
@@ -1158,18 +1187,50 @@ function checkPostReviewCommit({ taskDir, config }: any): any {
1158
1187
  );
1159
1188
  }
1160
1189
 
1190
+ function resolvePostReviewIdentityFailure(
1191
+ content: string,
1192
+ failure: { code: string; message: string }
1193
+ ): any {
1194
+ const exemption = resolvePostReviewExemption(content);
1195
+ if (!exemption.ok) {
1196
+ return failResult("post-review-commit", exemption.message, exemption.code);
1197
+ }
1198
+ if (exemption.exemptions.length === 0) {
1199
+ return failResult("post-review-commit", failure.message, failure.code);
1200
+ }
1201
+ return passResult(
1202
+ "post-review-commit",
1203
+ `Human-decided post-review exemption overrode ${failure.code}: ${failure.message}; ${formatPostReviewExemptions(exemption.exemptions)}`
1204
+ );
1205
+ }
1206
+
1207
+ function formatPostReviewExemptions(exemptions: readonly LedgerRow[]): string {
1208
+ return exemptions.map((row) => `${row.id}: ${row.evidence}`).join("; ");
1209
+ }
1210
+
1161
1211
  function resolvePostReviewExemption(content: string):
1162
- | { ok: true; exempt: boolean }
1163
- | { ok: false; message: string } {
1212
+ | { ok: true; exemptions: readonly LedgerRow[] }
1213
+ | { ok: false; code: "POST_REVIEW_EXEMPTION_INVALID"; message: string } {
1164
1214
  try {
1215
+ const rows = parseLedger(content);
1216
+ const invalid = validateLedgerRows(rows);
1217
+ if (invalid) {
1218
+ return {
1219
+ ok: false,
1220
+ code: "POST_REVIEW_EXEMPTION_INVALID",
1221
+ message: `${invalid.code}: ${invalid.message}`
1222
+ };
1223
+ }
1165
1224
  return {
1166
1225
  ok: true,
1167
- exempt: parseLedger(content).some(
1168
- (row) => row.stage === POST_REVIEW_COMMIT_STAGE && row.status === "human-decided"
1169
- )
1226
+ exemptions: rows.filter((row) => row.stage === POST_REVIEW_COMMIT_STAGE)
1227
+ };
1228
+ } catch (error) {
1229
+ return {
1230
+ ok: false,
1231
+ code: "POST_REVIEW_EXEMPTION_INVALID",
1232
+ message: `Invalid disagreement ledger: ${error instanceof Error ? error.message : String(error)}`
1170
1233
  };
1171
- } catch {
1172
- return { ok: false, message: "Invalid disagreement ledger" };
1173
1234
  }
1174
1235
  }
1175
1236
 
@@ -7,6 +7,8 @@ import { parseArtifactName } from './artifact-lifecycle.ts';
7
7
  import type { ArtifactFamily } from './artifact-lifecycle.ts';
8
8
  import { resolveTaskRef } from './resolve-ref.ts';
9
9
  import type { TaskWorkspaceState } from './resolve-ref.ts';
10
+ import { allowsManualOverride } from './guard-override.ts';
11
+ import type { ManualOverrideCapability } from './guard-override.ts';
10
12
 
11
13
  type VerificationEvent =
12
14
  | 'analyze.awaiting-input' | 'analyze.completed'
@@ -48,6 +50,7 @@ type TaskVerificationResult = {
48
50
  type VerificationOptions = {
49
51
  repoRoot?: string;
50
52
  engine?: typeof verifyInProcess;
53
+ manualOverride?: ManualOverrideCapability;
51
54
  };
52
55
 
53
56
  const gate = (skill: string, expectedState: VerificationSpec['expectedState'], artifactFamily?: ArtifactFamily): VerificationSpec => ({
@@ -93,7 +96,7 @@ function verifyTaskEvent(request: { taskRef: string; event: string; artifact?: s
93
96
  const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
94
97
  if (!resolved.ok) return failure(request, resolved.code, resolved.message, { taskId: resolved.taskId, skill: spec.skill, mode: spec.mode });
95
98
  const identity = { taskId: resolved.taskId, taskDir: resolved.taskDir, taskState: resolved.state, skill: spec.skill, mode: spec.mode } as const;
96
- if (resolved.state !== spec.expectedState) {
99
+ if (resolved.state !== spec.expectedState && !allowsManualOverride(options.manualOverride, 'task-verify', 'VERIFY_TASK_STATE_MISMATCH')) {
97
100
  return failure(request, 'VERIFY_TASK_STATE_MISMATCH', `event '${request.event}' requires workspace '${spec.expectedState}', received '${resolved.state}'`, identity);
98
101
  }
99
102
  if (spec.artifactFamily) {
@@ -111,11 +114,22 @@ function verifyTaskEvent(request: { taskRef: string; event: string; artifact?: s
111
114
  if (spec.mode === 'gate') {
112
115
  const payload = engine({ mode: 'gate', skillName: spec.skill, taskDir: resolved.taskDir, artifactFile: request.artifact, checks: [], repositoryRoot: resolved.repoRoot }) as Record<string, unknown>;
113
116
  const status = payload.gate as 'pass' | 'fail' | 'blocked';
117
+ if (status !== 'pass' && allowsManualOverride(options.manualOverride, 'verification-engine', status === 'blocked' ? 'CHECK_BLOCKED' : 'CHECK_FAILED')) {
118
+ invocations.push({ status: 'pass', exitCode: 0, payload: { ...payload, gate: 'pass', humanOverride: 'human-approved', originalGate: status } });
119
+ return {
120
+ status: 'pass', changed: false, event: request.event, requestRef: request.taskRef,
121
+ ...identity, artifact: request.artifact ?? null, invocations, error: null
122
+ };
123
+ }
114
124
  invocations.push({ status, exitCode: ({ pass: 0, fail: 1, blocked: 2 } as const)[status], payload });
115
125
  } else {
116
126
  for (const check of spec.checks ?? []) {
117
127
  const payload = engine({ mode: 'checks', skillName: spec.skill, taskDir: resolved.taskDir, artifactFile: request.artifact, checks: [check], repositoryRoot: resolved.repoRoot }) as Record<string, unknown>;
118
128
  const status = payload.status as 'pass' | 'fail' | 'blocked';
129
+ if (status !== 'pass' && allowsManualOverride(options.manualOverride, 'verification-engine', status === 'blocked' ? 'CHECK_BLOCKED' : 'CHECK_FAILED')) {
130
+ invocations.push({ status: 'pass', exitCode: 0, payload: { ...payload, status: 'pass', humanOverride: 'human-approved', originalStatus: status } });
131
+ break;
132
+ }
119
133
  invocations.push({ status, exitCode: ({ pass: 0, fail: 1, blocked: 2 } as const)[status], payload });
120
134
  if (status !== 'pass') break;
121
135
  }
@@ -11,6 +11,7 @@ import {
11
11
  import type { WorkflowWarning } from './workflow-warnings.ts';
12
12
  import { captureTaskWriteMetadata, writeTask } from './write.ts';
13
13
  import type { TaskMutation, TaskOperationSummary, TaskWriteOptions } from './write.ts';
14
+ import { allowsManualOverride } from './guard-override.ts';
14
15
 
15
16
  type WarningSeverity = 'IMPORTANT' | 'ACTION_REQUIRED';
16
17
  type WarningStatus = 'open' | 'resolved' | 'ignored';
@@ -79,7 +80,8 @@ function rowMutation(row: WorkflowWarning): TaskMutation {
79
80
  function applyWorkflowWarningIntent(intent: WorkflowWarningIntent, options: TaskWriteOptions = {}): WorkflowWarningIntentResult {
80
81
  const resolved = resolveTaskRef(intent.taskRef, { repoRoot: options.repoRoot });
81
82
  if (!resolved.ok) return failed(intent, resolved.code, resolved.message, resolved.taskId);
82
- if (resolved.state !== 'active') return failed(intent, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
83
+ const stateOverride = allowsManualOverride(options.manualOverride, 'workflow-warning', 'TASK_STATE_MISMATCH');
84
+ if (resolved.state !== 'active' && !stateOverride) return failed(intent, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
83
85
  let content: string;
84
86
  let rows: WorkflowWarning[];
85
87
  try {
@@ -125,7 +127,7 @@ function applyWorkflowWarningIntent(intent: WorkflowWarningIntent, options: Task
125
127
  }
126
128
 
127
129
  const writeResult = writeTask({
128
- taskRef: intent.taskRef, expectedState: 'active', mutations: [...sectionMutation(content), rowMutation(after)], dryRun: intent.dryRun
130
+ taskRef: intent.taskRef, expectedState: stateOverride ? resolved.state : 'active', mutations: [...sectionMutation(content), rowMutation(after)], dryRun: intent.dryRun
129
131
  }, {
130
132
  ...options,
131
133
  taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state },
package/lib/task/write.ts CHANGED
@@ -12,6 +12,8 @@ import type {
12
12
  ResolveTaskRefErrorCode,
13
13
  TaskWorkspaceState
14
14
  } from './resolve-ref.ts';
15
+ import { allowsManualOverride } from './guard-override.ts';
16
+ import type { ManualOverrideCapability } from './guard-override.ts';
15
17
  import { mutateTableRow, upsertSection } from './sections.ts';
16
18
  import type {
17
19
  TableRowDeleteMutation,
@@ -156,6 +158,7 @@ type TaskWriteOptions = {
156
158
  metadataProvider?: () => TaskWriteMetadata;
157
159
  randomSuffix?: () => string;
158
160
  fileSystem?: Partial<TaskFileSystem>;
161
+ manualOverride?: ManualOverrideCapability;
159
162
  };
160
163
 
161
164
  const DEFAULT_FILE_SYSTEM: TaskFileSystem = {
@@ -241,7 +244,7 @@ function writeTask(request: TaskWriteRequest, options: TaskWriteOptions = {}): T
241
244
  taskMdPath: resolved.taskMdPath,
242
245
  actualState: resolved.state
243
246
  };
244
- if (resolved.state !== request.expectedState) {
247
+ if (resolved.state !== request.expectedState && !allowsManualOverride(options.manualOverride, 'task.write', 'TASK_STATE_MISMATCH')) {
245
248
  return failure(
246
249
  request,
247
250
  identity,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fitlab-ai/agent-infra",
3
- "version": "0.9.6",
3
+ "version": "0.9.8",
4
4
  "description": "Bootstrap tool for AI multi-tool collaboration infrastructure — works with Claude Code, Codex, Antigravity CLI, and OpenCode",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -47,6 +47,13 @@ This dual-config approach ensures every AI tool receives appropriate project con
47
47
  settings.json # Claude settings
48
48
  ```
49
49
 
50
+ The whole `.agents/workspace/` tree is runtime-only and git-ignored. `active/`
51
+ contains current tasks, `blocked/` contains paused tasks, `completed/` contains
52
+ finished tasks, and `archive/` stores archived history. Runtime logs and process
53
+ evidence may also be created below this root. Do not place content here that must
54
+ be shared through Git; collaboration rules, templates, and workflows belong in
55
+ the version-controlled `.agents/` tree.
56
+
50
57
  ## Collaboration Model
51
58
 
52
59
  The multi-AI collaboration follows a structured workflow:
@@ -47,6 +47,11 @@
47
47
  settings.json # Claude 设置
48
48
  ```
49
49
 
50
+ 整个 `.agents/workspace/` 目录树都只保存运行时数据,并被 git ignore。`active/`
51
+ 保存当前任务,`blocked/` 保存暂停任务,`completed/` 保存已完成任务,`archive/`
52
+ 保存归档历史;运行时日志和过程证据也可能出现在该根目录下。不要在这里存放必须
53
+ 通过 Git 共享的内容;协作规则、模板和工作流应放在受版本控制的 `.agents/` 目录中。
54
+
50
55
  ## 协作模型
51
56
 
52
57
  多 AI 协作遵循结构化工作流:
@@ -36,6 +36,28 @@ function internalCliInvocation(args) {
36
36
  return { command: 'agent-infra-internal', commandArgs: args, shell: process.platform === 'win32' };
37
37
  }
38
38
 
39
+ function findCapabilityToken(value, depth = 0) {
40
+ if (depth > 5 || value == null) return '';
41
+ if (typeof value === 'string') {
42
+ const match = /agent-infra-codex-capability:([A-Za-z0-9_-]{20,})/.exec(value);
43
+ return match?.[1] || '';
44
+ }
45
+ if (Array.isArray(value)) {
46
+ for (const entry of value) {
47
+ const found = findCapabilityToken(entry, depth + 1);
48
+ if (found) return found;
49
+ }
50
+ return '';
51
+ }
52
+ if (typeof value === 'object') {
53
+ for (const entry of Object.values(value)) {
54
+ const found = findCapabilityToken(entry, depth + 1);
55
+ if (found) return found;
56
+ }
57
+ }
58
+ return '';
59
+ }
60
+
39
61
  let input = '';
40
62
  process.stdin.setEncoding('utf8');
41
63
  process.stdin.on('data', (chunk) => {
@@ -75,10 +97,11 @@ process.stdin.on('end', () => {
75
97
  process.exit(1);
76
98
  }
77
99
  const managedAgent = /^agent-infra-lifecycle-(executor|reviewer)$/.test(String(nativeAgent || ''));
100
+ const capabilityToken = hook === 'post-tool' ? findCapabilityToken(toolResponse) : '';
78
101
  const parentReconcile = hook === 'post-tool'
79
102
  && toolName === 'collaborationwait_agent'
80
103
  && toolResponse.timed_out !== true;
81
- if (!managedAgent && !parentReconcile) process.exit(0);
104
+ if (!managedAgent && !parentReconcile && !capabilityToken) process.exit(0);
82
105
  const hookDefinitionHash = createHash('sha256').update(readFileSync(codexHooks)).digest('hex');
83
106
  const normalized = {
84
107
  sessionId: String(event.session_id || ''),
@@ -93,6 +116,7 @@ process.stdin.on('end', () => {
93
116
  taskName: String(toolInput.task_name || ''),
94
117
  transcriptPath: String(event.transcript_path || '')
95
118
  };
119
+ if (capabilityToken) normalized.capabilityToken = capabilityToken;
96
120
  const args = ['codex-lifecycle', 'hook-event', '--event', hook, '--bridge', 'true'];
97
121
  try {
98
122
  const { command, commandArgs, shell } = internalCliInvocation(args);
@@ -123,11 +147,11 @@ process.stdin.on('end', () => {
123
147
  args.push(
124
148
  '--native-agent', String(nativeAgent),
125
149
  '--child-id', String(event.child_id || event.agent_id || event.thread_id || ''),
126
- '--parent-id', String(event.parent_id || event.parent_session_id || event.session_id || ''),
127
- '--spawn-mode', String(event.spawn_mode || (client === 'claude-code' ? 'fresh' : ''))
150
+ '--parent-id', String(event.parent_id || event.parent_session_id || event.session_id || '')
128
151
  );
152
+ if (event.spawn_mode) args.push('--spawn-mode', String(event.spawn_mode));
129
153
  if (event.model) args.push('--actual-model', String(event.model));
130
- const actualEffort = event.actual_reasoning_effort || event.reasoning_effort || event.model_reasoning_effort;
154
+ const actualEffort = event.effort?.level || event.actual_reasoning_effort || event.reasoning_effort || event.model_reasoning_effort;
131
155
  if (actualEffort) args.push('--actual-reasoning-effort', String(actualEffort));
132
156
  if (event.model_fallback_reason) args.push('--model-fallback-reason', String(event.model_fallback_reason));
133
157
  if (event.reasoning_effort_fallback_reason) {
@@ -138,6 +162,11 @@ process.stdin.on('end', () => {
138
162
  '--native-agent', String(nativeAgent),
139
163
  '--child-id', String(event.child_id || event.agent_id || event.thread_id || '')
140
164
  );
165
+ if (client === 'claude-code') {
166
+ if (event.model) args.push('--actual-model', String(event.model));
167
+ const actualEffort = event.effort?.level || event.actual_reasoning_effort || event.reasoning_effort;
168
+ if (actualEffort) args.push('--actual-reasoning-effort', String(actualEffort));
169
+ }
141
170
  }
142
171
  try {
143
172
  const { command, commandArgs, shell } = internalCliInvocation(args);
@@ -5,19 +5,26 @@
5
5
  - The orchestrator only routes and delegates. Stage skills remain the single source of truth for behavior and artifacts.
6
6
  - Every stage and rework round uses a fresh executor. Every review uses a fresh reviewer; follow-up reuse is forbidden.
7
7
  - A reviewer may only write its review artifact and core-generated task metadata. Business-code, HEAD, or index changes invalidate the receipt.
8
- - An active run has one pending delegation. Missing, mismatched, forked, replayed, hook-less, or drifted evidence pauses the run.
8
+ - An active run has one pending delegation. The child must pass the activation barrier before any stage side effect. Missing, late, mismatched, forked, replayed, hook-less, or drifted evidence pauses the run (on the claude-code path, missing or mismatched model/effort evidence, and fork/spawn-mode evidence this host structurally does not provide, follow the recording rules in `.agents/skills/run-task/reference/host-validation.md` instead and are exempt from this; missing or mismatched `parentId`/`childId` still pauses).
9
9
  - The first release ends after one existing safely gated `commit`; it does not create a PR, monitor checks, or complete the task.
10
10
 
11
11
  ## Recovery
12
12
 
13
- `orchestration.json` is the detailed state source. A v2 run persists complete policy, source, and append-only recovery history. Only a v1 run with no pending delegation and zero receipts may be upgraded in place after supplying a complete policy; any historical receipt stays paused because effort is unverifiable. A historical Codex `ORCHESTRATION_CLIENT_UNSUPPORTED` v2 pause appends `CLIENT_CAPABILITY_ENABLED` and resumes only when step count, next stage, baseline, receipts, pending delegation, commit authorization, completion evidence, and commit intent all prove that execution never advanced; unknown or non-empty evidence preserves the pause. Migration is forward-only: old binaries must not advance active v2 runs.
13
+ `orchestration.json` is the detailed state source. A v3 run persists complete policy, append-only recovery, build/contract/hook-source/controller provenance, and activation monotonic timestamps. Historical v2 state is read-compatible, but new Codex delegations require v3. An expired prepared orphan may be explicitly recovered only with the exact task fingerprint, no commit intent or consumed authorization, and no matching unconsumed active lifecycle evidence. Unknown schemas and incomplete active v3 state fail closed.
14
+
15
+ ## Codex Host and Capability
16
+
17
+ - Direct-host accepts only trusted project or managed lifecycle hooks. A task-bound sandbox requires a controlled nested controller and isolated `CODEX_HOME`; only user hooks bound to that controller context are accepted. Ordinary user/plugin hooks are not evidence.
18
+ - The controller starts a nested loop only after control generation, task binding, Codex version, closed bundle, build/contract manifest, profiles, and hook discovery pass. Both bypass flags are restricted to this task-bound launch path.
19
+ - Every prepare arms a one-use capability attested by the current loop's real PostToolUse. Atomic consumption binds task/session/build/controller and retains only a redacted tombstone.
14
20
 
15
21
  ## Model Policy
16
22
 
17
23
  - A new run persists model and reasoning effort for both roles. Explicit policy is atomic across all four role fields; only a fully absent explicit policy may fall back to the current client's `agentClients[].orchestration`.
18
24
  - Route resolves requested model/effort by role, and prepare matches both before snapshotting. Native spawn must not inherit session defaults.
19
- - Native start records host-observed actual model/effort. Each mismatch needs its own fallback reason, and requested values must never be fabricated as actual evidence.
25
+ - Native start records host-observed actual model/effort. Each mismatch needs its own fallback reason (the claude-code path may leave this blank and follow the recording rules instead), and requested values must never be fabricated as actual evidence.
20
26
  - Model selection is labeled as a complete catalog, partial catalog, or interactive-only guidance; a local override enum must not be presented as complete.
27
+ - claude-code's requested reasoning effort does not yet support per-role dispatch (concurrent tasks would race on a shared file); `delegationEvidence.actualReasoningEffort` is declared `spawn-ack`, meaning it is only recorded when honestly observed in a native spawn lifecycle event (Start/Stop) — it is not a promise of per-role dispatch and does not gate activation.
21
28
 
22
29
  ## Stable Pause Conditions
23
30
 
@@ -5,19 +5,26 @@
5
5
  - 总控只路由和委派;阶段技能仍是业务规则与产物格式的单一事实源。
6
6
  - 每个阶段和每轮返工都创建 fresh executor;每轮审查创建 fresh reviewer,禁止 follow-up 复用。
7
7
  - reviewer 只能写当前审查产物和核心生成的任务元数据。业务代码、HEAD 或暂存区变化会使 receipt 失效。
8
- - active run 中只有一个 pending delegation。缺失、错配、fork、重放、hook 不可用或工作区漂移一律暂停。
8
+ - active run 中只有一个 pending delegation。child 必须在任何阶段副作用前通过 activation barrier;缺失、超时、错配、fork、重放、hook 不可用或工作区漂移一律暂停(claude-code 路径下 model/effort 证据的缺失或错配、以及该宿主结构上不提供的 fork/spawn-mode 证据,按 `.agents/skills/run-task/reference/host-validation.md` 的记录规则处理,不在此列;`parentId`/`childId` 的缺失或错配仍然暂停)。
9
9
  - 首版成功终点是一次通过既有安全门禁的 `commit`;不创建 PR、不监控 checks、不执行 `complete-task`。
10
10
 
11
11
  ## 恢复语义
12
12
 
13
- `orchestration.json` 是详细状态源。v2 run 保存完整策略、来源与 append-only 恢复历史。只有无 pending delegation 且零历史 receipt 的 v1 run 可在补齐完整策略后原地升级;任一历史 receipt 因缺 effort 证据保持暂停。Codex 的历史 `ORCHESTRATION_CLIENT_UNSUPPORTED` v2 pause 仅在 stepCount、nextStage、baseline、receipts、pending delegation、commit authorization、completion evidence 与 commit intent 全部证明从未推进时追加 `CLIENT_CAPABILITY_ENABLED` 并恢复;任一未知或非空证据保持原暂停。迁移是 forward-only,旧二进制不得推进 v2 active run。
13
+ `orchestration.json` 是详细状态源。v3 run 保存完整策略、append-only 恢复历史、build/contract/hook source/controller provenance 与 activation 单调时钟。只有无 pending delegation 且零历史 receipt 的 v1 run 可在补齐完整策略后升级;历史 v2 状态仅兼容读取,新的 Codex delegation 必须写 v3。prepared orphan 只允许在 deadline 已过、task fingerprint 精确不变、无 commit intent/authorization 消耗且无匹配的未消费 active lifecycle evidence 时显式恢复。迁移是 forward-only,未知 schema 或缺 provenance 的 active v3 一律失败关闭。
14
+
15
+ ## Codex 宿主与 capability
16
+
17
+ - direct-host 只接受 trusted project 或 managed lifecycle hooks。task-bound sandbox 必须由受控 nested controller 建立隔离 `CODEX_HOME`,仅接受绑定 controller context 的 user hooks;普通 user/plugin hook 不得成为证据源。
18
+ - controller 只有在 control generation、task binding、Codex 版本、closed bundle、build/contract manifest、profile 与 hook discovery 全部通过后才能启动 nested loop。两个 bypass 参数只允许出现在该 task-bound 启动路径。
19
+ - 每次 prepare 前必须 arm 一次性 capability,并由同一当前 loop 的真实 PostToolUse attestation;token 原子消费后保留去敏 tombstone,不能重放或跨 task/session/build/controller 使用。
14
20
 
15
21
  ## 模型策略
16
22
 
17
23
  - 新 run 必须固化 executor/reviewer 各自的 model 与 reasoning effort;显式策略必须四字段原子完整,完全没有显式字段时才读取当前 client 的 `agentClients[].orchestration`。重入不得静默改写策略。
18
24
  - route 按 role 返回 requested model/effort,prepare 必须在工作区快照前精确匹配两者;原生 spawn 不能继承会话默认值。
19
- - 原生 start 必须记录宿主观察到的 actual model/effort。任一字段与 requested 不同时必须记录独立 fallback reason;requested 值不得补造 actual 证据。
25
+ - 原生 start 必须记录宿主观察到的 actual model/effort。任一字段与 requested 不同时必须记录独立 fallback reason(claude-code 路径下允许留空,按记录规则处理);requested 值不得补造 actual 证据。
20
26
  - 模型选择能力必须标记 complete catalog、partial catalog 或 interactive-only guidance;局部 override 枚举不得冒充完整目录。
27
+ - claude-code 的 requested reasoning effort 暂不支持按角色下发(跨任务共享文件存在写入竞态);`delegationEvidence.actualReasoningEffort` 声明为 `spawn-ack`,语义为"仅能在宿主原生 spawn 生命周期事件(Start/Stop)里如实观察到时记录,不构成按角色下发的承诺,也不构成放行门禁"。
21
28
 
22
29
  ## 稳定暂停条件
23
30
 
@@ -1,6 +1,6 @@
1
1
  # GitHub PR Readiness Intents
2
2
 
3
- The GitHub adapter requires `gh >= 2.16.0`. Platform context checks this dependency before any GitHub API, PR, or checks operation. Projects that do not select GitHub neither probe nor depend on `gh`.
3
+ The GitHub adapter requires `gh >= 2.72.0`. Platform context checks this dependency before any GitHub API, PR, or checks operation. Projects that do not select GitHub neither probe nor depend on `gh`.
4
4
 
5
5
  ```bash
6
6
  agent-infra-internal platform-checks inspect {task-id}
@@ -2,7 +2,7 @@
2
2
 
3
3
  PR 全部 checks 的获取、轮询 deadline、失败 run/job 定位和日志获取统一由 typed internal intent 执行。SKILL/模型只负责失败分类、根因分析、最小修复和授权后的提交推送。
4
4
 
5
- GitHub adapter 要求 `gh >= 2.16.0`。平台上下文会在任何 GitHub API、PR 或 checks 操作前检查该依赖;未选择 GitHub 平台时不探测也不依赖 `gh`。
5
+ GitHub adapter 要求 `gh >= 2.72.0`。平台上下文会在任何 GitHub API、PR 或 checks 操作前检查该依赖;未选择 GitHub 平台时不探测也不依赖 `gh`。
6
6
 
7
7
  ## 快照与监控
8
8