pi-subagents 0.67.0 → 0.68.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 (120) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/README.md +1 -1
  3. package/docs/agents.md +37 -12
  4. package/docs/configuration.md +61 -19
  5. package/docs/extension-api.md +5 -1
  6. package/docs/missions.md +2 -2
  7. package/docs/models.md +11 -79
  8. package/docs/observability.md +18 -8
  9. package/docs/standalone-background.md +13 -3
  10. package/docs/tool-reference.md +15 -12
  11. package/docs/watchdog.md +10 -12
  12. package/docs/workflows.md +11 -1
  13. package/index.ts +5 -2
  14. package/package.json +4 -2
  15. package/runner-peer-loader.mjs +24 -0
  16. package/runner-peer-preload.mjs +25 -11
  17. package/skills/pi-subagents/SKILL.md +18 -21
  18. package/skills/pi-subagents/references/constraints-and-recipes.md +3 -2
  19. package/skills/pi-subagents/references/execution-controls.md +4 -4
  20. package/skills/pi-subagents/references/management-authoring-rpc.md +0 -1
  21. package/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
  22. package/skills/pi-subagents/references/prompting-and-roles.md +16 -12
  23. package/skills/pi-subagents/references/review-and-validation.md +3 -3
  24. package/src/agents/agent-management.ts +57 -58
  25. package/src/agents/agent-serializer.ts +4 -3
  26. package/src/agents/agents.ts +184 -71
  27. package/src/agents/chain-serializer.ts +5 -0
  28. package/src/agents/runtime-agent-registry.ts +7 -6
  29. package/src/api/preflight.ts +20 -16
  30. package/src/api/required-child-extensions.ts +6 -0
  31. package/src/extension/config.ts +10 -37
  32. package/src/extension/fanout-child.ts +3 -0
  33. package/src/extension/herdr-pi-bridge.ts +160 -0
  34. package/src/extension/index.ts +42 -31
  35. package/src/extension/public-execution.ts +3 -3
  36. package/src/extension/schemas.ts +16 -5
  37. package/src/extension/tool-description.ts +8 -7
  38. package/src/intercom/native-supervisor-channel.ts +22 -18
  39. package/src/policy/authority.ts +4 -0
  40. package/src/profiles/profiles.ts +12 -6
  41. package/src/runs/background/active-run-index.ts +17 -1
  42. package/src/runs/background/async-execution.ts +309 -126
  43. package/src/runs/background/async-job-tracker.ts +8 -6
  44. package/src/runs/background/async-resume.ts +13 -4
  45. package/src/runs/background/async-status.ts +15 -4
  46. package/src/runs/background/auto-drain.ts +20 -10
  47. package/src/runs/background/binary-bootstrap.ts +5 -0
  48. package/src/runs/background/chain-append.ts +1 -1
  49. package/src/runs/background/chain-root-attachment.ts +14 -33
  50. package/src/runs/background/notify.ts +74 -6
  51. package/src/runs/background/result-files.ts +8 -4
  52. package/src/runs/background/result-watcher.ts +19 -2
  53. package/src/runs/background/run-child-session.ts +20 -29
  54. package/src/runs/background/runner-aliases.ts +4 -33
  55. package/src/runs/background/runner-child-launch.ts +4 -1
  56. package/src/runs/background/runner-child-sessions.ts +2 -2
  57. package/src/runs/background/runner-http-dispatcher.ts +119 -0
  58. package/src/runs/background/scheduled-runs.ts +11 -5
  59. package/src/runs/background/stale-run-reconciler.ts +35 -11
  60. package/src/runs/background/subagent-runner.ts +396 -275
  61. package/src/runs/background/subagent-wait.ts +128 -23
  62. package/src/runs/background/wait-completions.ts +75 -27
  63. package/src/runs/background/wait-subscriptions.ts +9 -3
  64. package/src/runs/background/wait-tool.ts +4 -2
  65. package/src/runs/foreground/async-stop-action.ts +93 -3
  66. package/src/runs/foreground/execution.ts +91 -218
  67. package/src/runs/foreground/foreground-history.ts +2 -1
  68. package/src/runs/foreground/subagent-executor.ts +266 -80
  69. package/src/runs/shared/acceptance.ts +34 -10
  70. package/src/runs/shared/async-status-projection.ts +123 -33
  71. package/src/runs/shared/child-launch-plan.ts +15 -3
  72. package/src/runs/shared/child-launch.ts +19 -6
  73. package/src/runs/shared/child-runtime-config.ts +5 -0
  74. package/src/runs/shared/child-session.ts +94 -50
  75. package/src/runs/shared/child-tool-plan.ts +28 -16
  76. package/src/runs/shared/dynamic-fanout.ts +2 -2
  77. package/src/runs/shared/external-cli-contract.ts +11 -1
  78. package/src/runs/shared/external-cli-preflight.ts +6 -2
  79. package/src/runs/shared/herdr-connection.ts +134 -0
  80. package/src/runs/shared/herdr-external-adapters.ts +169 -0
  81. package/src/runs/shared/herdr-machine.ts +279 -0
  82. package/src/runs/shared/herdr-pi-protocol.ts +59 -0
  83. package/src/runs/shared/herdr-placed-run.ts +263 -0
  84. package/src/runs/shared/model-resolution-diagnostic.ts +76 -0
  85. package/src/runs/shared/{model-fallback.ts → model-resolution.ts} +22 -237
  86. package/src/runs/shared/model-scope.ts +1 -1
  87. package/src/runs/shared/nested-events.ts +11 -2
  88. package/src/runs/shared/parallel-utils.ts +7 -2
  89. package/src/runs/shared/pi-spawn.ts +1 -1
  90. package/src/runs/shared/subagent-prompt-runtime.ts +4 -2
  91. package/src/runs/shared/worktree-setup-command.ts +27 -4
  92. package/src/runs/shared/worktree.ts +3 -3
  93. package/src/shared/child-cache-retention.ts +43 -0
  94. package/src/shared/launch-contract.ts +6 -9
  95. package/src/shared/pruned-fork.ts +1 -1
  96. package/src/shared/required-child-extensions.ts +81 -0
  97. package/src/shared/settings.ts +5 -2
  98. package/src/shared/shortcuts.ts +0 -4
  99. package/src/shared/types.ts +70 -29
  100. package/src/slash/slash-commands.ts +0 -6
  101. package/src/slash/subagents-admin.ts +13 -9
  102. package/src/tui/render.ts +20 -10
  103. package/src/watchdog/child-status.ts +28 -36
  104. package/src/watchdog/lsp-diagnostics.ts +1 -1
  105. package/src/watchdog/model-selection.ts +1 -1
  106. package/src/watchdog/register-child.ts +10 -3
  107. package/src/watchdog/register-main.ts +20 -20
  108. package/src/watchdog/render.ts +1 -1
  109. package/src/watchdog/review.ts +14 -30
  110. package/src/watchdog/rules.ts +1 -1
  111. package/src/watchdog/runtime.ts +23 -12
  112. package/src/watchdog/settings.ts +3 -6
  113. package/src/watchdog/types.ts +3 -5
  114. package/src/watchdog/warning-format.ts +1 -1
  115. package/src/workflows/scripted-workflow.ts +42 -3
  116. package/src/workflows/workflow-receipt.ts +21 -3
  117. package/src/workflows/workflow-resources.ts +13 -2
  118. package/src/runs/shared/model-exclusions.ts +0 -374
  119. package/src/runs/shared/readonly-model-continuation.ts +0 -69
  120. package/src/runs/shared/readonly-session-evidence.ts +0 -307
@@ -28,8 +28,10 @@ import { runSync } from "./execution.ts";
28
28
  import { handleWatchdogToolAction, WATCHDOG_TOOL_ACTIONS } from "../../watchdog/tool-actions.ts";
29
29
  import type { MainWatchdogRuntime } from "../../watchdog/runtime.ts";
30
30
  import { applyWatchdogLaunchRules } from "../../watchdog/rules.ts";
31
- import { buildModelCandidates, normalizeParentModel, resolveEffectiveSubagentModel, resolveModelOrigin, type ModelOrigin, type ParentModel } from "../shared/model-fallback.ts";
31
+ import { childWatchdogProgressForModel } from "../../watchdog/child-status.ts";
32
+ import { normalizeParentModel, resolveEffectiveSubagentModel, resolveModelOrigin, resolveModelSelection, type ModelOrigin, type ParentModel } from "../shared/model-resolution.ts";
32
33
  import { getHostBuiltinToolNames } from "../shared/child-tool-plan.ts";
34
+ import { projectChainOutputSchemas, resolveEffectiveOutputSchema } from "../shared/child-launch-plan.ts";
33
35
  import { formatRetainedChildren, listRetainedChildren } from "../background/retained-children.ts";
34
36
  import { resolveModelScopesForAgent, type ModelScopeConfig } from "../shared/model-scope.ts";
35
37
  import { recordRun } from "../shared/run-history.ts";
@@ -53,7 +55,7 @@ import { isScheduledRunAction } from "../background/scheduled-runs.ts";
53
55
  import { encodeIndexSegment } from "../background/index-segment.ts";
54
56
  import { enqueueChainAppendRequest, readPendingChainAppendRequests, runnerStepOutputNames } from "../background/chain-append.ts";
55
57
  import { ChainOutputValidationError, validateChainOutputBindingsWithContext } from "../shared/chain-outputs.ts";
56
- import { normalizeGateAcceptance, resolveAcceptanceReportMode, validateAcceptanceInput, validateExecutionAcceptance } from "../shared/acceptance.ts";
58
+ import { normalizeGateAcceptance, resolveAcceptanceReportMode, validateAcceptanceInput, validateExecutionAcceptance, validateExecutionAcceptancePolicy } from "../shared/acceptance.ts";
57
59
  import { canPreferFork, createForkContextResolver, resolveSubagentLaunchContext } from "../../shared/fork-context.ts";
58
60
  import { createPrunedForkSessionWriter } from "../../shared/pruned-fork.ts";
59
61
  import { resolveCurrentSessionId } from "../../shared/session-identity.ts";
@@ -68,6 +70,7 @@ import { usageBudgetExceededMessage, usageBudgetState, validateUsageBudgetConfig
68
70
  import { intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
69
71
  import { isAgentContract } from "../shared/agent-contract.ts";
70
72
  import { normalizeExtensionBindings, type ExtensionBindings } from "../shared/extension-bindings.ts";
73
+ import { resolveRequiredChildExtensions } from "../../shared/required-child-extensions.ts";
71
74
  import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, outputPathMappingFromTask, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
72
75
  import { assertJsonSchemaObject, cleanupStructuredOutputRuntime, createStructuredOutputRuntime } from "../shared/structured-output.ts";
73
76
  import { compactForegroundDetails, getSingleResultOutput, readStatus, resolveChildCwd, sumResultsCost, sumResultsUsage, toAgentToolUsage } from "../../shared/utils.ts";
@@ -96,7 +99,7 @@ import { dismissRecoveredWorkflow } from "./async-dismiss-action.ts";
96
99
  import { promotePausedWorkflowIfSettled, reconcileDetachedWorkflowChildCompletion } from "./workflow-detach-reconcile.ts";
97
100
  import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
98
101
  import { resolveAsyncRootResultPath, waitForImportedAsyncRoot } from "../background/chain-root-attachment.ts";
99
- import { resultFilePath, writeAsyncResultFile } from "../background/result-files.ts";
102
+ import { fallbackResultPayloadPathForSessionRun, removeResultIndex, resultFilePath, writeAsyncResultFile } from "../background/result-files.ts";
100
103
  import { attachRootChildrenToSteps, createNestedRoute, findNestedControlResult, inheritedNestedParentAddressOf, inheritedNestedRouteOf, resolveNestedAsyncDir, snapshotNestedEventFiles, updateForegroundNestedProjection, writeNestedControlRequest, writeNestedEvent, type NestedParentAddress, type NestedRoute, type NestedRunResolutionScope } from "../shared/nested-events.ts";
101
104
  import type { ChildRuntimeConfig } from "../shared/child-runtime-config.ts";
102
105
  import { resolveSubagentRunId, type ResolvedSubagentRunId } from "../background/run-id-resolver.ts";
@@ -106,6 +109,7 @@ import { inspectSubagentStatus } from "../background/run-status.ts";
106
109
  import { getExternalJobProvider } from "../../api/external-job-provider.ts";
107
110
  import { externalJobFollowUpRequestDigest, externalJobFollowUpRequestId, externalJobFollowUpRunId, externalJobPromptDigest, externalJobStableJson } from "../shared/external-job-runner.ts";
108
111
  import { externalCliReceiptMetadata, normalizeExternalCliRunnerStatus } from "../shared/external-cli-contract.ts";
112
+ import { formatHerdrMachineRunnerUnsupported, resolveHerdrMachinePlacement } from "../shared/herdr-machine.ts";
109
113
  import { applyForceTopLevelAsyncOverride } from "../background/top-level-async.ts";
110
114
  import { handleMissionAction, MISSION_ACTIONS } from "../../missions/actions.ts";
111
115
  import { attachMissionToLaunchResult, prepareMissionLaunch, writeMissionAsyncBinding, type MissionLaunchBinding } from "../../missions/lifecycle.ts";
@@ -117,7 +121,7 @@ import { handleInspectorAction, INSPECTOR_ACTIONS } from "../../inspectors/actio
117
121
  import { createBuiltinInspectorPlugins } from "../../inspectors/plugins.ts";
118
122
  import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../inspectors/herdr/project-panes.ts";
119
123
  import { previewSimpleWorkflowRun, runWorkflowScript, validateWorkflowScript, WorkflowScriptError, type WorkflowChildSettledNotification, type WorkflowLanePlan, type WorkflowReceiptResumeReference, type WorkflowScriptChildResult, type WorkflowScriptTraceEntry, type WorkflowSteerOptions, type WorkflowSteerResult } from "../../workflows/scripted-workflow.ts";
120
- import { formatIncrementalChildCompletion, scheduledCompletionTriggersTurn } from "../background/notify.ts";
124
+ import { formatIncrementalChildCompletion, incrementalChildCompletionTriggersTurn } from "../background/notify.ts";
121
125
  import { executeWorkflowHostCommand, resolveWorkflowHostOutputClaimPath, type WorkflowHostCommandParams, type WorkflowHostCommandResult } from "../../workflows/host-command.ts";
122
126
  import { buildWorkflowReceipt, readWorkflowReceipt, workflowReceiptPath, resolveWorkflowReceiptResumeEntry, writeWorkflowReceipt, type WorkflowReceipt, type WorkflowReceiptState } from "../../workflows/workflow-receipt.ts";
123
127
  import { upsertHostStep, validHostStepNodes } from "../shared/host-step-status.ts";
@@ -135,7 +139,8 @@ import {
135
139
  type WorkflowResourceAuthority,
136
140
  type WorkflowResourcePermit,
137
141
  } from "../../shared/workflow-child-permit.ts";
138
- import { resolveWorkflowResource } from "../../workflows/workflow-resources.ts";
142
+ import { deepFreezeWorkflowArgs, normalizeWorkflowArgs, resolveWorkflowResource } from "../../workflows/workflow-resources.ts";
143
+ import { stableJsonDigest } from "../../shared/launch-contract.ts";
139
144
  import {
140
145
  cleanupWorktrees,
141
146
  createWorktrees,
@@ -287,6 +292,7 @@ interface TaskParam {
287
292
  agent: string;
288
293
  task: string;
289
294
  cwd?: string;
295
+ machine?: string;
290
296
  count?: number;
291
297
  output?: string | boolean;
292
298
  outputMode?: "inline" | "file-only";
@@ -295,7 +301,7 @@ interface TaskParam {
295
301
  model?: string;
296
302
  fast?: boolean;
297
303
  skill?: string | string[] | boolean;
298
- outputSchema?: JsonSchemaObject;
304
+ outputSchema?: JsonSchemaObject | false;
299
305
  acceptance?: AcceptanceInput;
300
306
  agentContract?: AgentContract;
301
307
  toolBudget?: ToolBudgetConfig;
@@ -371,6 +377,8 @@ export interface SubagentParamsLike {
371
377
  foregroundOnly?: boolean;
372
378
  timeoutMs?: number;
373
379
  maxRuntimeMs?: number;
380
+ /** Async runs only: steer the child to checkpoint and stop this many ms before the run deadline. */
381
+ checkpointBeforeDeadlineMs?: number;
374
382
  /** Optional hard per-tool-call timeout (ms). Known-fast tools also have a default. */
375
383
  toolTimeoutMs?: number;
376
384
  toolBudget?: ToolBudgetConfig;
@@ -380,6 +388,9 @@ export interface SubagentParamsLike {
380
388
  control?: ControlConfig;
381
389
  sessionDir?: string;
382
390
  cwd?: string;
391
+ machine?: string;
392
+ /** Internal: the typed cwd when a machine is set, kept as a remote path and never resolved locally. */
393
+ machineCwd?: string;
383
394
  maxOutput?: MaxOutputConfig;
384
395
  artifacts?: boolean;
385
396
  includeProgress?: boolean;
@@ -399,7 +410,7 @@ export interface SubagentParamsLike {
399
410
  /** Internal-only; not part of the public tool schema. Wired for single-run reads (chain steps use their own field). */
400
411
  reads?: string[] | false;
401
412
  outputMode?: "inline" | "file-only";
402
- outputSchema?: JsonSchemaObject;
413
+ outputSchema?: JsonSchemaObject | false;
403
414
  agentScope?: unknown;
404
415
  chainDir?: string;
405
416
  acceptance?: AcceptanceInput;
@@ -746,7 +757,7 @@ function foregroundChildActivityFromProgress(progress: SingleResult["progress"]
746
757
  };
747
758
  }
748
759
 
749
- function rememberForegroundRun(state: SubagentState, input: { modelResponseAliases?: Record<string, string[]>; runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[]; params: SubagentParamsLike; effectiveOutput?: string | boolean; effectiveOutputMode: OutputMode; extensionBindings?: ExtensionBindings }): void {
760
+ function rememberForegroundRun(state: SubagentState, input: { modelResponseAliases?: Record<string, string[]>; runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[]; params: SubagentParamsLike; effectiveOutput?: string | boolean; effectiveOutputMode: OutputMode; extensionBindings?: ExtensionBindings; requiredExtensions?: SteeringRecoveryDescriptor["requiredExtensions"] }): void {
750
761
  state.foregroundRuns ??= new Map();
751
762
  const previous = state.foregroundRuns.get(input.runId);
752
763
  const updatedAt = Date.now();
@@ -799,6 +810,7 @@ function rememberForegroundRun(state: SubagentState, input: { modelResponseAlias
799
810
  ...(Object.keys(resumeContract).length ? { resumeContract } : {}),
800
811
  ...(result.launchContractDigest ? { launchContractDigest: result.launchContractDigest } : {}),
801
812
  ...(input.extensionBindings ? { extensionBindings: input.extensionBindings } : {}),
813
+ ...(input.requiredExtensions?.length ? { requiredExtensions: input.requiredExtensions } : {}),
802
814
  ...(result.launchResolvedExtensions ? { launchResolvedExtensions: result.launchResolvedExtensions } : {}),
803
815
  ...(result.runtimeAcknowledgedExtensions ? { runtimeAcknowledgedExtensions: result.runtimeAcknowledgedExtensions } : {}),
804
816
  ...(result.capabilityCeiling ? { capabilityCeiling: result.capabilityCeiling } : {}),
@@ -950,6 +962,7 @@ function resolveForegroundResumeTarget(params: SubagentParamsLike, state: Subage
950
962
  ...(child.launchContractDigest ? { launchContractDigest: child.launchContractDigest } : {}),
951
963
  ...(child.resumeContract ? { resumeContract: child.resumeContract } : {}),
952
964
  ...(child.extensionBindings ? { extensionBindings: normalizeExtensionBindings(child.extensionBindings)!.value } : {}),
965
+ ...(child.requiredExtensions ? { requiredExtensions: child.requiredExtensions } : {}),
953
966
  ...(child.capabilityCeiling ? { capabilityCeiling: child.capabilityCeiling } : {}),
954
967
  };
955
968
  }
@@ -1240,10 +1253,14 @@ function appendStepToAsyncChain(input: {
1240
1253
  };
1241
1254
  }
1242
1255
  const chain = [input.params.step];
1243
- const acceptanceErrors = validateExecutionAcceptance({ ...input.params, chain } as Parameters<typeof validateExecutionAcceptance>[0]);
1244
- if (acceptanceErrors.length > 0) {
1256
+ const validationParams = { ...input.params, chain } as SubagentParamsLike;
1257
+ const acceptancePolicyErrors = validateExecutionAcceptancePolicy(validationParams);
1258
+ const outputSchemaError = validateLaunchOutputSchemaOverrides(validationParams);
1259
+ const earlyErrors = [...acceptancePolicyErrors];
1260
+ if (outputSchemaError) earlyErrors.push(outputSchemaError);
1261
+ if (earlyErrors.length > 0) {
1245
1262
  return {
1246
- content: [{ type: "text", text: `Cannot append step: ${acceptanceErrors.join(" ")}` }],
1263
+ content: [{ type: "text", text: `Cannot append step: ${earlyErrors.join(" ")}` }],
1247
1264
  isError: true,
1248
1265
  details: { mode: "management", results: [] },
1249
1266
  };
@@ -1325,6 +1342,14 @@ function appendStepToAsyncChain(input: {
1325
1342
  const scope: AgentScope = resolveExecutionAgentScope(input.params.agentScope);
1326
1343
  const discoveredForAppend = input.deps.discoverAgents(input.requestCwd, scope, input.parentModel?.provider);
1327
1344
  const agents = discoveredForAppend.agents;
1345
+ const acceptanceErrors = validateExecutionAcceptance(projectEffectiveAcceptanceSchemas(validationParams, agents));
1346
+ if (acceptanceErrors.length > 0) {
1347
+ return {
1348
+ content: [{ type: "text", text: `Cannot append step: ${acceptanceErrors.join(" ")}` }],
1349
+ isError: true,
1350
+ details: { mode: "management", results: [] },
1351
+ };
1352
+ }
1328
1353
  const contextPolicy = resolveExplicitContextPolicy(input.params);
1329
1354
  const chainSkillInput = normalizeSkillInput(input.params.skill);
1330
1355
  const chainSkills = chainSkillInput === false ? [] : (chainSkillInput ?? []);
@@ -1705,7 +1730,7 @@ async function resumeExternalJobFollowUp(input: {
1705
1730
  ...input.baseAgentConfig,
1706
1731
  runner: { type: "external-job", provider: runner.provider, options: runner.options },
1707
1732
  };
1708
- const result = executeAsyncSingle(runId, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
1733
+ const result = await executeAsyncSingle(runId, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
1709
1734
  agent: input.target.agent,
1710
1735
  task: input.followUp,
1711
1736
  goal: input.followUp,
@@ -1802,13 +1827,13 @@ async function resumeAsyncRun(input: {
1802
1827
  details: { mode: "management", results: [] },
1803
1828
  };
1804
1829
  }
1805
- const acceptanceErrors = validateExecutionAcceptance(input.params as Parameters<typeof validateExecutionAcceptance>[0]);
1806
- if (acceptanceErrors.length > 0) {
1807
- return {
1808
- content: [{ type: "text", text: `Cannot resume: ${acceptanceErrors.join(" ")}` }],
1809
- isError: true,
1810
- details: { mode: "management", results: [] },
1811
- };
1830
+ const acceptancePolicyErrors = validateExecutionAcceptancePolicy(input.params);
1831
+ if (acceptancePolicyErrors.length > 0) {
1832
+ return { content: [{ type: "text", text: `Cannot resume: ${acceptancePolicyErrors.join(" ")}` }], isError: true, details: { mode: "management", results: [] } };
1833
+ }
1834
+ const outputSchemaError = validateLaunchOutputSchemaOverrides(input.params);
1835
+ if (outputSchemaError) {
1836
+ return { content: [{ type: "text", text: `Cannot resume: ${outputSchemaError}` }], isError: true, details: { mode: "management", results: [] } };
1812
1837
  }
1813
1838
  input.deps.state.currentSessionId = resolveCurrentSessionId(input.ctx.sessionManager);
1814
1839
 
@@ -1925,6 +1950,10 @@ async function resumeAsyncRun(input: {
1925
1950
  details: { mode: "chain", results: [] },
1926
1951
  };
1927
1952
  }
1953
+ const acceptanceErrors = validateExecutionAcceptance(projectEffectiveAcceptanceSchemas(input.params, agents));
1954
+ if (acceptanceErrors.length > 0) {
1955
+ return { content: [{ type: "text", text: `Cannot resume: ${acceptanceErrors.join(" ")}` }], isError: true, details: { mode: "chain", results: [] } };
1956
+ }
1928
1957
  const runId = randomUUID();
1929
1958
  const topLevelResume = depth === 0 && !inheritedNestedRoute(input.deps) && !input.params.workflowParentRunId;
1930
1959
  let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
@@ -1993,6 +2022,7 @@ async function resumeAsyncRun(input: {
1993
2022
  worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
1994
2023
  worktreeBaseDir: input.deps.config.worktreeBaseDir,
1995
2024
  baseRef: input.params.baseRef ?? target.recoveryDescriptor?.baseRef,
2025
+ ...(recoveryDescriptor?.requiredExtensions ? { requiredExtensions: recoveryDescriptor.requiredExtensions } : {}),
1996
2026
  worktreeProvider: input.deps.config.worktreeProvider,
1997
2027
  worktreeBranchPrefix: input.deps.config.worktreeBranchPrefix,
1998
2028
  controlConfig: resolveControlConfig(input.deps.config.control, input.params.control),
@@ -2032,6 +2062,23 @@ async function resumeAsyncRun(input: {
2032
2062
  if (input.params.baseRef !== undefined && "managedWorktree" in target && target.managedWorktree === true) {
2033
2063
  return { content: [{ type: "text", text: "Cannot resume with baseRef: retained managed-worktree children continue in their existing worktree. Start a new worktree run from that base ref instead." }], isError: true, details: { mode: "management", results: [] } };
2034
2064
  }
2065
+ const recoveryAgentConfig = recoveryDescriptor ? applySteeringRecoveryAgentConfig(baseAgentConfig, recoveryDescriptor) : baseAgentConfig;
2066
+ const agentConfig = intercomBridge.active ? applyIntercomBridgeToAgent(recoveryAgentConfig, intercomBridge) : recoveryAgentConfig;
2067
+ const foregroundContract = target.source === "foreground" ? target.resumeContract : undefined;
2068
+ const outputSchema = Object.hasOwn(input.params, "outputSchema")
2069
+ ? input.params.outputSchema
2070
+ : Object.hasOwn(foregroundContract ?? {}, "outputSchema")
2071
+ ? foregroundContract?.outputSchema
2072
+ : recoveryDescriptor?.structuredOutputSchema;
2073
+ const acceptance = input.params.acceptance !== undefined
2074
+ ? input.params.acceptance
2075
+ : foregroundContract?.acceptance !== undefined
2076
+ ? foregroundContract.acceptance
2077
+ : recoveryDescriptor?.acceptance;
2078
+ const acceptanceErrors = validateExecutionAcceptance({ ...input.params, acceptance, outputSchema });
2079
+ if (acceptanceErrors.length > 0) {
2080
+ return { content: [{ type: "text", text: `Cannot resume: ${acceptanceErrors.join(" ")}` }], isError: true, details: { mode: "management", results: [] } };
2081
+ }
2035
2082
  const runId = randomUUID();
2036
2083
  const topLevelResume = depth === 0 && !inheritedNestedRoute(input.deps) && !input.params.workflowParentRunId;
2037
2084
  let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
@@ -2055,17 +2102,13 @@ async function resumeAsyncRun(input: {
2055
2102
  if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "single", results: [], activeAsyncCapacity: error.snapshot } };
2056
2103
  return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "single", results: [] } };
2057
2104
  }
2058
- const recoveryAgentConfig = recoveryDescriptor ? applySteeringRecoveryAgentConfig(baseAgentConfig, recoveryDescriptor) : baseAgentConfig;
2059
- const agentConfig = intercomBridge.active ? applyIntercomBridgeToAgent(recoveryAgentConfig, intercomBridge) : recoveryAgentConfig;
2060
- const foregroundContract = target.source === "foreground" ? target.resumeContract : undefined;
2061
- const outputSchema = input.params.outputSchema ?? foregroundContract?.outputSchema ?? recoveryDescriptor?.structuredOutputSchema;
2062
2105
  const agentContract = input.params.agentContract ?? foregroundContract?.agentContract ?? recoveryDescriptor?.agentContract;
2063
2106
  const artifactConfig: ArtifactConfig = recoveryDescriptor?.artifactConfig ?? omitUndefinedProperties({ ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir });
2064
2107
  const artifactsDir = recoveryDescriptor?.artifactsDir ?? getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir);
2065
2108
  const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
2066
2109
  const parentModel = input.parentModel;
2067
2110
  const revivalAsyncDir = path.join(DIRS.async, runId);
2068
- const result = executeAsyncSingle(runId, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
2111
+ const result = await executeAsyncSingle(runId, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
2069
2112
  agent: target.agent,
2070
2113
  task: buildRevivedAsyncTask(target as Parameters<typeof buildRevivedAsyncTask>[0], effectiveFollowUp),
2071
2114
  goal: effectiveFollowUp,
@@ -2107,6 +2150,7 @@ async function resumeAsyncRun(input: {
2107
2150
  thinkingOverride: recoveryDescriptor?.thinking ?? target.thinking,
2108
2151
  thinkingCeiling: recoveryDescriptor?.thinkingCeiling ?? ("thinkingCeiling" in target ? target.thinkingCeiling : undefined),
2109
2152
  extensionBindings: recoveryDescriptor?.extensionBindings ?? ("extensionBindings" in target ? target.extensionBindings : undefined),
2153
+ requiredExtensions: recoveryDescriptor?.requiredExtensions ?? (target as { requiredExtensions?: SteeringRecoveryDescriptor["requiredExtensions"] }).requiredExtensions,
2110
2154
  outputBaseDir: resolveSingleRunOutputBaseDir(input.deps, artifactsDir, runId),
2111
2155
  maxSubagentDepth: recoveryDescriptor?.maxSubagentDepth ?? resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth, input.deps.childRuntime),
2112
2156
  waitToolEnabled: input.deps.waitToolEnabled,
@@ -2132,7 +2176,7 @@ async function resumeAsyncRun(input: {
2132
2176
  ...(agentContract ? { agentContract } : {}),
2133
2177
  ...(outputSchema ? { structuredOutputSchema: outputSchema } : {}),
2134
2178
  ...(recoveryDescriptor?.skills ? { skills: [...recoveryDescriptor.skills] } : {}),
2135
- ...(input.params.acceptance !== undefined ? { acceptance: input.params.acceptance } : foregroundContract?.acceptance !== undefined ? { acceptance: foregroundContract.acceptance } : recoveryDescriptor?.acceptance !== undefined ? { acceptance: recoveryDescriptor.acceptance } : {}),
2179
+ ...(acceptance !== undefined ? { acceptance } : {}),
2136
2180
  ...(input.params.timeoutMs !== undefined ? { timeoutMs: input.params.timeoutMs } : {}),
2137
2181
  ...(input.absoluteDeadlineAt !== undefined ? { absoluteDeadlineAt: input.absoluteDeadlineAt } : {}),
2138
2182
  ...(input.params.toolBudget !== undefined ? { toolBudget: input.params.toolBudget } : {}),
@@ -2181,7 +2225,7 @@ async function resumeAsyncRun(input: {
2181
2225
  } finally {
2182
2226
  input.signal?.removeEventListener("abort", stopOnAbort);
2183
2227
  }
2184
- fs.rmSync(resultPath, { force: true });
2228
+ if (completed.importedPublication) removeWorkflowAwaitedResult(asyncDir, resultPath, revivedId, completed.importedPublication);
2185
2229
  const usage = importedAsyncRootUsage(completed);
2186
2230
  const childResult: SingleResult = {
2187
2231
  index: 0,
@@ -2197,8 +2241,7 @@ async function resumeAsyncRun(input: {
2197
2241
  ...(completed.stopped ? { stopped: true } : {}),
2198
2242
  ...(completed.sessionFile ? { sessionFile: completed.sessionFile } : {}),
2199
2243
  ...(completed.model ? { model: completed.model } : {}),
2200
- ...(completed.attemptedModels ? { attemptedModels: completed.attemptedModels } : {}),
2201
- ...(completed.modelAttempts ? { modelAttempts: completed.modelAttempts } : {}),
2244
+ ...(completed.requestedModel ? { requestedModel: completed.requestedModel } : {}),
2202
2245
  ...(completed.contextOverflow ? { contextOverflow: true } : {}),
2203
2246
  ...(completed.structuredOutput !== undefined ? { structuredOutput: completed.structuredOutput } : {}),
2204
2247
  ...(completed.structuredOutputPath ? { structuredOutputPath: completed.structuredOutputPath } : {}),
@@ -2407,6 +2450,8 @@ function canonicalizeExecutionParams(params: SubagentParamsLike, agents: AgentCo
2407
2450
  params = omitUndefinedProperties({ ...params, agent: result.name });
2408
2451
  const agent = agents.find((candidate) => candidate.name === result.name);
2409
2452
  if (params.extensionBindings !== undefined && (agent?.runner?.type === "external-cli" || agent?.runner?.type === "external-job")) return { error: `extensionBindings is not supported for runner.type='${agent.runner.type}'.` };
2453
+ const machineError = agent ? formatHerdrMachineRunnerUnsupported({ machine: params.machine ?? agent.machine, agentName: agent.name, runnerType: agent.runner?.type, adapter: agent.runner?.type === "external-cli" ? agent.runner.adapter : undefined, worktree: params.worktree }) : undefined;
2454
+ if (machineError) return { error: machineError };
2410
2455
  }
2411
2456
  if (params.extensionBindings !== undefined) {
2412
2457
  try {
@@ -2459,6 +2504,18 @@ function canonicalizeExecutionParams(params: SubagentParamsLike, agents: AgentCo
2459
2504
  return { params };
2460
2505
  }
2461
2506
 
2507
+ function projectEffectiveAcceptanceSchemas(params: SubagentParamsLike, agents: AgentConfig[]): SubagentParamsLike {
2508
+ const withEffectiveSchema = <T extends { agent: string; outputSchema?: JsonSchemaObject | false }>(step: T): T => {
2509
+ const agent = agents.find((candidate) => candidate.name === step.agent);
2510
+ return agent ? { ...step, outputSchema: resolveEffectiveOutputSchema(agent, step.outputSchema) } : step;
2511
+ };
2512
+ return {
2513
+ ...params,
2514
+ ...(params.tasks ? { tasks: params.tasks.map(withEffectiveSchema) } : {}),
2515
+ ...(params.chain ? { chain: projectChainOutputSchemas(params.chain, agents) as ChainStep[] } : {}),
2516
+ };
2517
+ }
2518
+
2462
2519
  function validateExecutionInput(
2463
2520
  params: SubagentParamsLike,
2464
2521
  agents: AgentConfig[],
@@ -2481,7 +2538,7 @@ function validateExecutionInput(
2481
2538
  };
2482
2539
  }
2483
2540
 
2484
- const acceptanceErrors = validateExecutionAcceptance(params as Parameters<typeof validateExecutionAcceptance>[0]);
2541
+ const acceptanceErrors = validateExecutionAcceptance(projectEffectiveAcceptanceSchemas(params, agents));
2485
2542
  if (acceptanceErrors.length > 0) {
2486
2543
  return {
2487
2544
  content: [{ type: "text", text: acceptanceErrors.join(" ") }],
@@ -2733,6 +2790,7 @@ function applySingleAgentLaunchDefaults(params: SubagentParamsLike, agents: Agen
2733
2790
  const parentTimeoutMs = params.timeoutMs === undefined && params.maxRuntimeMs === undefined && agent.defaultTimeoutMs === undefined && params.workflowParentDeadlineAt !== undefined
2734
2791
  ? Math.max(1, params.workflowParentDeadlineAt - Date.now())
2735
2792
  : undefined;
2793
+ const outputSchema = params.outputSchema === false ? false : resolveEffectiveOutputSchema(agent, params.outputSchema);
2736
2794
  return {
2737
2795
  ...params,
2738
2796
  ...(params.async === undefined && agent.defaultAsync !== undefined ? { async: agent.defaultAsync } : {}),
@@ -2743,9 +2801,28 @@ function applySingleAgentLaunchDefaults(params: SubagentParamsLike, agents: Agen
2743
2801
  ...(params.acceptance === undefined && agent.defaultAcceptance !== undefined
2744
2802
  ? { acceptance: agent.defaultAcceptance }
2745
2803
  : {}),
2804
+ ...(outputSchema !== undefined ? { outputSchema } : {}),
2746
2805
  };
2747
2806
  }
2748
2807
 
2808
+ function validateLaunchOutputSchemaOverrides(params: SubagentParamsLike): string | undefined {
2809
+ const values: unknown[] = [params.outputSchema, ...(params.tasks ?? []).map((task) => task.outputSchema)];
2810
+ for (const step of params.chain ?? []) {
2811
+ if (isParallelStep(step)) values.push(...step.parallel.map((task) => task.outputSchema));
2812
+ else if (isDynamicParallelStep(step)) values.push(step.parallel.outputSchema);
2813
+ else values.push(step.outputSchema);
2814
+ }
2815
+ for (const value of values) {
2816
+ if (value === undefined || value === false) continue;
2817
+ try {
2818
+ assertJsonSchemaObject(value, "outputSchema");
2819
+ } catch (error) {
2820
+ return error instanceof Error ? error.message : String(error);
2821
+ }
2822
+ }
2823
+ return undefined;
2824
+ }
2825
+
2749
2826
  export const DEFAULT_FOREGROUND_TIMEOUT_MS = 30 * 60 * 1000;
2750
2827
 
2751
2828
  // Async single-agent runs also need a wall-clock backstop: a child whose bash
@@ -2909,11 +2986,20 @@ function withResolvedContext(
2909
2986
  }
2910
2987
 
2911
2988
  function withAggregatedToolUsage(result: AgentToolResult<Details>): AgentToolResult<Details> {
2912
- if (result.details.results.length === 0) return result;
2913
- const usage = sumResultsUsage(result.details.results);
2989
+ const highWarnings = result.details.results.flatMap((child) => child.watchdog?.warnings?.filter((warning) => warning.importance === "high") ?? []);
2990
+ const projected: AgentToolResult<Details> = {
2991
+ ...result,
2992
+ ...(highWarnings.length ? { content: [...result.content, { type: "text", text: ["High-importance watchdog findings:", ...highWarnings.map((warning) => `- ${warning.severity}: ${warning.summary}\n Evidence: ${warning.evidence}\n Recommended action: ${warning.recommendedAction}`)].join("\n") }] } : {}),
2993
+ details: {
2994
+ ...result.details,
2995
+ results: result.details.results.map((child) => ({ ...child, ...(child.watchdog ? { watchdog: childWatchdogProgressForModel(child.watchdog) } : {}) })),
2996
+ },
2997
+ };
2998
+ if (projected.details.results.length === 0) return projected;
2999
+ const usage = sumResultsUsage(projected.details.results);
2914
3000
  return usage.input !== 0 || usage.output !== 0 || usage.cacheRead !== 0 || usage.cacheWrite !== 0 || usage.cost !== 0 || usage.turns !== 0
2915
- ? { ...result, usage: toAgentToolUsage(usage) }
2916
- : result;
3001
+ ? { ...projected, usage: toAgentToolUsage(usage) }
3002
+ : projected;
2917
3003
  }
2918
3004
 
2919
3005
  function toExecutionErrorResult(params: SubagentParamsLike, error: unknown, contextSummary?: ContextSummary): AgentToolResult<Details> {
@@ -3124,6 +3210,15 @@ function importedAsyncRootUsage(completed: Awaited<ReturnType<typeof waitForImpo
3124
3210
  };
3125
3211
  }
3126
3212
 
3213
+ function removeWorkflowAwaitedResult(asyncDir: string, resultPath: string, runId: string, publication: NonNullable<Awaited<ReturnType<typeof waitForImportedAsyncRoot>>["importedPublication"]>): void {
3214
+ try {
3215
+ fs.rmSync(resultPath, { force: true });
3216
+ } catch {
3217
+ // Payload cleanup must not replace an already imported delivery.
3218
+ }
3219
+ removeResultIndex(asyncDir, publication.sessionId, runId, publication.toolCallId);
3220
+ }
3221
+
3127
3222
  async function waitForWorkflowAsyncSingleResult(
3128
3223
  params: SubagentParamsLike,
3129
3224
  launchResult: AgentToolResult<Details>,
@@ -3144,7 +3239,7 @@ async function waitForWorkflowAsyncSingleResult(
3144
3239
  } finally {
3145
3240
  options.signal?.removeEventListener("abort", stopOnAbort);
3146
3241
  }
3147
- fs.rmSync(resultPath, { force: true });
3242
+ if (completed.importedPublication) removeWorkflowAwaitedResult(asyncDir, resultPath, options.runId, completed.importedPublication);
3148
3243
  const usage = importedAsyncRootUsage(completed);
3149
3244
  const childResult: SingleResult = omitUndefinedProperties({
3150
3245
  index: 0,
@@ -3160,8 +3255,7 @@ async function waitForWorkflowAsyncSingleResult(
3160
3255
  ...(completed.stopped ? { stopped: true } : {}),
3161
3256
  ...(completed.sessionFile ? { sessionFile: completed.sessionFile } : {}),
3162
3257
  ...(completed.model ? { model: completed.model } : {}),
3163
- ...(completed.attemptedModels ? { attemptedModels: completed.attemptedModels } : {}),
3164
- ...(completed.modelAttempts ? { modelAttempts: completed.modelAttempts } : {}),
3258
+ ...(completed.requestedModel ? { requestedModel: completed.requestedModel } : {}),
3165
3259
  ...(completed.contextOverflow ? { contextOverflow: true } : {}),
3166
3260
  ...(completed.structuredOutput !== undefined ? { structuredOutput: completed.structuredOutput } : {}),
3167
3261
  ...(completed.structuredOutputPath ? { structuredOutputPath: completed.structuredOutputPath } : {}),
@@ -3275,7 +3369,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3275
3369
  const modelOverrideFromParent = modelOrigin === "inherited";
3276
3370
  const launchRuleError = applyWatchdogLaunchRules({ cwd: effectiveCwd, agent: a.name, model: modelOverride ?? (parentModel && `${parentModel.provider}/${parentModel.id}`), warn: (violation) => deps.watchdog?.displayRuleWarning(violation) });
3277
3371
  if (launchRuleError) return toExecutionErrorResult(params, new Error(launchRuleError), data.contextPolicy.contextSummary);
3278
- const asyncResult = executeAsyncSingle(id, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
3372
+ const asyncResult = await executeAsyncSingle(id, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
3279
3373
  agent: params.agent!,
3280
3374
  task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
3281
3375
  goal: params.task ?? "",
@@ -3285,6 +3379,8 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3285
3379
  availableModels,
3286
3380
  cwd: effectiveCwd,
3287
3381
  requestedCwd: data.requestedCwd,
3382
+ machine: params.machine,
3383
+ machineCwd: params.machineCwd,
3288
3384
  maxOutput: params.maxOutput,
3289
3385
  artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
3290
3386
  artifactConfig,
@@ -3321,7 +3417,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3321
3417
  childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(agent, index) : undefined,
3322
3418
  nestedRoute,
3323
3419
  agentContract: params.agentContract,
3324
- structuredOutputSchema: params.outputSchema,
3420
+ structuredOutputSchema: params.outputSchema || undefined,
3325
3421
  extensionBindings: params.extensionBindings,
3326
3422
  acceptance: params.acceptance,
3327
3423
  timeoutMs: data.timeoutMs,
@@ -3329,6 +3425,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3329
3425
  usageBudget: data.usageBudget,
3330
3426
  configToolBudget: data.configToolBudget,
3331
3427
  toolTimeoutMs: data.params?.toolTimeoutMs,
3428
+ checkpointBeforeDeadlineMs: data.params?.checkpointBeforeDeadlineMs ?? deps.config.checkpointBeforeDeadlineMs,
3332
3429
  configToolTimeoutMs: data.configToolTimeoutMs,
3333
3430
  capabilityCeiling: data.capabilityCeiling,
3334
3431
  runFanoutBudget: data.runFanoutBudget,
@@ -3447,6 +3544,23 @@ function appendWorkflowOutputWarning(text: string, warning: string | undefined):
3447
3544
  return warning ? `${text}\n\n${warning}` : text;
3448
3545
  }
3449
3546
 
3547
+ export function resolveWorkflowChildLocalCwd(input: {
3548
+ workflowCwd: string;
3549
+ discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
3550
+ agents: AgentConfig[];
3551
+ workflowAgentScope?: unknown;
3552
+ params: Record<string, unknown>;
3553
+ }): string {
3554
+ if (input.params.machine !== undefined) return input.workflowCwd;
3555
+ if (typeof input.params.agent === "string") {
3556
+ const agentScope = resolveExecutionAgentScope(input.params.agentScope ?? input.workflowAgentScope);
3557
+ const workflowAgents = input.discoverAgents(input.workflowCwd, agentScope).agents;
3558
+ const agent = resolveAgentName(input.params.agent, workflowAgents).agent ?? resolveAgentName(input.params.agent, input.agents).agent;
3559
+ if (agent?.machine !== undefined) return input.workflowCwd;
3560
+ }
3561
+ return typeof input.params.cwd === "string" ? resolveChildCwd(input.workflowCwd, input.params.cwd) : input.workflowCwd;
3562
+ }
3563
+
3450
3564
  function resolveWorkflowChildOutputPath(input: {
3451
3565
  ctxCwd: string;
3452
3566
  workflowCwd: string;
@@ -3472,7 +3586,7 @@ function resolveWorkflowChildOutputPath(input: {
3472
3586
  }, input.state);
3473
3587
  return { path: "recoveryDescriptor" in target ? target.recoveryDescriptor?.outputPath : undefined, inherited: false };
3474
3588
  }
3475
- const childCwd = typeof input.params.cwd === "string" ? resolveChildCwd(input.workflowCwd, input.params.cwd) : input.workflowCwd;
3589
+ const childCwd = resolveWorkflowChildLocalCwd(input);
3476
3590
  let agentOutput: string | undefined;
3477
3591
  if (rawOutput === true || rawOutput === "true" || (!hasExplicitOutput && !input.aggregateOutputPath)) {
3478
3592
  const agentScope = resolveExecutionAgentScope(input.params.agentScope ?? input.workflowAgentScope);
@@ -3574,7 +3688,7 @@ function prepareWorkflowChildLaunchParams(input: {
3574
3688
  const resolvedOutput = resolveWorkflowChildOutputPath({ ctxCwd: input.ctxCwd, workflowCwd: input.workflowCwd, artifactsDir: input.artifactsDir, workflowRunId: input.parentWorkflowRunId, aggregateOutputPath: input.aggregateOutputPath, configuredOutputBaseDir: input.configuredOutputBaseDir, discoverAgents: input.discoverAgents, agents: input.agents, workflowAgentScope: input.workflowAgentScope, key: input.workflowKey, params: input.childParams });
3575
3689
  if (resolvedOutput.path) childParams = { ...input.childParams, output: resolvedOutput.path };
3576
3690
  }
3577
- const childCwd = typeof childParams.cwd === "string" ? resolveChildCwd(input.workflowCwd, childParams.cwd) : input.workflowCwd;
3691
+ const childCwd = resolveWorkflowChildLocalCwd({ ...input, params: childParams });
3578
3692
  const agentScope = resolveExecutionAgentScope(childParams.agentScope ?? input.workflowAgentScope);
3579
3693
  const discoveredAgents = input.discoverAgents(childCwd, agentScope).agents;
3580
3694
  const agent = typeof childParams.agent === "string"
@@ -3706,6 +3820,12 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3706
3820
  details: { mode: "single", results: [] },
3707
3821
  };
3708
3822
  }
3823
+ let foregroundMachine: import("../../shared/types.ts").HerdrMachineReference | undefined;
3824
+ const requestedMachine = params.machine ?? agentConfig.machine;
3825
+ if (requestedMachine) {
3826
+ try { foregroundMachine = resolveHerdrMachinePlacement({ machine: requestedMachine, cwd: ctx.cwd, stepCwd: params.machineCwd }).machine; }
3827
+ catch (error) { return toExecutionErrorResult(params, error instanceof Error ? error : new Error(String(error)), data.contextPolicy.contextSummary); }
3828
+ }
3709
3829
  const effectiveToolBudget = resolveEffectiveToolBudget(omitUndefinedProperties({ runBudget: data.toolBudget, agentBudget: agentConfig.toolBudget, configBudget: data.configToolBudget }));
3710
3830
  if (effectiveToolBudget.error) return toExecutionErrorResult(params, new Error(effectiveToolBudget.error), data.contextPolicy.contextSummary);
3711
3831
 
@@ -3743,7 +3863,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3743
3863
  const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, agentConfig.maxSubagentDepth);
3744
3864
 
3745
3865
 
3746
- const sourceCwd = effectiveCwd;
3866
+ const sourceCwd = foregroundMachine?.cwd ?? effectiveCwd;
3747
3867
  let pendingHandoff: Details["parallelHandoff"];
3748
3868
  const { setup: worktreeSetup, errorResult: worktreeSetupError } = params.worktree ? await createSingleWorktreeSetup(
3749
3869
  sourceCwd,
@@ -3820,7 +3940,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3820
3940
  // Reads: caller override > agent defaultReads > none. `~`/`~/` expand to home;
3821
3941
  // absolute paths pass through; relative paths resolve against the child cwd.
3822
3942
  const reads = readsOverride !== undefined ? readsOverride : agentConfig.defaultReads ?? false;
3823
- const readPaths = Array.isArray(reads) ? resolveExistingReadPaths(reads, singleCwd) : [];
3943
+ const readPaths = !foregroundMachine && Array.isArray(reads) ? resolveExistingReadPaths(reads, singleCwd) : [];
3824
3944
  const readsInstruction = readPaths.length > 0
3825
3945
  ? `[Read from: ${readPaths.join(", ")}]\n\n`
3826
3946
  : "";
@@ -3883,6 +4003,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3883
4003
  : undefined;
3884
4004
 
3885
4005
  const deadlineAt = data.deadlineAt ?? (data.timeoutMs !== undefined ? Date.now() + data.timeoutMs : undefined);
4006
+ const requiredExtensions = deps.childRuntime?.requiredExtensions ?? resolveRequiredChildExtensions(data.parentPiSessionId);
3886
4007
  let r: Awaited<ReturnType<typeof runSync>> | undefined;
3887
4008
  let resolveDetachedWorkflowChild: ((result: Awaited<ReturnType<typeof runSync>>) => void) | undefined;
3888
4009
  const detachedWorkflowChild = params.workflowAwaitDetached === true
@@ -3890,10 +4011,14 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3890
4011
  : undefined;
3891
4012
  try {
3892
4013
  const launched = await runSync(ctx.cwd, agents, params.agent!, task, compactOptional<Parameters<typeof runSync>[4]>({
4014
+ machine: foregroundMachine,
4015
+ parentProviderRegistry: ctx.modelRegistry,
4016
+ remoteReads: foregroundMachine ? readsOverride : undefined,
3893
4017
  permissions: deps.config.permissions,
3894
4018
  runtimeSnapshotHost: deps.pi,
3895
4019
  hostAvailableBuiltins: getHostBuiltinToolNames(deps.pi),
3896
4020
  parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
4021
+ requiredExtensions,
3897
4022
  llmIntentArbiter: createTaskMutationArbiter({ model: ctx.model, modelRegistry: ctx.modelRegistry, sessionId: ctx.sessionManager.getSessionId() }),
3898
4023
  childRuntime: deps.childRuntime,
3899
4024
  onChildSession: (controls) => { childSessionControls = controls; },
@@ -4054,7 +4179,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
4054
4179
  usageBudget: usageBudgetState(data.usageBudget, totalCost),
4055
4180
  ...(worktreeHandoff?.reference ? { parallelHandoff: worktreeHandoff.reference } : {}),
4056
4181
  }));
4057
- rememberForegroundRun(deps.state, { modelResponseAliases, runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, results: details.results, params, effectiveOutput, effectiveOutputMode, extensionBindings: params.extensionBindings });
4182
+ rememberForegroundRun(deps.state, { modelResponseAliases, runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, results: details.results, params, effectiveOutput, effectiveOutputMode, extensionBindings: params.extensionBindings, requiredExtensions });
4058
4183
 
4059
4184
  const suppressRoutineResultIntercom = shouldSuppressRoutineResultIntercom({ suppressRoutineResultIntercom: params.suppressRoutineResultIntercom, results: [r] });
4060
4185
  if (!r.detached && !r.interrupted && !suppressRoutineResultIntercom) {
@@ -4456,8 +4581,9 @@ function terminalWorkflowReceipt(
4456
4581
  terminalOutcome?: WorkflowTerminalOutcome,
4457
4582
  hostSteps?: WorkflowReceipt["hostSteps"],
4458
4583
  resource?: WorkflowReceipt["resource"],
4584
+ argsDigest?: string,
4459
4585
  ): WorkflowReceipt {
4460
- return buildWorkflowReceipt({ workflowRunId, state, children, workflowChildren, terminalOutcome, hostSteps, resource });
4586
+ return buildWorkflowReceipt({ workflowRunId, state, children, workflowChildren, terminalOutcome, hostSteps, resource, ...(argsDigest ? { argsDigest } : {}) });
4461
4587
  }
4462
4588
 
4463
4589
  function workflowFailureTerminalOutcome(error: unknown, _children: WorkflowScriptChildResult[], usageBudget: ReturnType<typeof usageBudgetState>): WorkflowTerminalOutcome | undefined {
@@ -4603,7 +4729,7 @@ export function prepareWorkflowLaunchParams(
4603
4729
  const worktree = childParams.worktree ?? workflowDefaults.worktree;
4604
4730
  const baseRef = Object.hasOwn(childParams, "baseRef") ? childParams.baseRef : undefined;
4605
4731
  const outputSchema = Object.hasOwn(childParams, "outputSchema") ? childParams.outputSchema : workflowDefaults.outputSchema;
4606
- if (outputSchema !== undefined) assertJsonSchemaObject(outputSchema, "outputSchema");
4732
+ if (outputSchema !== undefined && outputSchema !== false) assertJsonSchemaObject(outputSchema, "outputSchema");
4607
4733
  const agentContract = Object.hasOwn(childParams, "agentContract") ? childParams.agentContract : workflowDefaults.agentContract;
4608
4734
  if (agentContract !== undefined && !isAgentContract(agentContract as AgentContract)) throw new Error("agentContract must be { version: 1 }.");
4609
4735
  const acceptance = Object.hasOwn(childParams, "acceptance") ? childParams.acceptance : workflowDefaults.acceptance;
@@ -4623,7 +4749,7 @@ export function prepareWorkflowLaunchParams(
4623
4749
  ...(lane ? { lane } : {}),
4624
4750
  ...(worktree !== undefined ? { worktree: worktree as boolean } : {}),
4625
4751
  ...(baseRef !== undefined ? { baseRef: baseRef as string } : {}),
4626
- ...(outputSchema !== undefined ? { outputSchema: outputSchema as JsonSchemaObject } : {}),
4752
+ ...(outputSchema !== undefined ? { outputSchema } : {}),
4627
4753
  ...(agentContract !== undefined ? { agentContract: agentContract as AgentContract } : {}),
4628
4754
  ...(acceptance !== undefined ? { acceptance: acceptance as AcceptanceInput } : {}),
4629
4755
  ...(output !== undefined ? { output: output as string | boolean } : {}),
@@ -4947,6 +5073,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4947
5073
  workflowResource = { permit: workflowResourcePermit, ...consumed };
4948
5074
  }
4949
5075
  if (requestParams.workflowScript !== undefined && normalizedAction === undefined) {
5076
+ const workflowArgsEvidence = requestParams.args === undefined ? undefined : { args: requestParams.args, argsDigest: stableJsonDigest(requestParams.args) };
5077
+ const workflowArgs = workflowArgsEvidence?.args;
5078
+ const workflowArgsDigest = workflowArgsEvidence?.argsDigest;
4950
5079
  const spawnBudgetValidation = validateWorkflowScript(requestParams.workflowScript, {
4951
5080
  maxSubagentSpawnsPerRun: requestParams.maxSubagentSpawnsPerRun ?? resolveMaxSubagentSpawnsPerRun(deps.config.maxSubagentSpawnsPerRun),
4952
5081
  });
@@ -5114,7 +5243,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5114
5243
  pid: process.pid,
5115
5244
  steps: [],
5116
5245
  ...(workflowPreflight ? { preflight: workflowPreflight } : {}),
5117
- workflow: { trace: [], emits: [], console: [], ...(workflowResource ? { resource: workflowResource.provenance } : {}) },
5246
+ workflow: { trace: [], emits: [], console: [], ...(workflowArgsEvidence ?? {}), ...(workflowResource ? { resource: workflowResource.provenance } : {}) },
5118
5247
  workflowChildren: workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState: "running", inventoryComplete: false }),
5119
5248
  runFanoutBudget: getRunFanoutBudgetSnapshot(workflowFanoutBudget),
5120
5249
  };
@@ -5144,10 +5273,37 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5144
5273
  };
5145
5274
  let pendingResultPublication: Promise<boolean> | undefined;
5146
5275
  let settleResultPublication: ((published: boolean) => void) | undefined;
5147
- const reportResultWriteFailure = (error: unknown): void => {
5276
+ let resultWriteFailureWakeDelivered = false;
5277
+ const reportResultWriteFailure = (error: unknown): boolean => {
5148
5278
  const message = `Failed to write async workflow result ${resultPath}: ${error instanceof Error ? error.message : String(error)}`;
5149
5279
  console.error(message, error);
5280
+ try {
5281
+ if (currentSessionId && fallbackResultPayloadPathForSessionRun(path.dirname(resultPath), currentSessionId, workflowRunId)) return true;
5282
+ } catch (pendingError) {
5283
+ console.error(`Failed to verify pending async workflow result '${workflowRunId}':`, pendingError);
5284
+ }
5150
5285
  appendWorkflowEvent({ type: "subagent.workflow.result_write_failed", error: message });
5286
+ // A missing result file means the result watcher will never deliver the
5287
+ // terminal completion wake. Intermediate child notices are context-only
5288
+ // once their turns are suppressed, so surface the failure as its own
5289
+ // actionable wake instead of leaving the parent asleep.
5290
+ if (resultWriteFailureWakeDelivered) return false;
5291
+ if (deps.state.currentSessionId !== currentSessionId || deps.state.completionOwnerId !== completionOwnerId) return false;
5292
+ try {
5293
+ deps.pi.sendMessage(
5294
+ {
5295
+ customType: "subagent-workflow-result-write-failed",
5296
+ content: message,
5297
+ display: true,
5298
+ },
5299
+ { triggerTurn: true },
5300
+ );
5301
+ resultWriteFailureWakeDelivered = true;
5302
+ } catch (sendError) {
5303
+ console.error(`Failed to send workflow result write failure notification for '${workflowRunId}':`, sendError);
5304
+ }
5305
+ deps.refreshResultDelivery?.();
5306
+ return false;
5151
5307
  };
5152
5308
  const runPersistence = createCapacityResilientJsonWriter({
5153
5309
  keepAlive: true,
@@ -5156,7 +5312,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5156
5312
  if (filePath === resultPath) settleResultPublication?.(true);
5157
5313
  },
5158
5314
  onError: (error, filePath) => {
5159
- if (filePath === resultPath) { reportResultWriteFailure(error); settleResultPublication?.(false); }
5315
+ if (filePath === resultPath) settleResultPublication?.(reportResultWriteFailure(error));
5160
5316
  else console.error(`Failed to persist async workflow state '${filePath}':`, error);
5161
5317
  },
5162
5318
  write: (filePath, payload) => filePath === resultPath
@@ -5237,8 +5393,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5237
5393
  if (!resultPublished) pendingResultPublication = new Promise<boolean>((resolve) => { settleResultPublication = resolve; });
5238
5394
  return true;
5239
5395
  } catch (error) {
5240
- reportResultWriteFailure(error);
5241
- return false;
5396
+ return reportResultWriteFailure(error);
5242
5397
  }
5243
5398
  };
5244
5399
  const projectWorkflowActivity = () => {
@@ -5276,7 +5431,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5276
5431
  indexPersistence.dispose();
5277
5432
  return { content: [{ type: "text", text: `Failed to create async workflow storage: ${error instanceof Error ? error.message : String(error)}` }], isError: true, details: { mode: "workflow", results: [] } };
5278
5433
  }
5279
- appendWorkflowEvent({ type: "subagent.workflow.started" });
5434
+ appendWorkflowEvent({ type: "subagent.workflow.started", ...(workflowArgsDigest ? { argsDigest: workflowArgsDigest } : {}) });
5280
5435
  // The runner does not execute workflow scripts: this active owner must consume its own
5281
5436
  // durable steer inbox, deliver through in-process child controls, and persist receipts.
5282
5437
  const emitWorkflowSteerEvent = (type: string, requestId: string, index?: number, extra: Record<string, unknown> = {}): void => {
@@ -5423,7 +5578,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5423
5578
  const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
5424
5579
  const workflowResults: SingleResult[] = [];
5425
5580
  const workflowChildRunIds = new Map<string, string>();
5426
- const { action: _action, agent: _agent, task: _task, resume: _resume, tasks: _tasks, chain: _chain, concurrency: _concurrency, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, missionId: _missionId, mission: _mission, preflight: _preflight, globalConcurrencyLimit: _globalConcurrencyLimit, maxSubagentSpawnsPerRun: _maxSubagentSpawnsPerRun, ...workflowChildDefaults } = workflowRequest;
5581
+ const { args: _args, action: _action, agent: _agent, task: _task, resume: _resume, tasks: _tasks, chain: _chain, concurrency: _concurrency, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, missionId: _missionId, mission: _mission, preflight: _preflight, globalConcurrencyLimit: _globalConcurrencyLimit, maxSubagentSpawnsPerRun: _maxSubagentSpawnsPerRun, ...workflowChildDefaults } = workflowRequest;
5427
5582
  const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
5428
5583
  const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
5429
5584
  const workflowAggregateOutputPath = resolveSingleOutputPath(workflowOutput, parentCwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, workflowRunId));
@@ -5548,7 +5703,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5548
5703
  try {
5549
5704
  const workflow = await runWorkflowScript({
5550
5705
  script: workflowScript,
5706
+ ...(workflowArgs ? { args: workflowArgs } : {}),
5551
5707
  workflowRunId,
5708
+ processCwd: ctx.cwd,
5552
5709
  globalConcurrencyLimit: requestParams.globalConcurrencyLimit ?? deps.config.globalConcurrencyLimit,
5553
5710
  timeoutMs: timeout,
5554
5711
  signal: controller.signal,
@@ -5579,7 +5736,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5579
5736
  content: formatIncrementalChildCompletion(notification),
5580
5737
  display: notification.outcome !== "completed",
5581
5738
  },
5582
- { triggerTurn: scheduledCompletionTriggersTurn(requestParams.scheduleOrigin, notification.outcome) },
5739
+ { triggerTurn: incrementalChildCompletionTriggersTurn(notification, requestParams.scheduleOrigin) },
5583
5740
  );
5584
5741
  } catch (sendError) {
5585
5742
  console.error(`Failed to send incremental child completion notification for '${notification.childKey}':`, sendError);
@@ -5719,8 +5876,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5719
5876
  const workflowUsage = sumResultsUsage(workflowResults);
5720
5877
  settleWorkflowSteerInbox("complete");
5721
5878
  const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState: "completed", inventoryComplete: true, trace: workflow.trace, children: workflow.children, steps: status.steps });
5722
- status = { ...status, state: "complete", endedAt: Date.now(), workflow: { value: workflow.value, trace: finalPreflightTrace, emits: workflow.emits, console: workflow.console, ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}) }, workflowChildren, totalTokens: { input: workflowUsage.input, output: workflowUsage.output, total: workflowUsage.input + workflowUsage.output }, totalCost: sumResultsCost(workflowResults) };
5723
- const receipt = terminalWorkflowReceipt(workflowRunId, "complete", workflow.children, workflowChildren, undefined, validHostStepNodes(status.workflowGraph), workflowResource?.provenance);
5879
+ status = { ...status, state: "complete", endedAt: Date.now(), workflow: { value: workflow.value, trace: finalPreflightTrace, emits: workflow.emits, console: workflow.console, ...(workflowArgsEvidence ?? {}), ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}) }, workflowChildren, totalTokens: { input: workflowUsage.input, output: workflowUsage.output, total: workflowUsage.input + workflowUsage.output }, totalCost: sumResultsCost(workflowResults) };
5880
+ const receipt = terminalWorkflowReceipt(workflowRunId, "complete", workflow.children, workflowChildren, undefined, validHostStepNodes(status.workflowGraph), workflowResource?.provenance, workflowArgsDigest);
5724
5881
  delete status.workflowReceiptPath;
5725
5882
  let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
5726
5883
  try {
@@ -5734,7 +5891,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5734
5891
  persist();
5735
5892
  deps.refreshResultDelivery?.();
5736
5893
  persistClosed = true;
5737
- appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(status.error ? { error: status.error } : {}) });
5894
+ appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(workflowArgsDigest ? { argsDigest: workflowArgsDigest } : {}), ...(status.error ? { error: status.error } : {}) });
5738
5895
  } catch (error) {
5739
5896
  const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
5740
5897
  const stopped = controller.signal.aborted;
@@ -5758,7 +5915,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5758
5915
  const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState, inventoryComplete: true, trace: partial.trace, children: partial.children, steps: status.steps });
5759
5916
  const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, partial.trace, { settled: true });
5760
5917
  const finalPreflightTrace = annotateWorkflowPreflightTrace(partial.trace, workflowPreflight);
5761
- status = compactOptional<AsyncStatus>({ ...status, state, stopped: stopped || undefined, activityState: pauseForDetached || status.activityState === "needs_attention" ? "needs_attention" : undefined, error: workflowFailureMessage(error, workflowRunId, partial.children), endedAt: Date.now(), workflow: { trace: finalPreflightTrace, emits: partial.emits, console: partial.console, ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}) }, workflowChildren });
5918
+ status = compactOptional<AsyncStatus>({ ...status, state, stopped: stopped || undefined, activityState: pauseForDetached || status.activityState === "needs_attention" ? "needs_attention" : undefined, error: workflowFailureMessage(error, workflowRunId, partial.children), endedAt: Date.now(), workflow: { trace: finalPreflightTrace, emits: partial.emits, console: partial.console, ...(workflowArgsEvidence ?? {}), ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}) }, workflowChildren });
5762
5919
  if (pauseForDetached) {
5763
5920
  const promoted = promotePausedWorkflowIfSettled(status);
5764
5921
  if (promoted) status = promoted;
@@ -5770,7 +5927,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5770
5927
  const resultSummary = appendWorkflowOutputWarning(terminalSummary, outputWarning);
5771
5928
  const receiptState: WorkflowReceiptState = status.state === "complete" ? "complete" : status.state === "paused" ? "paused" : status.state === "stopped" ? "stopped" : "failed";
5772
5929
  const terminalOutcome = workflowFailureTerminalOutcome(error, partial.children, usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)));
5773
- const receipt = terminalWorkflowReceipt(workflowRunId, receiptState, partial.children, workflowChildren, terminalOutcome, validHostStepNodes(status.workflowGraph), workflowResource?.provenance);
5930
+ const receipt = terminalWorkflowReceipt(workflowRunId, receiptState, partial.children, workflowChildren, terminalOutcome, validHostStepNodes(status.workflowGraph), workflowResource?.provenance, workflowArgsDigest);
5774
5931
  delete status.workflowReceiptPath;
5775
5932
  let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
5776
5933
  try {
@@ -5784,7 +5941,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5784
5941
  persist();
5785
5942
  deps.refreshResultDelivery?.();
5786
5943
  persistClosed = true;
5787
- appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(terminalOutcome ? { terminalOutcome } : {}), ...(status.error ? { error: status.error } : {}), ...(status.activityState ? { activityState: status.activityState } : {}) });
5944
+ appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(workflowArgsDigest ? { argsDigest: workflowArgsDigest } : {}), ...(terminalOutcome ? { terminalOutcome } : {}), ...(status.error ? { error: status.error } : {}), ...(status.activityState ? { activityState: status.activityState } : {}) });
5788
5945
  } finally {
5789
5946
  // Idempotent cleanup only: a failed result/index write must not authorize terminal status.
5790
5947
  try {
@@ -5804,7 +5961,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5804
5961
  details: { mode: "workflow", runId: workflowRunId, toolCallId, asyncId: workflowRunId, asyncDir, results: [], ...(workflowPreflight ? { preflight: workflowPreflight } : {}), workflow: status.workflow, workflowChildren: status.workflowChildren, chatProgress, ...(deps.state.activeAsyncCapacity ? { activeAsyncCapacity: deps.state.activeAsyncCapacity } : {}) },
5805
5962
  }, workflowFanoutBudget));
5806
5963
  }
5807
- const { workflowScript: _workflowScript, action: _action, agent: _agent, task: _task, resume: _resume, tasks: _tasks, chain: _chain, concurrency: _concurrency, async: _async, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, chatProgress: _chatProgress, missionId: _missionId, mission: _mission, preflight: _preflight, globalConcurrencyLimit: _globalConcurrencyLimit, maxSubagentSpawnsPerRun: _maxSubagentSpawnsPerRun, ...workflowChildDefaults } = requestParams;
5964
+ const { workflowScript: _workflowScript, args: _args, action: _action, agent: _agent, task: _task, resume: _resume, tasks: _tasks, chain: _chain, concurrency: _concurrency, async: _async, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, chatProgress: _chatProgress, missionId: _missionId, mission: _mission, preflight: _preflight, globalConcurrencyLimit: _globalConcurrencyLimit, maxSubagentSpawnsPerRun: _maxSubagentSpawnsPerRun, ...workflowChildDefaults } = requestParams;
5808
5965
  const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
5809
5966
  const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
5810
5967
  const workflowAggregateOutputPath = resolveSingleOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, foregroundWorkflowRunId));
@@ -5846,7 +6003,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5846
6003
  try {
5847
6004
  const workflow = await runWorkflowScript({
5848
6005
  script: requestParams.workflowScript,
6006
+ ...(workflowArgs ? { args: workflowArgs } : {}),
5849
6007
  workflowRunId: foregroundWorkflowRunId,
6008
+ processCwd: ctx.cwd,
5850
6009
  ...(delegatedWorkflowPermit ? { oneUsePermit: { claim: (key: string) => claimWorkflowChildPermit(delegatedWorkflowPermit, foregroundWorkflowRunId, key) } } : {}),
5851
6010
  globalConcurrencyLimit: requestParams.globalConcurrencyLimit ?? deps.config.globalConcurrencyLimit,
5852
6011
  timeoutMs: timeout,
@@ -5962,7 +6121,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5962
6121
  const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, workflow.trace, { settled: true });
5963
6122
  const finalPreflightTrace = annotateWorkflowPreflightTrace(workflow.trace, workflowPreflight);
5964
6123
  const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: foregroundWorkflowRunId, workflowState: "completed", inventoryComplete: true, trace: workflow.trace, children: workflow.children });
5965
- const receipt = terminalWorkflowReceipt(foregroundWorkflowRunId, "complete", workflow.children, workflowChildren, undefined, [...workflowHostSteps.values()], workflowResource?.provenance);
6124
+ const receipt = terminalWorkflowReceipt(foregroundWorkflowRunId, "complete", workflow.children, workflowChildren, undefined, [...workflowHostSteps.values()], workflowResource?.provenance, workflowArgsDigest);
5966
6125
  const traceLines = finalPreflightTrace.map((entry) => `- ${entry.operation} ${entry.key}: ${entry.state}${entry.runId ? ` (${entry.runId})` : ""}${entry.durationMs !== undefined ? ` in ${entry.durationMs}ms` : ""}${entry.warning ? ` — ${entry.warning}` : ""}${entry.error ? ` — ${entry.error}` : ""}`);
5967
6126
  const sections = [
5968
6127
  ...(workflowPreflight ? [formatWorkflowPreflight(workflowPreflight)] : []),
@@ -5980,7 +6139,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5980
6139
  const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
5981
6140
  return attachWorkflowMission(withRunFanoutBudget({
5982
6141
  content: [{ type: "text", text: displayText }],
5983
- details: compactOptional<Details>({ mode: "workflow", runId: foregroundWorkflowRunId, results: workflowDetailsResults(workflow.children), ...(workflowPreflight ? { preflight: workflowPreflight } : {}), workflowChildren, totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { value: workflow.value, trace: finalPreflightTrace, emits: workflow.emits, console: workflow.console, ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}), receipt }, chatProgress }),
6142
+ details: compactOptional<Details>({ mode: "workflow", runId: foregroundWorkflowRunId, results: workflowDetailsResults(workflow.children), ...(workflowPreflight ? { preflight: workflowPreflight } : {}), workflowChildren, totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { value: workflow.value, trace: finalPreflightTrace, emits: workflow.emits, console: workflow.console, ...(workflowArgsEvidence ?? {}), ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}), receipt }, chatProgress }),
5984
6143
  }, workflowFanoutBudget));
5985
6144
  } catch (error) {
5986
6145
  const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
@@ -6003,11 +6162,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6003
6162
  const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
6004
6163
  const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: foregroundWorkflowRunId, workflowState: "failed", inventoryComplete: true, trace: partial.trace, children: partial.children });
6005
6164
  const terminalOutcome = workflowFailureTerminalOutcome(error, partial.children, usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)));
6006
- const receipt = terminalWorkflowReceipt(foregroundWorkflowRunId, "failed", partial.children, workflowChildren, terminalOutcome, [...workflowHostSteps.values()], workflowResource?.provenance);
6165
+ const receipt = terminalWorkflowReceipt(foregroundWorkflowRunId, "failed", partial.children, workflowChildren, terminalOutcome, [...workflowHostSteps.values()], workflowResource?.provenance, workflowArgsDigest);
6007
6166
  return attachWorkflowMission(withRunFanoutBudget({
6008
6167
  content: [{ type: "text", text: displayText }],
6009
6168
  isError: true,
6010
- details: compactOptional<Details>({ mode: "workflow", runId: foregroundWorkflowRunId, results: workflowDetailsResults(partial.children), ...(workflowPreflight ? { preflight: workflowPreflight } : {}), workflowChildren, totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { trace: finalPreflightTrace, emits: partial.emits, console: partial.console, ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}), receipt }, chatProgress }),
6169
+ details: compactOptional<Details>({ mode: "workflow", runId: foregroundWorkflowRunId, results: workflowDetailsResults(partial.children), ...(workflowPreflight ? { preflight: workflowPreflight } : {}), workflowChildren, totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { trace: finalPreflightTrace, emits: partial.emits, console: partial.console, ...(workflowArgsEvidence ?? {}), ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}), receipt }, chatProgress }),
6011
6170
  }, workflowFanoutBudget));
6012
6171
  } finally {
6013
6172
  workflowProgressClosed = true;
@@ -6016,9 +6175,24 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6016
6175
  }
6017
6176
  }
6018
6177
  const directParams = requestParams;
6019
- const requestedCwd = directParams.cwd;
6020
- const requestCwd = resolveRequestedCwd(ctx.cwd, directParams.cwd);
6021
- const paramsWithResolvedCwd = directParams.cwd === undefined ? directParams : { ...directParams, cwd: requestCwd };
6178
+ // With a machine, cwd names a directory on that machine: keep it out of every local path resolution.
6179
+ // Placement can also come from the agent's frontmatter or settings override, so the agent is resolved first (discovery is fingerprint-cached).
6180
+ const placedByAgent = (): boolean => {
6181
+ if (directParams.machine !== undefined || directParams.cwd === undefined || typeof directParams.agent !== "string") return false;
6182
+ try {
6183
+ return resolveAgentName(directParams.agent, deps.discoverAgents(ctx.cwd, resolveExecutionAgentScope(directParams.agentScope)).agents).agent?.machine !== undefined;
6184
+ } catch {
6185
+ return false; // Discovery errors surface from the launch path itself.
6186
+ }
6187
+ };
6188
+ const remotePlacement = directParams.action === undefined && (directParams.machine !== undefined || placedByAgent());
6189
+ const requestedCwd = remotePlacement ? undefined : directParams.cwd;
6190
+ const requestCwd = remotePlacement ? ctx.cwd : resolveRequestedCwd(ctx.cwd, directParams.cwd);
6191
+ const paramsWithResolvedCwd = directParams.cwd === undefined
6192
+ ? directParams
6193
+ : remotePlacement
6194
+ ? omitUndefinedProperties({ ...directParams, cwd: undefined, machineCwd: directParams.cwd })
6195
+ : { ...directParams, cwd: requestCwd };
6022
6196
  const action = paramsWithResolvedCwd.action;
6023
6197
  let requestSessionId = "";
6024
6198
  let requestPiSessionId: string | undefined;
@@ -6117,6 +6291,24 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6117
6291
  return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "management", results: [] } };
6118
6292
  }
6119
6293
  }
6294
+ const policyAction = action === "stop" ? "stopRun" : action === "steer" ? "steerRun" : action === "schedule.create" ? "scheduleCreate" : action === "inspector.open" ? "inspectorOpen" : action === "project.open" ? "projectOpen" : undefined;
6295
+ if (policyAction) {
6296
+ // Child-safe mode is a hard capability boundary; the policy is an operator
6297
+ // preference. Refuse first, so the gate never prompts for an action that is
6298
+ // going to be rejected anyway and never masks the more specific reason.
6299
+ if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
6300
+ return { content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }], isError: true, details: { mode: "management", results: [] } };
6301
+ }
6302
+ const decision = resolveAuthorityDecision({ action: policyAction, ...(deps.config.authorityPolicy === undefined ? {} : { policy: deps.config.authorityPolicy }) });
6303
+ if (decision === "forbid") {
6304
+ return { content: [{ type: "text", text: `Authority policy forbids action '${action}'.` }], isError: true, details: { mode: "management", results: [] } };
6305
+ }
6306
+ if (decision === "confirm") {
6307
+ if (!ctx.hasUI) return { content: [{ type: "text", text: `Authority policy requires user confirmation for action '${action}', but this session has no interactive UI.` }], isError: true, details: { mode: "management", results: [] } };
6308
+ const confirmed = await ctx.ui.confirm(`Authorize subagent ${action}?`, `Authority policy requires confirmation before '${action}'.`);
6309
+ if (!confirmed) return { content: [{ type: "text", text: `Action '${action}' canceled; authority was not granted.` }], details: { mode: "management", results: [] } };
6310
+ }
6311
+ }
6120
6312
  if ((HERDR_PROJECT_PANE_ACTIONS as readonly string[]).includes(action)) {
6121
6313
  if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
6122
6314
  return { content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }], isError: true, details: { mode: "management", results: [] } };
@@ -6153,18 +6345,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6153
6345
  ...(currentSessionId ? { currentSessionId } : {}),
6154
6346
  });
6155
6347
  }
6156
- const policyAction = action === "stop" ? "stopRun" : action === "steer" ? "steerRun" : action === "schedule.create" ? "scheduleCreate" : undefined;
6157
- if (policyAction) {
6158
- const decision = resolveAuthorityDecision({ action: policyAction, ...(deps.config.authorityPolicy === undefined ? {} : { policy: deps.config.authorityPolicy }) });
6159
- if (decision === "forbid") {
6160
- return { content: [{ type: "text", text: `Authority policy forbids action '${action}'.` }], isError: true, details: { mode: "management", results: [] } };
6161
- }
6162
- if (decision === "confirm") {
6163
- if (!ctx.hasUI) return { content: [{ type: "text", text: `Authority policy requires user confirmation for action '${action}', but this session has no interactive UI.` }], isError: true, details: { mode: "management", results: [] } };
6164
- const confirmed = await ctx.ui.confirm(`Authorize subagent ${action}?`, `Authority policy requires confirmation before '${action}'.`);
6165
- if (!confirmed) return { content: [{ type: "text", text: `Action '${action}' canceled; authority was not granted.` }], details: { mode: "management", results: [] } };
6166
- }
6167
- }
6168
6348
  if ((WATCHDOG_TOOL_ACTIONS as readonly string[]).includes(action)) {
6169
6349
  if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
6170
6350
  return {
@@ -6676,6 +6856,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6676
6856
  const normalized = normalizeRepeatedParallelCounts(paramsWithResolvedCwd);
6677
6857
  if (normalized.error) return normalized.error;
6678
6858
  const normalizedParams = normalized.params!;
6859
+ const outputSchemaError = validateLaunchOutputSchemaOverrides(normalizedParams);
6860
+ if (outputSchemaError) return buildRequestedModeError(normalizedParams, outputSchemaError);
6679
6861
 
6680
6862
  let effectiveParams = applyForceTopLevelAsyncOverride(
6681
6863
  normalizedParams,
@@ -7257,6 +7439,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
7257
7439
  const { workflow: _workflow, args: _args, ...withoutResourceInput } = publicParams;
7258
7440
  publicParams = { ...withoutResourceInput, workflowScript: resolved.resource.script };
7259
7441
  workflowResourcePermits.set(publicParams, resolved.resource.permit);
7442
+ } else if (publicParams.workflowScript !== undefined || publicParams.workflowScriptPath !== undefined) {
7443
+ const normalizedArgs = normalizeWorkflowArgs(publicParams.args);
7444
+ if ("error" in normalizedArgs) return Promise.resolve({ content: [{ type: "text", text: normalizedArgs.error }], isError: true, details: { mode: publicParams.action ? "management" : "workflow", results: [] } });
7445
+ publicParams = { ...publicParams, args: deepFreezeWorkflowArgs(normalizedArgs.args) };
7260
7446
  }
7261
7447
  const loaded = loadWorkflowScriptPath(publicParams, ctx.cwd);
7262
7448
  if (loaded.error) {