pi-subagents 0.54.0 → 0.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/agents/reviewer.md +12 -2
  3. package/docs/agents.md +4 -4
  4. package/docs/configuration.md +4 -4
  5. package/docs/extension-api.md +14 -4
  6. package/docs/models.md +30 -3
  7. package/docs/observability.md +14 -3
  8. package/docs/tool-reference.md +21 -6
  9. package/docs/workflows.md +7 -1
  10. package/package.json +1 -1
  11. package/prompts/council.md +9 -0
  12. package/prompts/parallel-review.md +5 -1
  13. package/prompts/review-loop.md +9 -5
  14. package/skills/council-mode/SKILL.md +33 -10
  15. package/skills/pi-subagents/references/constraints-and-recipes.md +16 -1
  16. package/skills/pi-subagents/references/execution-controls.md +11 -4
  17. package/skills/pi-subagents/references/multi-lane-orchestration.md +3 -3
  18. package/skills/pi-subagents/references/prompting-and-roles.md +8 -8
  19. package/src/agents/agent-management.ts +30 -9
  20. package/src/agents/agent-serializer.ts +4 -2
  21. package/src/agents/agents.ts +168 -41
  22. package/src/api/external-job-provider.ts +10 -1
  23. package/src/api/preflight.ts +45 -17
  24. package/src/api/project-panes.ts +2 -0
  25. package/src/extension/index.ts +20 -1
  26. package/src/extension/public-execution.ts +12 -9
  27. package/src/extension/rpc.ts +77 -3
  28. package/src/extension/schemas.ts +7 -1
  29. package/src/extension/tool-description.ts +9 -6
  30. package/src/inspectors/herdr/focus.ts +55 -0
  31. package/src/inspectors/herdr/project-panes.ts +228 -44
  32. package/src/integrations/herdr-status.ts +26 -4
  33. package/src/runs/background/async-execution.ts +137 -13
  34. package/src/runs/background/async-job-tracker.ts +33 -14
  35. package/src/runs/background/async-resume.ts +23 -2
  36. package/src/runs/background/async-status.ts +10 -0
  37. package/src/runs/background/control-channel.ts +98 -10
  38. package/src/runs/background/notify.ts +62 -1
  39. package/src/runs/background/run-status.ts +15 -1
  40. package/src/runs/background/subagent-runner.ts +308 -47
  41. package/src/runs/foreground/async-stop-action.ts +23 -2
  42. package/src/runs/foreground/execution.ts +115 -12
  43. package/src/runs/foreground/foreground-history.ts +3 -2
  44. package/src/runs/foreground/subagent-executor.ts +404 -45
  45. package/src/runs/foreground/workflow-detach-reconcile.ts +18 -6
  46. package/src/runs/shared/acceptance.ts +11 -5
  47. package/src/runs/shared/agent-contract.ts +1 -1
  48. package/src/runs/shared/child-identity.ts +36 -0
  49. package/src/runs/shared/completion-guard.ts +52 -2
  50. package/src/runs/shared/dynamic-fanout.ts +1 -1
  51. package/src/runs/shared/extension-bindings.ts +78 -0
  52. package/src/runs/shared/external-cli-runner.ts +2 -0
  53. package/src/runs/shared/external-job-bridge.ts +53 -37
  54. package/src/runs/shared/external-job-runner.ts +126 -23
  55. package/src/runs/shared/fast-mode-extension.ts +10 -0
  56. package/src/runs/shared/model-exclusions.ts +2 -1
  57. package/src/runs/shared/model-fallback.ts +12 -7
  58. package/src/runs/shared/mutation-evidence.ts +145 -0
  59. package/src/runs/shared/orca-progress-tabs.ts +71 -9
  60. package/src/runs/shared/parallel-utils.ts +15 -0
  61. package/src/runs/shared/pi-args.ts +56 -12
  62. package/src/runs/shared/structured-output.ts +18 -4
  63. package/src/runs/shared/subagent-prompt-runtime.ts +12 -6
  64. package/src/runs/shared/tool-availability.ts +1 -3
  65. package/src/shared/launch-contract.ts +12 -5
  66. package/src/shared/settings.ts +6 -1
  67. package/src/shared/thinking-ceiling.ts +52 -0
  68. package/src/shared/types.ts +107 -2
  69. package/src/tui/fleet-status.ts +66 -13
  70. package/src/tui/render.ts +5 -4
  71. package/src/workflows/scripted-workflow.ts +156 -12
@@ -3,6 +3,7 @@ import type { AgentToolResult } from "@earendil-works/pi-agent-core";
3
3
  import type { Details, SubagentState } from "../../shared/types.ts";
4
4
  import { deliverStopRequest } from "../background/control-channel.ts";
5
5
  import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
6
+ import { isStoppableAsyncStatusStep, resolveAsyncStatusChild, type ResolvedAsyncStatusChild } from "../shared/child-identity.ts";
6
7
 
7
8
  function getAsyncStopTarget(
8
9
  state: SubagentState,
@@ -25,6 +26,7 @@ export function stopAsyncRun(
25
26
  runId: string | undefined,
26
27
  kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean,
27
28
  location?: { asyncDir: string | null; resolvedId?: string },
29
+ childId?: string,
28
30
  ): AgentToolResult<Details> | null {
29
31
  const target = getAsyncStopTarget(state, runId, location);
30
32
  if (!target) return null;
@@ -43,15 +45,34 @@ export function stopAsyncRun(
43
45
  details: { mode: "management", results: [] },
44
46
  };
45
47
  }
48
+ let child: ResolvedAsyncStatusChild | undefined;
49
+ if (childId !== undefined) {
50
+ const resolution = resolveAsyncStatusChild(status, childId);
51
+ if (!resolution.ok) {
52
+ return {
53
+ content: [{ type: "text", text: resolution.message }],
54
+ isError: true,
55
+ details: { mode: "management", results: [] },
56
+ };
57
+ }
58
+ child = resolution.child;
59
+ if (!isStoppableAsyncStatusStep(child.step)) {
60
+ return {
61
+ content: [{ type: "text", text: `Child '${childId}' in async run '${status.runId}' is ${child.step.status}; stop only supports pending or running children.` }],
62
+ isError: true,
63
+ details: { mode: "management", results: [] },
64
+ };
65
+ }
66
+ }
46
67
  try {
47
- deliverStopRequest({ asyncDir: target.asyncDir, pid: typeof status.pid === "number" ? status.pid : undefined, kill, source: "stop-action" });
68
+ deliverStopRequest({ asyncDir: target.asyncDir, pid: typeof status.pid === "number" ? status.pid : undefined, kill, source: "stop-action", targetIndex: child?.index, childId: child?.id ?? childId });
48
69
  const tracked = state.asyncJobs.get(target.asyncId);
49
70
  if (tracked) {
50
71
  tracked.activityState = undefined;
51
72
  tracked.updatedAt = Date.now();
52
73
  }
53
74
  return {
54
- content: [{ type: "text", text: `Stop requested for async run ${target.asyncId}.` }],
75
+ content: [{ type: "text", text: child ? `Stop requested for child ${child.id} in async run ${target.asyncId}.` : `Stop requested for async run ${target.asyncId}.` }],
55
76
  details: { mode: "management", results: [] },
56
77
  };
57
78
  } catch (error) {
@@ -55,7 +55,7 @@ import {
55
55
  import { buildSkillInjection, resolveSkillsWithFallback } from "../../agents/skills.ts";
56
56
  import { buildAgentMemoryInjection } from "../../agents/agent-memory.ts";
57
57
  import { effectiveToolTimeoutMs, formatToolTimeoutMessage, resolveToolTimeoutMs, toolTimeoutCallKey, toolTimeoutFromEnv } from "../shared/tool-timeout.ts";
58
- import { evaluateCompletionMutationGuard } from "../shared/completion-guard.ts";
58
+ import { evaluateCompletionMutationGuard, validateImplementationToolContract } from "../shared/completion-guard.ts";
59
59
  import { arbitrateCompletionGuardRescue } from "../shared/llm-intent-arbiter.ts";
60
60
  import { getPiSpawnCommand } from "../shared/pi-spawn.ts";
61
61
  import { createJsonlWriter } from "../../shared/jsonl-writer.ts";
@@ -66,12 +66,15 @@ import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, projectLaunchResolved
66
66
  import { readRuntimeAcknowledgedExtensions } from "../shared/runtime-acknowledged-extensions.ts";
67
67
  import { assertAgentAllowedByCapabilityCeiling, decodeSubagentCapabilityCeiling, intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV } from "../shared/capability-ceiling.ts";
68
68
  import { resolveEffectiveThinking } from "../../shared/model-info.ts";
69
- import { MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput } from "../shared/structured-output.ts";
69
+ import { assertThinkingWithinCeiling, decodeThinkingCeiling, intersectThinkingCeilings, SUBAGENT_THINKING_CEILING_ENV } from "../../shared/thinking-ceiling.ts";
70
+ import { MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput, readStructuredOutputAcceptanceReport } from "../shared/structured-output.ts";
70
71
  import { formatProcessSignalError, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
71
72
  import { readChildToolDiagnosticError } from "../shared/tool-availability.ts";
73
+ import { buildTimeoutRecoverySummary, collectTrackedMutationEvidence, snapshotTrackedMutations } from "../shared/mutation-evidence.ts";
72
74
  import { captureSingleOutputSnapshot, extractChildWrittenOutput, finalizeSingleOutput, formatSavedOutputReference, injectOutputPathSystemPrompt, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
73
75
  import {
74
76
  buildModelCandidates,
77
+ formatSubagentModelVerificationError,
75
78
  formatModelAttemptNote,
76
79
  isContextOverflow,
77
80
  isRetryableModelFailure,
@@ -310,10 +313,13 @@ async function runSingleAttempt(
310
313
  taskDelivery?: SubagentTaskDelivery;
311
314
  orcaProgressTab?: OrcaProgressTab;
312
315
  launchWarnings: { emitted: boolean };
316
+ verifyModel: boolean;
313
317
  },
314
318
  ): Promise<SingleResult> {
315
319
  const effectiveThinking = options.thinkingOverride ?? agent.thinking;
316
320
  const modelArg = applyThinkingSuffix(model, effectiveThinking, options.thinkingOverride !== undefined);
321
+ assertThinkingWithinCeiling({ model: modelArg, configThinking: effectiveThinking, ceiling: options.thinkingCeiling, agent: agent.name, runId: options.runId });
322
+ const expectedModelForVerification = shared.verifyModel ? modelArg : undefined;
317
323
  const resolvedThinking = resolveEffectiveThinking(modelArg, effectiveThinking);
318
324
  const watchdogConfig = resolveWatchdogConfig(options.cwd ?? runtimeCwd);
319
325
  const childWatchdog = watchdogConfig.ok
@@ -363,6 +369,8 @@ async function runSingleAttempt(
363
369
  steerCapabilityPath: options.steerCapabilityPath,
364
370
  steerAckDir: options.steerAckDir,
365
371
  structuredOutput: options.structuredOutput,
372
+ fast: options.fast ?? agent.fast,
373
+ modelCandidates: shared.modelCandidates,
366
374
  toolBudget: options.toolBudget,
367
375
  allowZeroToolBudget: options.allowZeroToolBudget,
368
376
  permissionRules,
@@ -370,6 +378,8 @@ async function runSingleAttempt(
370
378
  childWatchdog,
371
379
  waitToolEnabled: options.waitToolEnabled,
372
380
  capabilityCeiling: options.capabilityCeiling,
381
+ thinkingCeiling: options.thinkingCeiling,
382
+ extensionBindings: options.extensionBindings,
373
383
  });
374
384
  if (!shared.launchWarnings.emitted && warnings.length > 0) {
375
385
  for (const warning of warnings) console.warn(`[pi-subagents] ${warning}`);
@@ -385,18 +395,53 @@ async function runSingleAttempt(
385
395
  cwd: options.cwd ?? runtimeCwd,
386
396
  requireReadTool: Boolean(shared.resolvedSkillNames?.length),
387
397
  structuredOutput: Boolean(options.structuredOutput),
398
+ fast: options.fast ?? agent.fast,
399
+ model: modelArg,
400
+ modelCandidates: shared.modelCandidates,
388
401
  capabilityCeiling: options.capabilityCeiling,
389
402
  inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
390
403
  agentName: agent.name,
391
404
  permissionRules,
392
405
  });
406
+ const contractTools = toolPlan.explicitToolAllowlist ? toolPlan.effectiveToolAllowlist : undefined;
407
+ const contractError = validateImplementationToolContract({
408
+ agent: agent.name,
409
+ task: shared.originalTask ?? task,
410
+ tools: contractTools,
411
+ mcpDirectTools: toolPlan.effectiveMcpTools,
412
+ configuredExtensions: toolPlan.configuredExtensions,
413
+ requestedTools: toolPlan.requestedBuiltinTools,
414
+ acceptanceRole: agent.acceptanceRole,
415
+ completionGuard: agent.completionGuard,
416
+ });
417
+ if (contractError) {
418
+ cleanupTempDir(tempDir);
419
+ return {
420
+ index: options.index ?? 0,
421
+ agent: agent.name,
422
+ task,
423
+ messages: [],
424
+ finalOutput: "",
425
+ exitCode: 1,
426
+ error: contractError,
427
+ usage: emptyUsage(),
428
+ model: modelArg,
429
+ modelAttempts: [],
430
+ attemptedModels: [],
431
+ progressSummary: { status: "failed", toolCount: 0, tokens: 0, durationMs: 0 },
432
+ ...(toolPlan.capabilityCeiling ? { capabilityCeiling: toolPlan.capabilityCeiling } : {}),
433
+ ...(toolPlan.capabilityAudit ? { capabilityAudit: toolPlan.capabilityAudit } : {}),
434
+ };
435
+ }
393
436
  const launchResolvedExtensions = projectLaunchResolvedChildExtensions(toolPlan);
394
437
  const launchContractDigest = launchBindingDigest({
395
438
  definitionDigest: agentDefinitionDigest(agent),
396
439
  task: shared.originalTask ?? task,
397
440
  ...(modelArg ? { model: modelArg } : {}),
398
441
  modelCandidates: shared.modelCandidates,
442
+ ...((options.fast ?? agent.fast) !== undefined ? { fast: options.fast ?? agent.fast } : {}),
399
443
  ...(resolvedThinking ? { thinking: resolvedThinking } : {}),
444
+ ...(options.thinkingCeiling ? { thinkingCeiling: options.thinkingCeiling } : {}),
400
445
  systemPrompt: effectiveSystemPrompt,
401
446
  systemPromptMode: agent.systemPromptMode,
402
447
  inheritProjectContext: agent.inheritProjectContext,
@@ -408,6 +453,7 @@ async function runSingleAttempt(
408
453
  ...(options.outputPath ? { outputPath: options.outputPath } : {}),
409
454
  outputMode: options.outputMode ?? "inline",
410
455
  ...(options.structuredOutput ? { structuredOutputSchema: options.structuredOutput.schema } : {}),
456
+ ...(options.extensionBindings ? { extensionBindings: options.extensionBindings } : {}),
411
457
  });
412
458
  const result: SingleResult = withRunContext({
413
459
  index: options.index ?? 0,
@@ -487,9 +533,11 @@ async function runSingleAttempt(
487
533
  return result;
488
534
  }
489
535
  const spawnEnv = { ...process.env, ...sharedEnv, ...getSubagentDepthEnv(options.maxSubagentDepth) };
536
+ const mutationSnapshot = snapshotTrackedMutations(options.cwd ?? runtimeCwd);
490
537
  let observedMutationAttempt = false;
491
538
  let structuredOutputToolInvoked = false;
492
539
  let structuredOutputMessageStartIndex: number | undefined;
540
+ let toolAvailabilityError: string | undefined;
493
541
 
494
542
  const exitCode = await new Promise<number>((resolve) => {
495
543
  const spawnSpec = getPiSpawnCommand(args);
@@ -1042,6 +1090,10 @@ async function runSingleAttempt(
1042
1090
  if (evt.message.model) {
1043
1091
  progress.model = evt.message.model;
1044
1092
  if (!result.model) result.model = evt.message.model;
1093
+ if (expectedModelForVerification && !hasToolCall) {
1094
+ const modelVerificationError = formatSubagentModelVerificationError(expectedModelForVerification, evt.message.model, options.availableModels);
1095
+ if (modelVerificationError && !result.error) result.error = modelVerificationError;
1096
+ }
1045
1097
  }
1046
1098
  if (evt.message.errorMessage) assistantError = evt.message.errorMessage;
1047
1099
  const assistantText = extractTextFromContent(evt.message.content);
@@ -1267,6 +1319,7 @@ async function runSingleAttempt(
1267
1319
  // JSONL artifact flush is best effort.
1268
1320
  });
1269
1321
  const toolDiagnosticError = readChildToolDiagnosticError(toolDiagnosticPath);
1322
+ toolAvailabilityError = toolDiagnosticError;
1270
1323
  result.runtimeAcknowledgedExtensions = readRuntimeAcknowledgedExtensions(runtimeAcknowledgedExtensionsPath);
1271
1324
  cleanupTempDir(tempDir);
1272
1325
  stdoutReader.end();
@@ -1405,6 +1458,9 @@ async function runSingleAttempt(
1405
1458
  result.structuredOutputFailed = true;
1406
1459
  } else {
1407
1460
  result.structuredOutput = structured.value;
1461
+ const acceptanceReport = readStructuredOutputAcceptanceReport(options.structuredOutput);
1462
+ (result as SingleResult & { structuredAcceptanceReport?: unknown; structuredAcceptanceReportError?: string }).structuredAcceptanceReport = acceptanceReport.value;
1463
+ (result as SingleResult & { structuredAcceptanceReport?: unknown; structuredAcceptanceReportError?: string }).structuredAcceptanceReportError = acceptanceReport.error;
1408
1464
  validatedStructuredOutput = true;
1409
1465
  }
1410
1466
  }
@@ -1442,6 +1498,7 @@ async function runSingleAttempt(
1442
1498
  tokens: progress.tokens,
1443
1499
  durationMs: progress.durationMs,
1444
1500
  };
1501
+ const mutationEvidence = collectTrackedMutationEvidence(mutationSnapshot, options.cwd ?? runtimeCwd);
1445
1502
 
1446
1503
  const acceptanceOutput = getFinalOutput(result.messages ?? []);
1447
1504
  let fullOutput = stripAcceptanceReport(acceptanceOutput);
@@ -1449,9 +1506,19 @@ async function runSingleAttempt(
1449
1506
  result.outputState = fullOutput.trim() || result.structuredOutput !== undefined ? "present" : "absent";
1450
1507
  if (result.timedOut) {
1451
1508
  const timeoutMessage = formatTimeoutMessage(options.timeoutMs ?? 0);
1509
+ result.timeoutRecovery = buildTimeoutRecoverySummary({
1510
+ termination: "timed-out",
1511
+ evidence: mutationEvidence,
1512
+ currentTool: progress.currentTool,
1513
+ currentToolArgs: progress.currentToolArgs,
1514
+ currentPath: progress.currentPath,
1515
+ sessionFile: options.sessionFile,
1516
+ transcriptPath: shared.transcriptWriter ? shared.artifactPaths?.transcriptPath : undefined,
1517
+ artifactPaths: shared.artifactPaths,
1518
+ });
1452
1519
  fullOutput = fullOutput.trim()
1453
- ? `${timeoutMessage}\n\nPartial output before timeout:\n${fullOutput}`
1454
- : timeoutMessage;
1520
+ ? `${timeoutMessage}\n\n${result.timeoutRecovery.message}\n\nPartial output before timeout:\n${fullOutput}`
1521
+ : `${timeoutMessage}\n\n${result.timeoutRecovery.message}`;
1455
1522
  } else if (result.turnBudgetExceeded && result.turnBudget) {
1456
1523
  fullOutput = formatTurnBudgetOutput(turnBudgetExceededMessage(result.turnBudget, result.turnBudget.turnCount), fullOutput);
1457
1524
  } else if (result.turnBudget?.outcome === "termination-deferred") {
@@ -1462,16 +1529,20 @@ async function runSingleAttempt(
1462
1529
  fullOutput = fullOutput.trim() ? `${note}\n\n${fullOutput}` : note;
1463
1530
  }
1464
1531
  const completionGuardEnabled = isAgentContractV1(options.agentContract) ? agent.completionGuard === true : agent.completionGuard !== false;
1465
- const completionGuard = result.exitCode === 0 && !result.error && completionGuardEnabled
1532
+ const completionGuard = ((result.exitCode === 0 && !result.error) || toolAvailabilityError) && completionGuardEnabled
1466
1533
  ? evaluateCompletionMutationGuard({
1467
1534
  agent: agent.name,
1468
1535
  task: shared.originalTask ?? task,
1469
1536
  messages: result.messages ?? [],
1470
- tools: agent.tools,
1471
- mcpDirectTools: agent.mcpDirectTools,
1537
+ tools: contractTools,
1538
+ mcpDirectTools: toolPlan.effectiveMcpTools,
1539
+ toolAvailabilityError,
1540
+ mutationEvidence,
1472
1541
  })
1473
1542
  : undefined;
1474
- let completionGuardTriggered = completionGuard?.triggered === true && !observedMutationAttempt;
1543
+ const mutationAttemptObserved = observedMutationAttempt || mutationEvidence.attemptedMutation;
1544
+ let completionGuardTriggered = completionGuard?.triggered === true && !mutationAttemptObserved;
1545
+ const completionGuardBlocked = completionGuard?.blocked === true;
1475
1546
  // The classifier is deliberately narrow, so a read-only review task can
1476
1547
  // still be misread as implementation. Arbitrate BEFORE any failure side
1477
1548
  // effect is published (effects, exit code, progress, notifications,
@@ -1492,7 +1563,9 @@ async function runSingleAttempt(
1492
1563
  result.effects = {
1493
1564
  ...(result.effects ?? {}),
1494
1565
  fileMutation: {
1495
- status: completionGuard.expectedMutation
1566
+ status: completionGuardBlocked
1567
+ ? "blocked"
1568
+ : completionGuard.expectedMutation
1496
1569
  ? completionGuardTriggered
1497
1570
  ? "missing"
1498
1571
  : arbiterRescued
@@ -1500,7 +1573,9 @@ async function runSingleAttempt(
1500
1573
  : "observed"
1501
1574
  : "not-applicable",
1502
1575
  expected: completionGuard.expectedMutation,
1503
- attempted: completionGuard.attemptedMutation || observedMutationAttempt,
1576
+ attempted: completionGuardBlocked ? false : completionGuard.attemptedMutation || mutationAttemptObserved,
1577
+ evidence: mutationEvidence,
1578
+ ...(completionGuardBlocked && completionGuard.message ? { message: completionGuard.message } : {}),
1504
1579
  ...(completionGuardTriggered ? { message: "Subagent completed without making edits for an implementation task." } : {}),
1505
1580
  ...(arbiterRescued ? { resolvedBy: "llm-intent-arbiter" } : {}),
1506
1581
  },
@@ -1582,6 +1657,14 @@ async function runSyncCompletionInner(
1582
1657
  error: `Unknown agent: ${agentName}`,
1583
1658
  }, options.context));
1584
1659
  }
1660
+ options = {
1661
+ ...options,
1662
+ thinkingCeiling: intersectThinkingCeilings(
1663
+ options.thinkingCeiling,
1664
+ agent.maxThinking,
1665
+ decodeThinkingCeiling(process.env[SUBAGENT_THINKING_CEILING_ENV]),
1666
+ ),
1667
+ };
1585
1668
  try {
1586
1669
  assertAgentAllowedByCapabilityCeiling(agent.name, options.capabilityCeiling);
1587
1670
  } catch (error) {
@@ -1652,7 +1735,7 @@ async function runSyncCompletionInner(
1652
1735
  dynamicGroup: options.acceptanceContext?.dynamicGroup,
1653
1736
  agentContract: options.agentContract,
1654
1737
  });
1655
- const acceptancePrompt = formatAcceptancePrompt(effectiveAcceptance, { reportOptional: isAgentContractV1(options.agentContract) });
1738
+ const acceptancePrompt = formatAcceptancePrompt(effectiveAcceptance, { reportOptional: isAgentContractV1(options.agentContract), structuredOutput: Boolean(options.structuredOutput?.acceptanceReportPath) });
1656
1739
  const taskWithAcceptance = acceptancePrompt ? `${task}\n${acceptancePrompt}` : task;
1657
1740
  options.onEffectivePrompt?.(taskWithAcceptance);
1658
1741
  const sessionEnabled = Boolean(options.sessionFile || options.sessionDir) || shareEnabled;
@@ -1695,9 +1778,25 @@ async function runSyncCompletionInner(
1695
1778
  options.modelOverride ?? agent.model,
1696
1779
  agent.fallbackModels,
1697
1780
  options.availableModels,
1698
- options.preferredModelProvider,
1781
+ agent.modelProvider ?? options.preferredModelProvider,
1699
1782
  { scope: options.modelScope, primaryModelFromParent: options.modelOverrideFromParent },
1700
1783
  );
1784
+ try {
1785
+ for (const candidate of candidates) {
1786
+ const model = applyThinkingSuffix(candidate, options.thinkingOverride ?? agent.thinking, options.thinkingOverride !== undefined);
1787
+ assertThinkingWithinCeiling({ model, configThinking: options.thinkingOverride ?? agent.thinking, ceiling: options.thinkingCeiling, agent: agent.name, runId: options.runId });
1788
+ }
1789
+ } catch (error) {
1790
+ return redactResultPrompt(withRunContext({
1791
+ index: options.index ?? 0,
1792
+ agent: agent.name,
1793
+ task,
1794
+ exitCode: 1,
1795
+ messages: [],
1796
+ usage: emptyUsage(),
1797
+ error: error instanceof Error ? error.message : String(error),
1798
+ }, options.context));
1799
+ }
1701
1800
  const attemptedModels: string[] = [];
1702
1801
  const modelAttempts: ModelAttempt[] = [];
1703
1802
  const aggregateUsage = emptyUsage();
@@ -1777,6 +1876,7 @@ async function runSyncCompletionInner(
1777
1876
  modelAttemptsLoop: for (let modelIndex = 0; modelIndex < modelsToTry.length; modelIndex++) {
1778
1877
  const candidate = modelsToTry[modelIndex];
1779
1878
  for (let startupAttemptIndex = 0; ; startupAttemptIndex++) {
1879
+ const verifyModel = Boolean(candidate) && !(options.modelOverrideFromParent && modelIndex === 0);
1780
1880
  const outputSnapshot = captureSingleOutputSnapshot(options.outputPath);
1781
1881
  const result = await runSingleAttempt(runtimeCwd, agent, taskWithAcceptance, candidate, attemptOptions, {
1782
1882
  sessionEnabled,
@@ -1795,6 +1895,7 @@ async function runSyncCompletionInner(
1795
1895
  taskDelivery: taskDeliveryOverride,
1796
1896
  orcaProgressTab,
1797
1897
  launchWarnings,
1898
+ verifyModel,
1798
1899
  });
1799
1900
  lastResult = result;
1800
1901
  if (startupAttemptIndex === 0) {
@@ -1971,6 +2072,8 @@ async function runSyncCompletionInner(
1971
2072
  result.acceptance = await evaluateAcceptance({
1972
2073
  acceptance: effectiveAcceptance,
1973
2074
  output: acceptanceOutputByResult.get(result) ?? result.finalOutput ?? "",
2075
+ report: (result as SingleResult & { structuredAcceptanceReport?: import("../../shared/types.ts").AcceptanceReport; structuredAcceptanceReportError?: string }).structuredAcceptanceReport,
2076
+ reportError: (result as SingleResult & { structuredAcceptanceReport?: import("../../shared/types.ts").AcceptanceReport; structuredAcceptanceReportError?: string }).structuredAcceptanceReportError,
1974
2077
  fileOutput: childWrittenOutput !== undefined && options.outputPath
1975
2078
  ? { content: childWrittenOutput, path: options.outputPath, authoritative: options.outputMode === "file-only" }
1976
2079
  : undefined,
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import type { ForegroundResumeChild, ForegroundResumeRun, SubagentState } from "../../shared/types.ts";
4
4
  import { DIRS } from "../../shared/types.ts";
5
- import { writeAtomicJson } from "../../shared/atomic-json.ts";
5
+ import { writePrivateAtomicJson } from "../../shared/atomic-json.ts";
6
6
  import { utf8Tail } from "../../shared/utf8.ts";
7
7
 
8
8
  export const MAX_REMEMBERED_FOREGROUND_RUNS = 50;
@@ -52,6 +52,7 @@ function compactChild(child: ForegroundResumeChild): ForegroundResumeChild {
52
52
  ...(child.transcriptError ? { transcriptError: child.transcriptError } : {}),
53
53
  ...(child.acceptance ? { acceptance: child.acceptance } : {}),
54
54
  ...(child.launchContractDigest ? { launchContractDigest: child.launchContractDigest } : {}),
55
+ ...(child.extensionBindings ? { extensionBindings: child.extensionBindings } : {}),
55
56
  ...(child.capabilityCeiling ? { capabilityCeiling: child.capabilityCeiling } : {}),
56
57
  ...(child.updatedAt !== undefined ? { updatedAt: child.updatedAt } : {}),
57
58
  };
@@ -118,7 +119,7 @@ export function persistForegroundRunHistory(state: SubagentState, options: { res
118
119
  if (compact) merged.set(compact.runId, compact);
119
120
  }
120
121
  const runs = sortAndBound([...merged.values()], limit);
121
- writeAtomicJson(historyPath(resultsDir), { version: HISTORY_VERSION, runs });
122
+ writePrivateAtomicJson(historyPath(resultsDir), { version: HISTORY_VERSION, runs });
122
123
  }
123
124
 
124
125
  export function restoreForegroundRunHistory(state: SubagentState, options: { resultsDir?: string; sessionId?: string | null; limit?: number } = {}): number {