pi-subagents 0.60.0 → 0.61.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 (57) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/docs/agents.md +2 -2
  3. package/docs/configuration.md +9 -5
  4. package/docs/extension-api.md +14 -7
  5. package/docs/models.md +1 -1
  6. package/docs/observability.md +1 -1
  7. package/docs/tool-reference.md +12 -3
  8. package/docs/workflows.md +14 -13
  9. package/install.mjs +2 -1
  10. package/package.json +1 -1
  11. package/skills/pi-subagents/SKILL.md +6 -4
  12. package/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
  13. package/skills/pi-subagents/references/execution-controls.md +42 -11
  14. package/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
  15. package/skills/pi-subagents/references/prompting-and-roles.md +4 -4
  16. package/skills/pi-subagents/references/review-and-validation.md +1 -1
  17. package/src/agents/agent-management.ts +102 -63
  18. package/src/agents/agents.ts +527 -221
  19. package/src/api/background-work.ts +7 -2
  20. package/src/api/external-runs.ts +67 -4
  21. package/src/api/preflight.ts +13 -8
  22. package/src/api/shared-types.ts +1 -0
  23. package/src/extension/index.ts +7 -4
  24. package/src/extension/public-execution.ts +47 -4
  25. package/src/extension/rpc.ts +62 -4
  26. package/src/extension/schemas.ts +11 -7
  27. package/src/extension/tool-description.ts +14 -18
  28. package/src/runs/background/async-execution.ts +51 -35
  29. package/src/runs/background/async-job-tracker.ts +62 -3
  30. package/src/runs/background/async-resume.ts +3 -1
  31. package/src/runs/background/async-status.ts +59 -9
  32. package/src/runs/background/auto-drain.ts +1 -1
  33. package/src/runs/background/fleet-view.ts +1 -1
  34. package/src/runs/background/result-watcher.ts +1 -1
  35. package/src/runs/background/resume-guidance.ts +1 -1
  36. package/src/runs/background/run-status.ts +2 -2
  37. package/src/runs/background/subagent-runner.ts +1 -2
  38. package/src/runs/background/subagent-wait.ts +20 -21
  39. package/src/runs/background/wait-completions.ts +1 -1
  40. package/src/runs/background/wait-tool.ts +24 -18
  41. package/src/runs/foreground/execution.ts +61 -2
  42. package/src/runs/foreground/subagent-executor.ts +178 -58
  43. package/src/runs/shared/acceptance.ts +43 -18
  44. package/src/runs/shared/host-step-status.ts +1 -0
  45. package/src/runs/shared/model-fallback.ts +61 -17
  46. package/src/runs/shared/permissions.ts +1 -1
  47. package/src/runs/shared/tool-timeout.ts +1 -1
  48. package/src/runs/shared/workflow-graph.ts +3 -2
  49. package/src/shared/types.ts +40 -3
  50. package/src/shared/workflow-child-permit.ts +91 -0
  51. package/src/slash/prompt-template-bridge.ts +37 -1
  52. package/src/slash/slash-commands.ts +18 -26
  53. package/src/tui/render.ts +95 -52
  54. package/src/workflows/scripted-workflow.ts +153 -4
  55. package/src/workflows/workflow-child-summary.ts +1 -1
  56. package/src/workflows/workflow-receipt.ts +41 -4
  57. package/src/workflows/workflow-resources.ts +150 -0
@@ -23,11 +23,11 @@ import { handleManagementAction } from "../../agents/agent-management.ts";
23
23
  import { handleRefinementAction } from "../../agents/agent-refinements.ts";
24
24
  import { buildDoctorReport } from "../../extension/doctor.ts";
25
25
  import { readSubagentGuide } from "../../extension/subagent-guide.ts";
26
- import { normalizePublicSubagentExecution } from "../../extension/public-execution.ts";
26
+ import { normalizePublicSubagentExecution, validateWorkflowCapacityOverrides } from "../../extension/public-execution.ts";
27
27
  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
- import { buildModelCandidates, inheritsParentModel, normalizeParentModel, resolveEffectiveSubagentModel, type ParentModel } from "../shared/model-fallback.ts";
30
+ import { buildModelCandidates, normalizeParentModel, resolveEffectiveSubagentModel, resolveModelOrigin, type ModelOrigin, type ParentModel } from "../shared/model-fallback.ts";
31
31
  import { formatRetainedChildren, listRetainedChildren } from "../background/retained-children.ts";
32
32
  import { resolveModelScopesForAgent, type ModelScopeConfig } from "../shared/model-scope.ts";
33
33
  import { recordRun } from "../shared/run-history.ts";
@@ -120,7 +120,17 @@ import { assertWorkflowLaneKey, normalizeWorkflowLaneMetadata } from "../shared/
120
120
  import { parseWorkflowChildSummary, workflowChildSummary } from "../../workflows/workflow-child-summary.ts";
121
121
  import { resolveWorkflowChatProgress, type WorkflowChatProgressProjection } from "../../workflows/chat-progress.ts";
122
122
  import { annotateWorkflowPreflightTrace, formatWorkflowPreflight, formatWorkflowPreflightWarnings, normalizeWorkflowPreflight, workflowPreflightWarnings } from "../../workflows/workflow-preflight.ts";
123
- import { claimWorkflowChildPermit, validateWorkflowChildPermitRoot, type WorkflowChildPermit, type WorkflowChildPermitContext } from "../../shared/workflow-child-permit.ts";
123
+ import {
124
+ authorizeWorkflowResourceHost,
125
+ claimWorkflowChildPermit,
126
+ consumeWorkflowResourcePermit,
127
+ validateWorkflowChildPermitRoot,
128
+ type WorkflowChildPermit,
129
+ type WorkflowChildPermitContext,
130
+ type WorkflowResourceAuthority,
131
+ type WorkflowResourcePermit,
132
+ } from "../../shared/workflow-child-permit.ts";
133
+ import { resolveWorkflowResource } from "../../workflows/workflow-resources.ts";
124
134
  import {
125
135
  cleanupWorktrees,
126
136
  createWorktrees,
@@ -160,6 +170,7 @@ import {
160
170
  type ToolBudgetConfig,
161
171
  type Usage,
162
172
  type UsageBudgetConfig,
173
+ type WorkflowResourceProvenanceV1,
163
174
  type WorkflowGraphNode,
164
175
  type WorkflowGraphSnapshot,
165
176
  type WorkflowNodeStatus,
@@ -309,6 +320,8 @@ export interface SubagentParamsLike {
309
320
  planId?: string;
310
321
  workflowScript?: string;
311
322
  workflowScriptPath?: string;
323
+ globalConcurrencyLimit?: number;
324
+ maxSubagentSpawnsPerRun?: number;
312
325
  preflight?: import("../../shared/types.ts").WorkflowPreflightV1;
313
326
  chatProgress?: "auto" | "off" | "live-card";
314
327
  isolation?: "none" | "worktree";
@@ -357,8 +370,13 @@ export interface SubagentParamsLike {
357
370
  artifacts?: boolean;
358
371
  includeProgress?: boolean;
359
372
  model?: string;
373
+ /** Internal recovery provenance for a resolved model override. */
374
+ modelOrigin?: ModelOrigin;
360
375
  fast?: boolean;
361
376
  thinking?: string | false;
377
+ /** Public named workflow resource. Resolved before entering the workflow sandbox. */
378
+ workflow?: string;
379
+ args?: Record<string, unknown>;
362
380
  scope?: string;
363
381
  target?: string;
364
382
  focus?: boolean;
@@ -418,9 +436,9 @@ interface ExecutorDeps {
418
436
  kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
419
437
  }
420
438
 
421
- type ForkSessionFileForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean) => string | undefined;
422
- type PrepareForkSessionForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean) => Promise<void>;
423
- type ForkThinkingOverrideForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean) => AgentConfig["thinking"] | undefined;
439
+ type ForkSessionFileForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean, modelOrigin?: ModelOrigin) => string | undefined;
440
+ type PrepareForkSessionForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean, modelOrigin?: ModelOrigin) => Promise<void>;
441
+ type ForkThinkingOverrideForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean, modelOrigin?: ModelOrigin) => AgentConfig["thinking"] | undefined;
424
442
 
425
443
  interface ExecutionContextData {
426
444
  params: SubagentParamsLike;
@@ -447,6 +465,8 @@ interface ExecutionContextData {
447
465
  effectiveAsync: boolean;
448
466
  asyncRunId: string;
449
467
  controlConfig: ResolvedControlConfig;
468
+ /** Structured delegation consumers do not need duration-only heartbeat snapshots. */
469
+ suppressUnchangedDelegationUpdates?: boolean;
450
470
  intercomBridge: IntercomBridgeState;
451
471
  nestedRoute?: NestedRouteInfo;
452
472
  timeoutMs?: number;
@@ -865,7 +885,7 @@ function resolveForegroundResumeTarget(params: SubagentParamsLike, state: Subage
865
885
  if (matches.length === 0) return undefined;
866
886
  if (matches.length > 1) throw new Error(`Ambiguous foreground run id prefix '${requested}' matched: ${matches.map((run) => run.runId).join(", ")}. Provide a longer id.`);
867
887
  const run = matches[0]!;
868
- if (run.children.some((child) => child.status === "detached")) throw new Error(`Foreground run '${run.runId}' is detached for intercom coordination and cannot be revived safely while any child may still be live. Reply to the supervisor request first, then wait with subagent_wait({ id: "${run.runId}" }); use status to recover the result and do not launch a replacement while it remains detached.`);
888
+ if (run.children.some((child) => child.status === "detached")) throw new Error(`Foreground run '${run.runId}' is detached for intercom coordination and cannot be revived safely while any child may still be live. Reply to the supervisor request first, then wait with bg_wait({ id: "${run.runId}" }); use status to recover the result and do not launch a replacement while it remains detached.`);
869
889
  if (run.children.length > 1 && params.index === undefined) throw new Error(`Foreground run '${run.runId}' has ${run.children.length} children. Provide index to choose one.`);
870
890
  const index = params.index ?? 0;
871
891
  if (!Number.isInteger(index)) throw new Error(`Foreground run '${run.runId}' index must be an integer.`);
@@ -2024,6 +2044,7 @@ async function resumeAsyncRun(input: {
2024
2044
  modelOverride: recoveryDescriptor?.model ?? target.model,
2025
2045
  fast: recoveryDescriptor?.fast,
2026
2046
  modelOverrideFromParent: recoveryDescriptor?.modelOverrideFromParent,
2047
+ modelOrigin: recoveryDescriptor?.modelOrigin ?? (recoveryDescriptor?.modelOverrideFromParent ? "inherited" : undefined),
2027
2048
  thinkingOverride: recoveryDescriptor?.thinking ?? target.thinking,
2028
2049
  thinkingCeiling: recoveryDescriptor?.thinkingCeiling ?? ("thinkingCeiling" in target ? target.thinkingCeiling : undefined),
2029
2050
  extensionBindings: recoveryDescriptor?.extensionBindings ?? ("extensionBindings" in target ? target.extensionBindings : undefined),
@@ -2997,7 +3018,15 @@ async function preflightForkSessionsForStaticTasks(
2997
3018
  ): Promise<void> {
2998
3019
  if (!contextPolicy.usesFork) return;
2999
3020
  if (params.agent) {
3000
- if (shouldForkAgent(contextPolicy, params.agent)) await prepareSessionForTask(params.agent, 0, params.model);
3021
+ if (shouldForkAgent(contextPolicy, params.agent)) {
3022
+ await prepareSessionForTask(
3023
+ params.agent,
3024
+ 0,
3025
+ params.model,
3026
+ params.modelOrigin === "inherited",
3027
+ params.modelOrigin,
3028
+ );
3029
+ }
3001
3030
  return;
3002
3031
  }
3003
3032
  if (params.tasks) {
@@ -3175,10 +3204,19 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3175
3204
  return buildRequestedModeError(params, `Agent '${a.name}' uses runner.type='${a.runner.type}' and does not support fast mode.`);
3176
3205
  }
3177
3206
  const modelScopes = resolveModelScopesForAgent(data.modelScope, a.name, parentModel);
3207
+ const modelOrigin = resolveModelOrigin({
3208
+ storedOrigin: params.modelOrigin as ModelOrigin | undefined,
3209
+ explicitModel: params.model as string | undefined,
3210
+ agentModel: a.model,
3211
+ parentModel,
3212
+ });
3178
3213
  const modelOverride = a.runner?.type === "external-cli" || a.runner?.type === "external-job"
3179
3214
  ? params.model ?? (externalRunnerWithoutExplicitModel ? undefined : a.model)
3180
- : resolveEffectiveSubagentModel(params.model as string | undefined, a.model, parentModel, availableModels, a.modelProvider ?? currentProvider, modelScopes.length === 0 ? {} : { scope: modelScopes });
3181
- const modelOverrideFromParent = inheritsParentModel(params.model as string | undefined, a.model, parentModel);
3215
+ : resolveEffectiveSubagentModel(params.model as string | undefined, a.model, parentModel, availableModels, a.modelProvider ?? currentProvider, {
3216
+ ...(modelScopes.length === 0 ? {} : { scope: modelScopes }),
3217
+ source: modelOrigin === "explicit" ? "explicit" : "inherited",
3218
+ });
3219
+ const modelOverrideFromParent = modelOrigin === "inherited";
3182
3220
  const asyncResult = executeAsyncSingle(id, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
3183
3221
  agent: params.agent!,
3184
3222
  task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
@@ -3195,7 +3233,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3195
3233
  shareEnabled,
3196
3234
  activeAsyncCapacity: data.activeAsyncCapacity,
3197
3235
  sessionRoot,
3198
- sessionFile: sessionFileForTask(params.agent!, 0, modelOverride, modelOverrideFromParent),
3236
+ sessionFile: sessionFileForTask(params.agent!, 0, modelOverride, modelOverrideFromParent, modelOrigin),
3199
3237
  context: contextPolicy.contextForAgent(params.agent!),
3200
3238
  skills,
3201
3239
  output: effectiveOutput,
@@ -3206,7 +3244,8 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3206
3244
  modelOverride,
3207
3245
  fast: params.fast,
3208
3246
  modelOverrideFromParent,
3209
- thinkingOverride: externalRunnerWithoutExplicitModel ? undefined : thinkingOverrideForTask(params.agent!, 0, modelOverride, modelOverrideFromParent),
3247
+ modelOrigin,
3248
+ thinkingOverride: externalRunnerWithoutExplicitModel ? undefined : thinkingOverrideForTask(params.agent!, 0, modelOverride, modelOverrideFromParent, modelOrigin),
3210
3249
  thinkingCeiling: a.maxThinking,
3211
3250
  maxSubagentDepth,
3212
3251
  waitToolEnabled: deps.waitToolEnabled,
@@ -3311,8 +3350,11 @@ function workflowHostCommandRunner(input: {
3311
3350
  workflowRunId: string;
3312
3351
  claimedOutputPaths: Map<string, string>;
3313
3352
  producedOutputPaths: Set<string>;
3353
+ authorize?: (key: string, params: WorkflowHostCommandParams) => string | undefined;
3314
3354
  }) {
3315
3355
  return async (key: string, params: WorkflowHostCommandParams, signal: AbortSignal): Promise<WorkflowHostCommandResult> => {
3356
+ const authorizationError = input.authorize?.(key, params);
3357
+ if (authorizationError) throw new Error(authorizationError);
3316
3358
  const defaultOutputPath = path.join(input.artifactsDir, "outputs", sanitizeRunPathSegment(input.workflowRunId), "host", `${sanitizeRunPathSegment(key)}.log`);
3317
3359
  const outputPath = params.output ? path.resolve(input.workflowCwd, params.output) : defaultOutputPath;
3318
3360
  const claimPath = resolveWorkflowHostOutputClaimPath(outputPath);
@@ -3552,6 +3594,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3552
3594
  onUpdate,
3553
3595
  controlConfig,
3554
3596
  contextPolicy,
3597
+ suppressUnchangedDelegationUpdates,
3555
3598
  } = data;
3556
3599
  let lane: import("../../shared/types.ts").WorkflowLaneMetadata | undefined;
3557
3600
  try {
@@ -3581,15 +3624,24 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3581
3624
  const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
3582
3625
  const modelScopes = resolveModelScopesForAgent(data.modelScope, agentConfig.name, parentModel);
3583
3626
  let task = typeof params.task === "string" ? params.task : "";
3627
+ const modelOrigin = resolveModelOrigin({
3628
+ storedOrigin: params.modelOrigin as ModelOrigin | undefined,
3629
+ explicitModel: params.model as string | undefined,
3630
+ agentModel: agentConfig.model,
3631
+ parentModel,
3632
+ });
3584
3633
  let modelOverride: string | undefined = resolveEffectiveSubagentModel(
3585
3634
  params.model as string | undefined,
3586
3635
  agentConfig.model,
3587
3636
  parentModel,
3588
3637
  availableModels,
3589
3638
  agentConfig.modelProvider ?? currentProvider,
3590
- modelScopes.length === 0 ? {} : { scope: modelScopes },
3639
+ {
3640
+ ...(modelScopes.length === 0 ? {} : { scope: modelScopes }),
3641
+ source: modelOrigin === "explicit" ? "explicit" : "inherited",
3642
+ },
3591
3643
  );
3592
- const modelOverrideFromParent = inheritsParentModel(params.model as string | undefined, agentConfig.model, parentModel);
3644
+ const modelOverrideFromParent = modelOrigin === "inherited";
3593
3645
  let skillOverride: string[] | false | undefined = normalizeSkillInput(params.skill);
3594
3646
  let readsOverride: string[] | false | undefined = params.reads;
3595
3647
  const rawOutput = params.output !== undefined ? params.output : agentConfig.output;
@@ -3660,7 +3712,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3660
3712
  let detachForeground: ((reason?: string) => boolean) | undefined;
3661
3713
  const foregroundControl = deps.state.foregroundControls.get(runId);
3662
3714
  if (foregroundControl) {
3663
- const thinking = resolveEffectiveThinking(modelOverride, thinkingOverrideForTask(params.agent!, 0, modelOverride, modelOverrideFromParent));
3715
+ const thinking = resolveEffectiveThinking(modelOverride, thinkingOverrideForTask(params.agent!, 0, modelOverride, modelOverrideFromParent, modelOrigin));
3664
3716
  beginForegroundChild(foregroundControl, omitUndefinedProperties({
3665
3717
  index: 0,
3666
3718
  agent: params.agent!,
@@ -3712,7 +3764,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3712
3764
  intercomEvents: deps.pi.events,
3713
3765
  runId,
3714
3766
  sessionDir: sessionDirForIndex(0),
3715
- sessionFile: sessionFileForTask(params.agent!, 0, modelOverride, modelOverrideFromParent),
3767
+ sessionFile: sessionFileForTask(params.agent!, 0, modelOverride, modelOverrideFromParent, modelOrigin),
3716
3768
  share: shareEnabled,
3717
3769
  artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
3718
3770
  artifactConfig,
@@ -3724,6 +3776,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3724
3776
  waitToolEnabled: deps.waitToolEnabled,
3725
3777
  waitToolDefaultTimeoutMs: deps.waitToolDefaultTimeoutMs,
3726
3778
  onUpdate: forwardSingleUpdate,
3779
+ suppressUnchangedDelegationUpdates,
3727
3780
  controlConfig,
3728
3781
  onControlEvent,
3729
3782
  intercomSessionName: childIntercomTarget,
@@ -3733,7 +3786,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3733
3786
  modelOverride,
3734
3787
  fast: params.fast,
3735
3788
  modelOverrideFromParent,
3736
- thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride, modelOverrideFromParent),
3789
+ modelOrigin,
3790
+ thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride, modelOverrideFromParent, modelOrigin),
3737
3791
  thinkingCeiling: agentConfig.maxThinking,
3738
3792
  extensionBindings: params.extensionBindings,
3739
3793
  availableModels,
@@ -3880,11 +3934,11 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3880
3934
  const worktreeSuffix = worktreeHandoff?.suffix ? `\n\n${worktreeHandoff.suffix}` : "";
3881
3935
  if (r.detached) {
3882
3936
  const statusRecovery = `subagent({ action: "status", id: "${runId}" }) to recover the result; do not resume or launch a replacement while it remains detached.`;
3883
- const blockingRecovery = `subagent_wait({ id: "${runId}" }). Use ${statusRecovery}`;
3937
+ const blockingRecovery = `bg_wait({ id: "${runId}" }). Use ${statusRecovery}`;
3884
3938
  const message = r.detachedReason === "intercom coordination"
3885
3939
  ? `Detached for intercom coordination: ${params.agent}. Reply to the supervisor request first, then wait with ${blockingRecovery}`
3886
3940
  : r.detachedReason === "user request"
3887
- ? `Detached at user request: ${params.agent}. The child continues independently. Register a completion wake-up with subagent_wait({ id: "${runId}", nonBlocking: true }), or use ${statusRecovery}`
3941
+ ? `Detached at user request: ${params.agent}. The child continues independently. Register a completion wake-up with bg_wait({ id: "${runId}", nonBlocking: true }), or use ${statusRecovery}`
3888
3942
  : `Detached before task completion: ${params.agent}. Wait with ${blockingRecovery}`;
3889
3943
  return {
3890
3944
  content: [{ type: "text", text: `${message}${worktreeSuffix}` }],
@@ -4022,7 +4076,8 @@ function workflowChildResult(
4022
4076
  : result.details.usageBudget?.exhausted || result.details.results.some((child) => child.turnBudgetExceeded || child.toolBudgetBlocked)
4023
4077
  ? { state: "partial" as const, reason: "budget_exhausted" as const }
4024
4078
  : undefined);
4025
- const ok = result.isError !== true && !detached && !interrupted && !stopped;
4079
+ const acceptanceRecovery = result.details.results.find((child) => child.acceptance?.recovery)?.acceptance?.recovery;
4080
+ const ok = result.isError !== true && !detached && !interrupted && !stopped && acceptanceRecovery === undefined;
4026
4081
  const artifactPaths = new Set<string>();
4027
4082
  if (result.details.asyncDir) artifactPaths.add(result.details.asyncDir);
4028
4083
  if (result.details.parallelHandoff?.path) artifactPaths.add(result.details.parallelHandoff.path);
@@ -4080,6 +4135,7 @@ function workflowChildResult(
4080
4135
  ...(requestedContext ? { requestedContext } : {}),
4081
4136
  ...(resolvedContext ? { resolvedContext } : {}),
4082
4137
  ...(outputReference ? { outputReference } : {}),
4138
+ ...(acceptanceRecovery ? { recovery: acceptanceRecovery } : {}),
4083
4139
  ...(outputPathMapping ? { outputPathMapping } : {}),
4084
4140
  ...(externalAdapter ? { externalAdapter } : {}),
4085
4141
  resumability,
@@ -4089,13 +4145,14 @@ function workflowChildResult(
4089
4145
  };
4090
4146
  }
4091
4147
 
4092
- function workflowChildAccountingFields(child: WorkflowScriptChildResult): { usage?: Usage; sessionFile?: string } {
4148
+ function workflowChildAccountingFields(child: WorkflowScriptChildResult): { usage?: Usage; sessionFile?: string; recovery?: import("../../shared/types.ts").AcceptanceRecoveryMetadata } {
4093
4149
  if (!child.results?.length) return {};
4094
4150
  const usage = sumResultsUsage(child.results);
4095
4151
  const sessionFile = child.results.find((result) => result.sessionFile)?.sessionFile;
4096
4152
  return {
4097
4153
  ...(usage.input !== 0 || usage.output !== 0 || usage.cacheRead !== 0 || usage.cacheWrite !== 0 || usage.cost !== 0 || usage.turns !== 0 ? { usage } : {}),
4098
4154
  ...(sessionFile ? { sessionFile } : {}),
4155
+ ...(child.recovery ? { recovery: child.recovery } : {}),
4099
4156
  };
4100
4157
  }
4101
4158
 
@@ -4223,8 +4280,9 @@ function terminalWorkflowReceipt(
4223
4280
  workflowChildren?: WorkflowReceipt["workflowChildren"],
4224
4281
  terminalOutcome?: WorkflowTerminalOutcome,
4225
4282
  hostSteps?: WorkflowReceipt["hostSteps"],
4283
+ resource?: WorkflowReceipt["resource"],
4226
4284
  ): WorkflowReceipt {
4227
- return buildWorkflowReceipt({ workflowRunId, state, children, workflowChildren, terminalOutcome, hostSteps });
4285
+ return buildWorkflowReceipt({ workflowRunId, state, children, workflowChildren, terminalOutcome, hostSteps, resource });
4228
4286
  }
4229
4287
 
4230
4288
  function workflowFailureTerminalOutcome(error: unknown, _children: WorkflowScriptChildResult[], usageBudget: ReturnType<typeof usageBudgetState>): WorkflowTerminalOutcome | undefined {
@@ -4316,6 +4374,10 @@ export function prepareWorkflowLaunchParams(
4316
4374
  workflowKey: string,
4317
4375
  options: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; awaitDetachedChild?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor; parentDeadlineAt?: number; externalAsyncRequired?: boolean; capabilityCeiling?: ResolvedSubagentCapabilityCeiling; outputClaimPath?: string } = {},
4318
4376
  ): SubagentParamsLike {
4377
+ const { globalConcurrencyLimit: _globalConcurrencyLimit, maxSubagentSpawnsPerRun: _maxSubagentSpawnsPerRun, ...workflowDefaultsWithoutCapacity } = workflowDefaults;
4378
+ const { globalConcurrencyLimit: _childGlobalConcurrencyLimit, maxSubagentSpawnsPerRun: _childMaxSubagentSpawnsPerRun, ...childParamsWithoutCapacity } = childParams;
4379
+ workflowDefaults = workflowDefaultsWithoutCapacity;
4380
+ childParams = childParamsWithoutCapacity;
4319
4381
  const capabilityCeiling = intersectSubagentCapabilityCeilings(workflowDefaults.capabilityCeiling, options.capabilityCeiling);
4320
4382
  const lane = normalizeWorkflowLaneMetadata(Object.hasOwn(childParams, "lane") ? childParams.lane : workflowDefaults.lane, `workflow child '${workflowKey}'.lane`);
4321
4383
  assertWorkflowLaneKey(lane, workflowKey, `workflow child '${workflowKey}'.lane`);
@@ -4586,6 +4648,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4586
4648
  const delegatedThinkingOverrides = new WeakMap<object, AgentConfig["thinking"]>();
4587
4649
  const delegatedZeroToolBudgets = new WeakSet<object>();
4588
4650
  const delegatedExecutions = new WeakSet<object>();
4651
+ const publicExecutions = new WeakSet<object>();
4652
+ const workflowResourcePermits = new WeakMap<object, WorkflowResourcePermit>();
4589
4653
  const workflowPermitContexts = new WeakMap<object, { root: WorkflowChildPermit } | { child: WorkflowChildPermitContext }>();
4590
4654
  const warnedArtifactPackageDirs = new Set<string>();
4591
4655
  const scheduledOwnerExecutors = new Map<string, ReturnType<typeof createSubagentExecutor>>();
@@ -4602,6 +4666,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4602
4666
  const delegatedThinkingOverride = delegatedThinkingOverrides.get(params);
4603
4667
  const allowZeroToolBudget = delegatedZeroToolBudgets.has(params);
4604
4668
  const delegatedExecution = delegatedExecutions.has(params);
4669
+ const publicExecution = publicExecutions.has(params);
4670
+ const workflowResourcePermit = workflowResourcePermits.get(params);
4605
4671
  const workflowPermitContext = workflowPermitContexts.get(params);
4606
4672
  const delegatedWorkflowPermit = workflowPermitContext && "root" in workflowPermitContext ? workflowPermitContext.root : undefined;
4607
4673
  const workflowChildPermitLaunch = workflowPermitContext && "child" in workflowPermitContext ? workflowPermitContext.child : undefined;
@@ -4612,6 +4678,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4612
4678
  const normalizedGate = normalizeGateParams(params);
4613
4679
  if (!normalizedGate.ok) return buildRequestedModeError(params, normalizedGate.error);
4614
4680
  let requestParams = normalizedGate.params;
4681
+ const capacityOverrideError = validateWorkflowCapacityOverrides(requestParams);
4682
+ if (capacityOverrideError) return buildRequestedModeError(requestParams, capacityOverrideError);
4615
4683
  let workflowPreflight: import("../../shared/types.ts").WorkflowPreflightV1 | undefined;
4616
4684
  try {
4617
4685
  if (requestParams.preflight !== undefined && requestParams.workflowScript === undefined && requestParams.workflowScriptPath === undefined) {
@@ -4643,6 +4711,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4643
4711
  }
4644
4712
  const normalizedAction = typeof requestParams.action === "string" ? requestParams.action.trim() : requestParams.action;
4645
4713
  if (normalizedAction === "resume" && requestParams.extensionBindings !== undefined) return buildRequestedModeError(requestParams, "extensionBindings is not supported with action='resume'; resume uses the original retained child binding.");
4714
+ let workflowResource: { permit: WorkflowResourcePermit; provenance: WorkflowResourceProvenanceV1; authority: WorkflowResourceAuthority } | undefined;
4715
+ if (workflowResourcePermit) {
4716
+ if (typeof requestParams.workflowScript !== "string") return buildRequestedModeError(requestParams, "Resolved workflow resource is missing its workflow script.");
4717
+ const consumed = consumeWorkflowResourcePermit(workflowResourcePermit, requestParams.workflowScript);
4718
+ if (typeof consumed === "string") return buildRequestedModeError(requestParams, consumed);
4719
+ workflowResource = { permit: workflowResourcePermit, ...consumed };
4720
+ }
4646
4721
  if (requestParams.workflowScript !== undefined && normalizedAction === undefined) {
4647
4722
  if (delegatedWorkflowPermit) {
4648
4723
  const permitError = validateWorkflowChildPermitRoot(delegatedWorkflowPermit, _id);
@@ -4722,7 +4797,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4722
4797
  try {
4723
4798
  workflowFanoutBudget = requestParams.runFanoutBudget
4724
4799
  ?? decodeRunFanoutBudgetDescriptor(process.env[RUN_FANOUT_BUDGET_ENV])
4725
- ?? createRunFanoutBudget(_id, resolveMaxSubagentSpawnsPerRun(deps.config.maxSubagentSpawnsPerRun));
4800
+ ?? createRunFanoutBudget(_id, requestParams.maxSubagentSpawnsPerRun ?? resolveMaxSubagentSpawnsPerRun(deps.config.maxSubagentSpawnsPerRun));
4726
4801
  } catch (error) {
4727
4802
  workflowCapacity?.rollback();
4728
4803
  return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "workflow", results: [] } };
@@ -4800,7 +4875,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4800
4875
  pid: process.pid,
4801
4876
  steps: [],
4802
4877
  ...(workflowPreflight ? { preflight: workflowPreflight } : {}),
4803
- workflow: { trace: [], emits: [], console: [] },
4878
+ workflow: { trace: [], emits: [], console: [], ...(workflowResource ? { resource: workflowResource.provenance } : {}) },
4804
4879
  workflowChildren: workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState: "running", inventoryComplete: false }),
4805
4880
  runFanoutBudget: getRunFanoutBudgetSnapshot(workflowFanoutBudget),
4806
4881
  };
@@ -4952,7 +5027,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4952
5027
  const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
4953
5028
  const workflowResults: SingleResult[] = [];
4954
5029
  const workflowChildRunIds = new Map<string, string>();
4955
- 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, ...workflowChildDefaults } = workflowRequest;
5030
+ 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;
4956
5031
  const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
4957
5032
  const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
4958
5033
  const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, parentCwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, workflowRunId));
@@ -4961,7 +5036,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4961
5036
  const childOutputClaimPaths = new Map<string, string>();
4962
5037
  const producedChildOutputPaths = new Set<string>();
4963
5038
  const workflowSteps = new Map<string, NonNullable<AsyncStatus["steps"]>[number]>();
4964
- const runHostCommand = workflowHostCommandRunner({ workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId, claimedOutputPaths, producedOutputPaths: producedChildOutputPaths });
5039
+ const runHostCommand = workflowHostCommandRunner({
5040
+ workflowCwd,
5041
+ artifactsDir: workflowArtifactsDir,
5042
+ workflowRunId,
5043
+ claimedOutputPaths,
5044
+ producedOutputPaths: producedChildOutputPaths,
5045
+ ...(workflowResource ? { authorize: (key, params) => authorizeWorkflowResourceHost(workflowResource!.permit, key, params.command) } : {}),
5046
+ });
5047
+ const workflowHost = workflowResource
5048
+ ? workflowResource.authority.host ? runHostCommand : undefined
5049
+ : publicExecution ? undefined : runHostCommand;
4965
5050
  let projectedTraceLength = 0;
4966
5051
  let projectedTraceTail: NonNullable<Details["workflow"]>["trace"][number] | undefined;
4967
5052
  const updateTrace = (trace: NonNullable<Details["workflow"]>["trace"]) => {
@@ -5067,7 +5152,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5067
5152
  try {
5068
5153
  const workflow = await runWorkflowScript({
5069
5154
  script: workflowScript,
5070
- globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
5155
+ globalConcurrencyLimit: requestParams.globalConcurrencyLimit ?? deps.config.globalConcurrencyLimit,
5071
5156
  timeoutMs: timeout,
5072
5157
  signal: controller.signal,
5073
5158
  registerStopChild: (stop) => {
@@ -5081,7 +5166,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5081
5166
  applyWorkflowLaneTrace(status.workflowGraph, status.workflow?.trace ?? []);
5082
5167
  persist({ tolerateStatusWriteFailure: true });
5083
5168
  },
5084
- host: runHostCommand,
5169
+ ...(workflowHost ? { host: workflowHost } : {}),
5085
5170
  onHostStep: (hostStep) => {
5086
5171
  status = upsertHostStep({ status, hostStep, persist: (nextStatus) => {
5087
5172
  status = nextStatus;
@@ -5206,8 +5291,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5206
5291
  const resultSummary = appendWorkflowOutputWarning(summary, outputWarning);
5207
5292
  const workflowUsage = sumResultsUsage(workflowResults);
5208
5293
  const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState: "completed", inventoryComplete: true, trace: workflow.trace, children: workflow.children, steps: status.steps });
5209
- status = { ...status, state: "complete", endedAt: Date.now(), workflow: { value: workflow.value, trace: finalPreflightTrace, emits: workflow.emits, console: workflow.console, ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}) }, workflowChildren, totalTokens: { input: workflowUsage.input, output: workflowUsage.output, total: workflowUsage.input + workflowUsage.output }, totalCost: sumResultsCost(workflowResults) };
5210
- const receipt = terminalWorkflowReceipt(workflowRunId, "complete", workflow.children, workflowChildren, undefined, validHostStepNodes(status.workflowGraph));
5294
+ 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) };
5295
+ const receipt = terminalWorkflowReceipt(workflowRunId, "complete", workflow.children, workflowChildren, undefined, validHostStepNodes(status.workflowGraph), workflowResource?.provenance);
5211
5296
  let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
5212
5297
  try {
5213
5298
  workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
@@ -5241,7 +5326,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5241
5326
  const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState, inventoryComplete: true, trace: partial.trace, children: partial.children, steps: status.steps });
5242
5327
  const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, partial.trace, { settled: true });
5243
5328
  const finalPreflightTrace = annotateWorkflowPreflightTrace(partial.trace, workflowPreflight);
5244
- status = compactOptional<AsyncStatus>({ ...status, state, stopped: stopped || undefined, activityState: pauseForDetached ? "needs_attention" : undefined, error: workflowFailureMessage(error, workflowRunId, partial.children), endedAt: Date.now(), workflow: { trace: finalPreflightTrace, emits: partial.emits, console: partial.console, ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}) }, workflowChildren });
5329
+ status = compactOptional<AsyncStatus>({ ...status, state, stopped: stopped || undefined, activityState: pauseForDetached ? "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 });
5245
5330
  if (pauseForDetached) {
5246
5331
  const promoted = promotePausedWorkflowIfSettled(status);
5247
5332
  if (promoted) status = promoted;
@@ -5253,7 +5338,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5253
5338
  const resultSummary = appendWorkflowOutputWarning(terminalSummary, outputWarning);
5254
5339
  const receiptState: WorkflowReceiptState = status.state === "complete" ? "complete" : status.state === "paused" ? "paused" : status.state === "stopped" ? "stopped" : "failed";
5255
5340
  const terminalOutcome = workflowFailureTerminalOutcome(error, partial.children, usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)));
5256
- const receipt = terminalWorkflowReceipt(workflowRunId, receiptState, partial.children, workflowChildren, terminalOutcome, validHostStepNodes(status.workflowGraph));
5341
+ const receipt = terminalWorkflowReceipt(workflowRunId, receiptState, partial.children, workflowChildren, terminalOutcome, validHostStepNodes(status.workflowGraph), workflowResource?.provenance);
5257
5342
  let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
5258
5343
  try {
5259
5344
  workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
@@ -5275,10 +5360,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5275
5360
  });
5276
5361
  return attachWorkflowMission(withRunFanoutBudget({
5277
5362
  content: [{ type: "text", text: `${workflowPreflight ? `${formatWorkflowPreflight(workflowPreflight)}\n\n` : ""}${formatAsyncStartedMessage(`Async workflow [${workflowRunId}]`, ctx.hasUI === true)}` }],
5278
- details: { mode: "workflow", runId: workflowRunId, toolCallId, asyncId: workflowRunId, asyncDir, results: [], ...(workflowPreflight ? { preflight: workflowPreflight } : {}), workflowChildren: status.workflowChildren, chatProgress, ...(deps.state.activeAsyncCapacity ? { activeAsyncCapacity: deps.state.activeAsyncCapacity } : {}) },
5363
+ 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 } : {}) },
5279
5364
  }, workflowFanoutBudget));
5280
5365
  }
5281
- 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, ...workflowChildDefaults } = requestParams;
5366
+ 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;
5282
5367
  const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
5283
5368
  const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
5284
5369
  const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, _id));
@@ -5288,9 +5373,21 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5288
5373
  const producedChildOutputPaths = new Set<string>();
5289
5374
  const workflowResults: SingleResult[] = [];
5290
5375
  const workflowChildRunIds = new Map<string, string>();
5291
- const runHostCommand = workflowHostCommandRunner({ workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId: _id, claimedOutputPaths, producedOutputPaths: producedChildOutputPaths });
5376
+ const runHostCommand = workflowHostCommandRunner({
5377
+ workflowCwd,
5378
+ artifactsDir: workflowArtifactsDir,
5379
+ workflowRunId: _id,
5380
+ claimedOutputPaths,
5381
+ producedOutputPaths: producedChildOutputPaths,
5382
+ ...(workflowResource ? { authorize: (key, params) => authorizeWorkflowResourceHost(workflowResource!.permit, key, params.command) } : {}),
5383
+ });
5384
+ const workflowHost = delegatedWorkflowPermit
5385
+ ? undefined
5386
+ : workflowResource
5387
+ ? workflowResource.authority.host ? runHostCommand : undefined
5388
+ : publicExecution ? undefined : runHostCommand;
5292
5389
  const workflowHostSteps = new Map<string, HostStepNodeV1>();
5293
- let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [] };
5390
+ let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [], ...(workflowResource ? { resource: workflowResource.provenance } : {}) };
5294
5391
  let liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "running", inventoryComplete: false });
5295
5392
  const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
5296
5393
  const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(resolveCurrentSessionId(ctx.sessionManager)));
@@ -5302,11 +5399,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5302
5399
  const workflow = await runWorkflowScript({
5303
5400
  script: requestParams.workflowScript,
5304
5401
  ...(delegatedWorkflowPermit ? { oneUsePermit: { claim: (key: string) => claimWorkflowChildPermit(delegatedWorkflowPermit, _id, key) } } : {}),
5305
- globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
5402
+ globalConcurrencyLimit: requestParams.globalConcurrencyLimit ?? deps.config.globalConcurrencyLimit,
5306
5403
  timeoutMs: timeout,
5307
5404
  signal,
5308
5405
  ...(workflowState ? { state: workflowState } : {}),
5309
- ...(!delegatedWorkflowPermit ? { host: runHostCommand } : {}),
5406
+ ...(workflowHost ? { host: workflowHost } : {}),
5310
5407
  onHostStep: (hostStep) => {
5311
5408
  workflowHostSteps.set(hostStep.id, { ...hostStep, ...(hostStep.freshness ? { freshness: { ...hostStep.freshness } } : {}) });
5312
5409
  },
@@ -5403,7 +5500,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5403
5500
  const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, workflow.trace, { settled: true });
5404
5501
  const finalPreflightTrace = annotateWorkflowPreflightTrace(workflow.trace, workflowPreflight);
5405
5502
  const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "completed", inventoryComplete: true, trace: workflow.trace, children: workflow.children });
5406
- const receipt = terminalWorkflowReceipt(_id, "complete", workflow.children, workflowChildren, undefined, [...workflowHostSteps.values()]);
5503
+ const receipt = terminalWorkflowReceipt(_id, "complete", workflow.children, workflowChildren, undefined, [...workflowHostSteps.values()], workflowResource?.provenance);
5407
5504
  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}` : ""}`);
5408
5505
  const sections = [
5409
5506
  ...(workflowPreflight ? [formatWorkflowPreflight(workflowPreflight)] : []),
@@ -5421,7 +5518,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5421
5518
  const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
5422
5519
  return attachWorkflowMission(withRunFanoutBudget({
5423
5520
  content: [{ type: "text", text: displayText }],
5424
- details: compactOptional<Details>({ mode: "workflow", runId: _id, results: workflow.children.flatMap((child) => (child.results ?? []) as SingleResult[]), ...(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, ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}), receipt }, chatProgress }),
5521
+ details: compactOptional<Details>({ mode: "workflow", runId: _id, results: workflow.children.flatMap((child) => (child.results ?? []) as SingleResult[]), ...(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 }),
5425
5522
  }, workflowFanoutBudget));
5426
5523
  } catch (error) {
5427
5524
  const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
@@ -5444,11 +5541,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5444
5541
  const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
5445
5542
  const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "failed", inventoryComplete: true, trace: partial.trace, children: partial.children });
5446
5543
  const terminalOutcome = workflowFailureTerminalOutcome(error, partial.children, usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)));
5447
- const receipt = terminalWorkflowReceipt(_id, "failed", partial.children, workflowChildren, terminalOutcome, [...workflowHostSteps.values()]);
5544
+ const receipt = terminalWorkflowReceipt(_id, "failed", partial.children, workflowChildren, terminalOutcome, [...workflowHostSteps.values()], workflowResource?.provenance);
5448
5545
  return attachWorkflowMission(withRunFanoutBudget({
5449
5546
  content: [{ type: "text", text: displayText }],
5450
5547
  isError: true,
5451
- details: compactOptional<Details>({ mode: "workflow", runId: _id, results: partial.children.flatMap((child) => (child.results ?? []) as SingleResult[]), ...(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, ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}), receipt }, chatProgress }),
5548
+ details: compactOptional<Details>({ mode: "workflow", runId: _id, results: partial.children.flatMap((child) => (child.results ?? []) as SingleResult[]), ...(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 }),
5452
5549
  }, workflowFanoutBudget));
5453
5550
  }
5454
5551
  }
@@ -6208,34 +6305,47 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6208
6305
  let forkSessionFileForIndex: (idx?: number) => string | undefined = () => undefined;
6209
6306
  let prepareForkSessionForIndex: (idx?: number) => Promise<void> = async () => {};
6210
6307
  let forkThinkingOverrideForIndex: (idx?: number) => AgentConfig["thinking"] | undefined = () => undefined;
6211
- let prepareForkThinking = (_agentName: string, _index: number, _modelOverride?: string, _modelOverrideFromParent?: boolean): void => {};
6308
+ let prepareForkThinking = (_agentName: string, _index: number, _modelOverride?: string, _modelOverrideFromParent?: boolean, _modelOrigin?: ModelOrigin): void => {};
6212
6309
  const forkThinkingRequirements = new Map<number, boolean>();
6213
6310
  const forkThinkingDowngrades = new Map<number, string>();
6214
6311
  try {
6215
6312
  const forkAvailableModels = contextPolicy.usesFork ? ctx.modelRegistry.getAvailable().map(toModelInfo) : [];
6216
6313
  const parentModel = requestParentModel;
6217
- prepareForkThinking = (agentName, index, modelOverride, modelOverrideFromParent) => {
6314
+ prepareForkThinking = (agentName, index, modelOverride, modelOverrideFromParent, storedOrigin) => {
6218
6315
  const agentConfig = agents.find((agent) => agent.name === agentName);
6219
6316
  if (agentConfig?.runner?.type === "external-cli" || agentConfig?.runner?.type === "external-job") {
6220
6317
  forkThinkingRequirements.set(index, true);
6221
6318
  return;
6222
6319
  }
6320
+ const effectiveStoredOrigin = storedOrigin === "configured" && modelOverride === undefined && agentConfig?.model === undefined
6321
+ ? undefined
6322
+ : storedOrigin;
6323
+ const origin = resolveModelOrigin({
6324
+ fromParent: modelOverrideFromParent,
6325
+ storedOrigin: effectiveStoredOrigin,
6326
+ explicitModel: modelOverrideFromParent || effectiveStoredOrigin === "configured" ? undefined : modelOverride,
6327
+ agentModel: agentConfig?.model,
6328
+ parentModel,
6329
+ });
6223
6330
  const primaryModel = modelOverrideFromParent
6224
6331
  ? modelOverride
6225
6332
  : resolveEffectiveSubagentModel(
6226
- modelOverride,
6333
+ effectiveStoredOrigin === "configured" ? undefined : modelOverride,
6227
6334
  agentConfig?.model,
6228
6335
  parentModel,
6229
6336
  forkAvailableModels,
6230
6337
  agentConfig?.modelProvider ?? parentModel?.provider,
6231
- { source: "inherited" },
6338
+ { source: origin === "explicit" ? "explicit" : "inherited" },
6232
6339
  );
6233
6340
  const candidates = buildModelCandidates(
6234
6341
  primaryModel,
6235
6342
  agentConfig?.fallbackModels,
6236
6343
  forkAvailableModels,
6237
6344
  agentConfig?.modelProvider ?? parentModel?.provider,
6238
- { primaryModelFromParent: modelOverrideFromParent ?? inheritsParentModel(modelOverride, agentConfig?.model, parentModel) },
6345
+ {
6346
+ primaryModelFromParent: origin === "inherited",
6347
+ origin,
6348
+ },
6239
6349
  );
6240
6350
  forkThinkingRequirements.set(
6241
6351
  index,
@@ -6353,25 +6463,25 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6353
6463
  }
6354
6464
  const sessionDirForIndex = (idx?: number) =>
6355
6465
  path.join(sessionRoot, `run-${idx ?? 0}`);
6356
- const forkSessionFileForTask: ForkSessionFileForTask = (agentName, idx = 0, modelOverride, modelOverrideFromParent) => {
6466
+ const forkSessionFileForTask: ForkSessionFileForTask = (agentName, idx = 0, modelOverride, modelOverrideFromParent, modelOrigin) => {
6357
6467
  if (!shouldForkAgent(contextPolicy, agentName)) return undefined;
6358
- prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
6468
+ prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent, modelOrigin);
6359
6469
  return forkSessionFileForIndex(idx);
6360
6470
  };
6361
- const prepareForkSessionForTask: PrepareForkSessionForTask = async (agentName, idx = 0, modelOverride, modelOverrideFromParent) => {
6471
+ const prepareForkSessionForTask: PrepareForkSessionForTask = async (agentName, idx = 0, modelOverride, modelOverrideFromParent, modelOrigin) => {
6362
6472
  if (!shouldForkAgent(contextPolicy, agentName)) return;
6363
- prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
6473
+ prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent, modelOrigin);
6364
6474
  await prepareForkSessionForIndex(idx);
6365
6475
  };
6366
- const forkThinkingOverrideForTask: ForkThinkingOverrideForTask = (agentName, idx = 0, modelOverride, modelOverrideFromParent) => {
6476
+ const forkThinkingOverrideForTask: ForkThinkingOverrideForTask = (agentName, idx = 0, modelOverride, modelOverrideFromParent, modelOrigin) => {
6367
6477
  if (!shouldForkAgent(contextPolicy, agentName)) return delegatedThinkingOverride;
6368
- prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
6478
+ prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent, modelOrigin);
6369
6479
  const override = forkThinkingOverrideForIndex(idx);
6370
6480
  if (override === "off") forkThinkingDowngrades.set(idx, agentName);
6371
6481
  return override ?? delegatedThinkingOverride;
6372
6482
  };
6373
- const childSessionFileForTask: ForkSessionFileForTask = (agentName, idx, modelOverride, modelOverrideFromParent) =>
6374
- forkSessionFileForTask(agentName, idx, modelOverride, modelOverrideFromParent) ?? path.join(sessionDirForIndex(idx), "session.jsonl");
6483
+ const childSessionFileForTask: ForkSessionFileForTask = (agentName, idx, modelOverride, modelOverrideFromParent, modelOrigin) =>
6484
+ forkSessionFileForTask(agentName, idx, modelOverride, modelOverrideFromParent, modelOrigin) ?? path.join(sessionDirForIndex(idx), "session.jsonl");
6375
6485
  const childSessionFileForIndex = (idx?: number) =>
6376
6486
  path.join(sessionDirForIndex(idx), "session.jsonl");
6377
6487
  try {
@@ -6465,6 +6575,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6465
6575
  effectiveAsync,
6466
6576
  asyncRunId,
6467
6577
  controlConfig,
6578
+ ...(delegatedExecution ? { suppressUnchangedDelegationUpdates: true } : {}),
6468
6579
  intercomBridge,
6469
6580
  nestedRoute,
6470
6581
  timeoutMs: foregroundTimeout.timeoutMs,
@@ -6740,10 +6851,19 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6740
6851
  if (!normalized.ok) {
6741
6852
  return Promise.resolve({ content: [{ type: "text", text: normalized.error }], isError: true, details: { mode: normalized.mode, results: [] } });
6742
6853
  }
6743
- const loaded = loadWorkflowScriptPath(normalized.params, ctx.cwd);
6854
+ let publicParams = normalized.params as SubagentParamsLike;
6855
+ if (publicParams.workflow !== undefined) {
6856
+ const resolved = resolveWorkflowResource(publicParams.workflow, publicParams.args);
6857
+ if (!resolved.ok) return Promise.resolve({ content: [{ type: "text", text: resolved.error }], isError: true, details: { mode: "workflow", results: [] } });
6858
+ const { workflow: _workflow, args: _args, ...withoutResourceInput } = publicParams;
6859
+ publicParams = { ...withoutResourceInput, workflowScript: resolved.resource.script };
6860
+ workflowResourcePermits.set(publicParams, resolved.resource.permit);
6861
+ }
6862
+ const loaded = loadWorkflowScriptPath(publicParams, ctx.cwd);
6744
6863
  if (loaded.error) {
6745
- return Promise.resolve({ content: [{ type: "text", text: loaded.error }], isError: true, details: { mode: normalized.params.action ? "management" : "workflow", results: [] } });
6864
+ return Promise.resolve({ content: [{ type: "text", text: loaded.error }], isError: true, details: { mode: publicParams.action ? "management" : "workflow", results: [] } });
6746
6865
  }
6866
+ publicExecutions.add(loaded.params!);
6747
6867
  return executeWithSingleDispatchGuard(id, loaded.params!, signal, onUpdate, ctx);
6748
6868
  };
6749
6869
 
@@ -6788,7 +6908,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6788
6908
  });
6789
6909
  scheduledOwnerExecutors.set(ownerSessionId, ownerExecutor);
6790
6910
  }
6791
- return ownerExecutor.execute(id, params, signal, undefined, ctx);
6911
+ return ownerExecutor.executePublic(id, params, signal, undefined, ctx);
6792
6912
  };
6793
6913
 
6794
6914
  return { execute: executeWithSingleDispatchGuard, executePublic, executeDelegated, executeScheduled };