pi-subagents 0.60.0 → 0.62.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 (76) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/docs/agents.md +7 -3
  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 +13 -4
  8. package/docs/workflows.md +14 -13
  9. package/install.mjs +1 -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 +41 -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 +121 -63
  18. package/src/agents/agent-serializer.ts +3 -0
  19. package/src/agents/agents.ts +543 -223
  20. package/src/agents/runtime-agent-registry.ts +5 -1
  21. package/src/api/background-work.ts +7 -2
  22. package/src/api/external-runs.ts +67 -4
  23. package/src/api/preflight.ts +17 -8
  24. package/src/api/shared-types.ts +1 -0
  25. package/src/extension/index.ts +7 -4
  26. package/src/extension/public-execution.ts +48 -4
  27. package/src/extension/rpc.ts +62 -4
  28. package/src/extension/schemas.ts +17 -9
  29. package/src/extension/tool-description.ts +15 -19
  30. package/src/runs/background/active-async-capacity.ts +26 -7
  31. package/src/runs/background/async-execution.ts +98 -51
  32. package/src/runs/background/async-job-tracker.ts +62 -3
  33. package/src/runs/background/async-resume.ts +6 -3
  34. package/src/runs/background/async-status.ts +59 -9
  35. package/src/runs/background/auto-drain.ts +1 -1
  36. package/src/runs/background/fleet-view.ts +1 -1
  37. package/src/runs/background/process-terminal.ts +16 -0
  38. package/src/runs/background/result-watcher.ts +1 -1
  39. package/src/runs/background/resume-guidance.ts +1 -1
  40. package/src/runs/background/run-status.ts +2 -2
  41. package/src/runs/background/scheduled-runs.ts +63 -6
  42. package/src/runs/background/steering.ts +4 -1
  43. package/src/runs/background/subagent-runner.ts +15 -9
  44. package/src/runs/background/subagent-wait.ts +20 -21
  45. package/src/runs/background/wait-completions.ts +1 -1
  46. package/src/runs/background/wait-tool.ts +18 -18
  47. package/src/runs/foreground/execution.ts +75 -9
  48. package/src/runs/foreground/subagent-executor.ts +181 -60
  49. package/src/runs/shared/acceptance.ts +113 -27
  50. package/src/runs/shared/capability-ceiling.ts +1 -0
  51. package/src/runs/shared/dynamic-fanout.ts +1 -1
  52. package/src/runs/shared/host-step-status.ts +1 -0
  53. package/src/runs/shared/model-fallback.ts +61 -17
  54. package/src/runs/shared/parallel-utils.ts +2 -6
  55. package/src/runs/shared/permissions.ts +1 -1
  56. package/src/runs/shared/pi-args.ts +24 -12
  57. package/src/runs/shared/pi-spawn.ts +69 -35
  58. package/src/runs/shared/structured-output.ts +33 -6
  59. package/src/runs/shared/subagent-prompt-runtime.ts +20 -3
  60. package/src/runs/shared/task-intent.ts +17 -6
  61. package/src/runs/shared/tool-timeout.ts +1 -1
  62. package/src/runs/shared/workflow-graph.ts +3 -2
  63. package/src/shared/atomic-json.ts +3 -1
  64. package/src/shared/fork-context.ts +0 -12
  65. package/src/shared/fork-session-cwd.ts +27 -0
  66. package/src/shared/launch-contract.ts +3 -0
  67. package/src/shared/types.ts +43 -4
  68. package/src/shared/workflow-child-permit.ts +91 -0
  69. package/src/slash/prompt-template-bridge.ts +37 -1
  70. package/src/slash/slash-commands.ts +18 -26
  71. package/src/slash/subagents-admin.ts +2 -0
  72. package/src/tui/render.ts +95 -52
  73. package/src/workflows/scripted-workflow.ts +153 -4
  74. package/src/workflows/workflow-child-summary.ts +1 -1
  75. package/src/workflows/workflow-receipt.ts +41 -4
  76. 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";
@@ -50,7 +50,7 @@ import { acquireActiveAsyncCapacity, ActiveAsyncCapacityError, getActiveAsyncCap
50
50
  import { isScheduledRunAction } from "../background/scheduled-runs.ts";
51
51
  import { enqueueChainAppendRequest, readPendingChainAppendRequests, runnerStepOutputNames } from "../background/chain-append.ts";
52
52
  import { ChainOutputValidationError, validateChainOutputBindingsWithContext } from "../shared/chain-outputs.ts";
53
- import { normalizeGateAcceptance, validateExecutionAcceptance } from "../shared/acceptance.ts";
53
+ import { normalizeGateAcceptance, resolveAcceptanceReportMode, validateExecutionAcceptance } from "../shared/acceptance.ts";
54
54
  import { canPreferFork, createForkContextResolver, forkedChildRequiresThinkingOff, resolveSubagentLaunchContext } from "../../shared/fork-context.ts";
55
55
  import { createPrunedForkSessionWriter } from "../../shared/pruned-fork.ts";
56
56
  import { resolveCurrentSessionId } from "../../shared/session-identity.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;
@@ -375,6 +393,7 @@ export interface SubagentParamsLike {
375
393
  agentContract?: AgentContract;
376
394
  at?: string;
377
395
  every?: string;
396
+ sessionOnly?: boolean;
378
397
  on?: string | number;
379
398
  timezone?: string;
380
399
  overlap?: "skip";
@@ -418,9 +437,9 @@ interface ExecutorDeps {
418
437
  kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
419
438
  }
420
439
 
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;
440
+ type ForkSessionFileForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean, modelOrigin?: ModelOrigin) => string | undefined;
441
+ type PrepareForkSessionForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean, modelOrigin?: ModelOrigin) => Promise<void>;
442
+ type ForkThinkingOverrideForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean, modelOrigin?: ModelOrigin) => AgentConfig["thinking"] | undefined;
424
443
 
425
444
  interface ExecutionContextData {
426
445
  params: SubagentParamsLike;
@@ -447,6 +466,8 @@ interface ExecutionContextData {
447
466
  effectiveAsync: boolean;
448
467
  asyncRunId: string;
449
468
  controlConfig: ResolvedControlConfig;
469
+ /** Structured delegation consumers do not need duration-only heartbeat snapshots. */
470
+ suppressUnchangedDelegationUpdates?: boolean;
450
471
  intercomBridge: IntercomBridgeState;
451
472
  nestedRoute?: NestedRouteInfo;
452
473
  timeoutMs?: number;
@@ -865,7 +886,7 @@ function resolveForegroundResumeTarget(params: SubagentParamsLike, state: Subage
865
886
  if (matches.length === 0) return undefined;
866
887
  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
888
  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.`);
889
+ 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
890
  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
891
  const index = params.index ?? 0;
871
892
  if (!Number.isInteger(index)) throw new Error(`Foreground run '${run.runId}' index must be an integer.`);
@@ -2024,6 +2045,7 @@ async function resumeAsyncRun(input: {
2024
2045
  modelOverride: recoveryDescriptor?.model ?? target.model,
2025
2046
  fast: recoveryDescriptor?.fast,
2026
2047
  modelOverrideFromParent: recoveryDescriptor?.modelOverrideFromParent,
2048
+ modelOrigin: recoveryDescriptor?.modelOrigin ?? (recoveryDescriptor?.modelOverrideFromParent ? "inherited" : undefined),
2027
2049
  thinkingOverride: recoveryDescriptor?.thinking ?? target.thinking,
2028
2050
  thinkingCeiling: recoveryDescriptor?.thinkingCeiling ?? ("thinkingCeiling" in target ? target.thinkingCeiling : undefined),
2029
2051
  extensionBindings: recoveryDescriptor?.extensionBindings ?? ("extensionBindings" in target ? target.extensionBindings : undefined),
@@ -2997,7 +3019,15 @@ async function preflightForkSessionsForStaticTasks(
2997
3019
  ): Promise<void> {
2998
3020
  if (!contextPolicy.usesFork) return;
2999
3021
  if (params.agent) {
3000
- if (shouldForkAgent(contextPolicy, params.agent)) await prepareSessionForTask(params.agent, 0, params.model);
3022
+ if (shouldForkAgent(contextPolicy, params.agent)) {
3023
+ await prepareSessionForTask(
3024
+ params.agent,
3025
+ 0,
3026
+ params.model,
3027
+ params.modelOrigin === "inherited",
3028
+ params.modelOrigin,
3029
+ );
3030
+ }
3001
3031
  return;
3002
3032
  }
3003
3033
  if (params.tasks) {
@@ -3175,10 +3205,19 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3175
3205
  return buildRequestedModeError(params, `Agent '${a.name}' uses runner.type='${a.runner.type}' and does not support fast mode.`);
3176
3206
  }
3177
3207
  const modelScopes = resolveModelScopesForAgent(data.modelScope, a.name, parentModel);
3208
+ const modelOrigin = resolveModelOrigin({
3209
+ storedOrigin: params.modelOrigin as ModelOrigin | undefined,
3210
+ explicitModel: params.model as string | undefined,
3211
+ agentModel: a.model,
3212
+ parentModel,
3213
+ });
3178
3214
  const modelOverride = a.runner?.type === "external-cli" || a.runner?.type === "external-job"
3179
3215
  ? 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);
3216
+ : resolveEffectiveSubagentModel(params.model as string | undefined, a.model, parentModel, availableModels, a.modelProvider ?? currentProvider, {
3217
+ ...(modelScopes.length === 0 ? {} : { scope: modelScopes }),
3218
+ source: modelOrigin === "explicit" ? "explicit" : "inherited",
3219
+ });
3220
+ const modelOverrideFromParent = modelOrigin === "inherited";
3182
3221
  const asyncResult = executeAsyncSingle(id, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
3183
3222
  agent: params.agent!,
3184
3223
  task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
@@ -3195,7 +3234,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3195
3234
  shareEnabled,
3196
3235
  activeAsyncCapacity: data.activeAsyncCapacity,
3197
3236
  sessionRoot,
3198
- sessionFile: sessionFileForTask(params.agent!, 0, modelOverride, modelOverrideFromParent),
3237
+ sessionFile: sessionFileForTask(params.agent!, 0, modelOverride, modelOverrideFromParent, modelOrigin),
3199
3238
  context: contextPolicy.contextForAgent(params.agent!),
3200
3239
  skills,
3201
3240
  output: effectiveOutput,
@@ -3206,7 +3245,8 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3206
3245
  modelOverride,
3207
3246
  fast: params.fast,
3208
3247
  modelOverrideFromParent,
3209
- thinkingOverride: externalRunnerWithoutExplicitModel ? undefined : thinkingOverrideForTask(params.agent!, 0, modelOverride, modelOverrideFromParent),
3248
+ modelOrigin,
3249
+ thinkingOverride: externalRunnerWithoutExplicitModel ? undefined : thinkingOverrideForTask(params.agent!, 0, modelOverride, modelOverrideFromParent, modelOrigin),
3210
3250
  thinkingCeiling: a.maxThinking,
3211
3251
  maxSubagentDepth,
3212
3252
  waitToolEnabled: deps.waitToolEnabled,
@@ -3311,8 +3351,11 @@ function workflowHostCommandRunner(input: {
3311
3351
  workflowRunId: string;
3312
3352
  claimedOutputPaths: Map<string, string>;
3313
3353
  producedOutputPaths: Set<string>;
3354
+ authorize?: (key: string, params: WorkflowHostCommandParams) => string | undefined;
3314
3355
  }) {
3315
3356
  return async (key: string, params: WorkflowHostCommandParams, signal: AbortSignal): Promise<WorkflowHostCommandResult> => {
3357
+ const authorizationError = input.authorize?.(key, params);
3358
+ if (authorizationError) throw new Error(authorizationError);
3316
3359
  const defaultOutputPath = path.join(input.artifactsDir, "outputs", sanitizeRunPathSegment(input.workflowRunId), "host", `${sanitizeRunPathSegment(key)}.log`);
3317
3360
  const outputPath = params.output ? path.resolve(input.workflowCwd, params.output) : defaultOutputPath;
3318
3361
  const claimPath = resolveWorkflowHostOutputClaimPath(outputPath);
@@ -3552,6 +3595,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3552
3595
  onUpdate,
3553
3596
  controlConfig,
3554
3597
  contextPolicy,
3598
+ suppressUnchangedDelegationUpdates,
3555
3599
  } = data;
3556
3600
  let lane: import("../../shared/types.ts").WorkflowLaneMetadata | undefined;
3557
3601
  try {
@@ -3581,15 +3625,24 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3581
3625
  const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
3582
3626
  const modelScopes = resolveModelScopesForAgent(data.modelScope, agentConfig.name, parentModel);
3583
3627
  let task = typeof params.task === "string" ? params.task : "";
3628
+ const modelOrigin = resolveModelOrigin({
3629
+ storedOrigin: params.modelOrigin as ModelOrigin | undefined,
3630
+ explicitModel: params.model as string | undefined,
3631
+ agentModel: agentConfig.model,
3632
+ parentModel,
3633
+ });
3584
3634
  let modelOverride: string | undefined = resolveEffectiveSubagentModel(
3585
3635
  params.model as string | undefined,
3586
3636
  agentConfig.model,
3587
3637
  parentModel,
3588
3638
  availableModels,
3589
3639
  agentConfig.modelProvider ?? currentProvider,
3590
- modelScopes.length === 0 ? {} : { scope: modelScopes },
3640
+ {
3641
+ ...(modelScopes.length === 0 ? {} : { scope: modelScopes }),
3642
+ source: modelOrigin === "explicit" ? "explicit" : "inherited",
3643
+ },
3591
3644
  );
3592
- const modelOverrideFromParent = inheritsParentModel(params.model as string | undefined, agentConfig.model, parentModel);
3645
+ const modelOverrideFromParent = modelOrigin === "inherited";
3593
3646
  let skillOverride: string[] | false | undefined = normalizeSkillInput(params.skill);
3594
3647
  let readsOverride: string[] | false | undefined = params.reads;
3595
3648
  const rawOutput = params.output !== undefined ? params.output : agentConfig.output;
@@ -3638,7 +3691,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3638
3691
  return { content: [{ type: "text", text: validationError }], isError: true, details: { mode: "single", results: [] } };
3639
3692
  }
3640
3693
  const structuredRuntime = params.outputSchema
3641
- ? createStructuredOutputRuntime(params.outputSchema, artifactConfig.enabled ? path.join(artifactsDir, "structured-output", runId) : undefined, { captureAcceptanceReport: params.acceptance !== false })
3694
+ ? createStructuredOutputRuntime(params.outputSchema, artifactConfig.enabled ? path.join(artifactsDir, "structured-output", runId) : undefined, { acceptanceReport: resolveAcceptanceReportMode(params.acceptance) })
3642
3695
  : undefined;
3643
3696
  // Reads: caller override > agent defaultReads > none. `~`/`~/` expand to home;
3644
3697
  // absolute paths pass through; relative paths resolve against the child cwd.
@@ -3660,7 +3713,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3660
3713
  let detachForeground: ((reason?: string) => boolean) | undefined;
3661
3714
  const foregroundControl = deps.state.foregroundControls.get(runId);
3662
3715
  if (foregroundControl) {
3663
- const thinking = resolveEffectiveThinking(modelOverride, thinkingOverrideForTask(params.agent!, 0, modelOverride, modelOverrideFromParent));
3716
+ const thinking = resolveEffectiveThinking(modelOverride, thinkingOverrideForTask(params.agent!, 0, modelOverride, modelOverrideFromParent, modelOrigin));
3664
3717
  beginForegroundChild(foregroundControl, omitUndefinedProperties({
3665
3718
  index: 0,
3666
3719
  agent: params.agent!,
@@ -3712,7 +3765,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3712
3765
  intercomEvents: deps.pi.events,
3713
3766
  runId,
3714
3767
  sessionDir: sessionDirForIndex(0),
3715
- sessionFile: sessionFileForTask(params.agent!, 0, modelOverride, modelOverrideFromParent),
3768
+ sessionFile: sessionFileForTask(params.agent!, 0, modelOverride, modelOverrideFromParent, modelOrigin),
3716
3769
  share: shareEnabled,
3717
3770
  artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
3718
3771
  artifactConfig,
@@ -3724,6 +3777,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3724
3777
  waitToolEnabled: deps.waitToolEnabled,
3725
3778
  waitToolDefaultTimeoutMs: deps.waitToolDefaultTimeoutMs,
3726
3779
  onUpdate: forwardSingleUpdate,
3780
+ suppressUnchangedDelegationUpdates,
3727
3781
  controlConfig,
3728
3782
  onControlEvent,
3729
3783
  intercomSessionName: childIntercomTarget,
@@ -3733,7 +3787,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3733
3787
  modelOverride,
3734
3788
  fast: params.fast,
3735
3789
  modelOverrideFromParent,
3736
- thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride, modelOverrideFromParent),
3790
+ modelOrigin,
3791
+ thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride, modelOverrideFromParent, modelOrigin),
3737
3792
  thinkingCeiling: agentConfig.maxThinking,
3738
3793
  extensionBindings: params.extensionBindings,
3739
3794
  availableModels,
@@ -3880,11 +3935,11 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3880
3935
  const worktreeSuffix = worktreeHandoff?.suffix ? `\n\n${worktreeHandoff.suffix}` : "";
3881
3936
  if (r.detached) {
3882
3937
  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}`;
3938
+ const blockingRecovery = `bg_wait({ id: "${runId}" }). Use ${statusRecovery}`;
3884
3939
  const message = r.detachedReason === "intercom coordination"
3885
3940
  ? `Detached for intercom coordination: ${params.agent}. Reply to the supervisor request first, then wait with ${blockingRecovery}`
3886
3941
  : 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}`
3942
+ ? `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
3943
  : `Detached before task completion: ${params.agent}. Wait with ${blockingRecovery}`;
3889
3944
  return {
3890
3945
  content: [{ type: "text", text: `${message}${worktreeSuffix}` }],
@@ -4022,7 +4077,8 @@ function workflowChildResult(
4022
4077
  : result.details.usageBudget?.exhausted || result.details.results.some((child) => child.turnBudgetExceeded || child.toolBudgetBlocked)
4023
4078
  ? { state: "partial" as const, reason: "budget_exhausted" as const }
4024
4079
  : undefined);
4025
- const ok = result.isError !== true && !detached && !interrupted && !stopped;
4080
+ const acceptanceRecovery = result.details.results.find((child) => child.acceptance?.recovery)?.acceptance?.recovery;
4081
+ const ok = result.isError !== true && !detached && !interrupted && !stopped && acceptanceRecovery === undefined;
4026
4082
  const artifactPaths = new Set<string>();
4027
4083
  if (result.details.asyncDir) artifactPaths.add(result.details.asyncDir);
4028
4084
  if (result.details.parallelHandoff?.path) artifactPaths.add(result.details.parallelHandoff.path);
@@ -4080,6 +4136,7 @@ function workflowChildResult(
4080
4136
  ...(requestedContext ? { requestedContext } : {}),
4081
4137
  ...(resolvedContext ? { resolvedContext } : {}),
4082
4138
  ...(outputReference ? { outputReference } : {}),
4139
+ ...(acceptanceRecovery ? { recovery: acceptanceRecovery } : {}),
4083
4140
  ...(outputPathMapping ? { outputPathMapping } : {}),
4084
4141
  ...(externalAdapter ? { externalAdapter } : {}),
4085
4142
  resumability,
@@ -4089,13 +4146,14 @@ function workflowChildResult(
4089
4146
  };
4090
4147
  }
4091
4148
 
4092
- function workflowChildAccountingFields(child: WorkflowScriptChildResult): { usage?: Usage; sessionFile?: string } {
4149
+ function workflowChildAccountingFields(child: WorkflowScriptChildResult): { usage?: Usage; sessionFile?: string; recovery?: import("../../shared/types.ts").AcceptanceRecoveryMetadata } {
4093
4150
  if (!child.results?.length) return {};
4094
4151
  const usage = sumResultsUsage(child.results);
4095
4152
  const sessionFile = child.results.find((result) => result.sessionFile)?.sessionFile;
4096
4153
  return {
4097
4154
  ...(usage.input !== 0 || usage.output !== 0 || usage.cacheRead !== 0 || usage.cacheWrite !== 0 || usage.cost !== 0 || usage.turns !== 0 ? { usage } : {}),
4098
4155
  ...(sessionFile ? { sessionFile } : {}),
4156
+ ...(child.recovery ? { recovery: child.recovery } : {}),
4099
4157
  };
4100
4158
  }
4101
4159
 
@@ -4223,8 +4281,9 @@ function terminalWorkflowReceipt(
4223
4281
  workflowChildren?: WorkflowReceipt["workflowChildren"],
4224
4282
  terminalOutcome?: WorkflowTerminalOutcome,
4225
4283
  hostSteps?: WorkflowReceipt["hostSteps"],
4284
+ resource?: WorkflowReceipt["resource"],
4226
4285
  ): WorkflowReceipt {
4227
- return buildWorkflowReceipt({ workflowRunId, state, children, workflowChildren, terminalOutcome, hostSteps });
4286
+ return buildWorkflowReceipt({ workflowRunId, state, children, workflowChildren, terminalOutcome, hostSteps, resource });
4228
4287
  }
4229
4288
 
4230
4289
  function workflowFailureTerminalOutcome(error: unknown, _children: WorkflowScriptChildResult[], usageBudget: ReturnType<typeof usageBudgetState>): WorkflowTerminalOutcome | undefined {
@@ -4316,6 +4375,10 @@ export function prepareWorkflowLaunchParams(
4316
4375
  workflowKey: string,
4317
4376
  options: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; awaitDetachedChild?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor; parentDeadlineAt?: number; externalAsyncRequired?: boolean; capabilityCeiling?: ResolvedSubagentCapabilityCeiling; outputClaimPath?: string } = {},
4318
4377
  ): SubagentParamsLike {
4378
+ const { globalConcurrencyLimit: _globalConcurrencyLimit, maxSubagentSpawnsPerRun: _maxSubagentSpawnsPerRun, ...workflowDefaultsWithoutCapacity } = workflowDefaults;
4379
+ const { globalConcurrencyLimit: _childGlobalConcurrencyLimit, maxSubagentSpawnsPerRun: _childMaxSubagentSpawnsPerRun, ...childParamsWithoutCapacity } = childParams;
4380
+ workflowDefaults = workflowDefaultsWithoutCapacity;
4381
+ childParams = childParamsWithoutCapacity;
4319
4382
  const capabilityCeiling = intersectSubagentCapabilityCeilings(workflowDefaults.capabilityCeiling, options.capabilityCeiling);
4320
4383
  const lane = normalizeWorkflowLaneMetadata(Object.hasOwn(childParams, "lane") ? childParams.lane : workflowDefaults.lane, `workflow child '${workflowKey}'.lane`);
4321
4384
  assertWorkflowLaneKey(lane, workflowKey, `workflow child '${workflowKey}'.lane`);
@@ -4586,6 +4649,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4586
4649
  const delegatedThinkingOverrides = new WeakMap<object, AgentConfig["thinking"]>();
4587
4650
  const delegatedZeroToolBudgets = new WeakSet<object>();
4588
4651
  const delegatedExecutions = new WeakSet<object>();
4652
+ const publicExecutions = new WeakSet<object>();
4653
+ const workflowResourcePermits = new WeakMap<object, WorkflowResourcePermit>();
4589
4654
  const workflowPermitContexts = new WeakMap<object, { root: WorkflowChildPermit } | { child: WorkflowChildPermitContext }>();
4590
4655
  const warnedArtifactPackageDirs = new Set<string>();
4591
4656
  const scheduledOwnerExecutors = new Map<string, ReturnType<typeof createSubagentExecutor>>();
@@ -4602,6 +4667,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4602
4667
  const delegatedThinkingOverride = delegatedThinkingOverrides.get(params);
4603
4668
  const allowZeroToolBudget = delegatedZeroToolBudgets.has(params);
4604
4669
  const delegatedExecution = delegatedExecutions.has(params);
4670
+ const publicExecution = publicExecutions.has(params);
4671
+ const workflowResourcePermit = workflowResourcePermits.get(params);
4605
4672
  const workflowPermitContext = workflowPermitContexts.get(params);
4606
4673
  const delegatedWorkflowPermit = workflowPermitContext && "root" in workflowPermitContext ? workflowPermitContext.root : undefined;
4607
4674
  const workflowChildPermitLaunch = workflowPermitContext && "child" in workflowPermitContext ? workflowPermitContext.child : undefined;
@@ -4612,6 +4679,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4612
4679
  const normalizedGate = normalizeGateParams(params);
4613
4680
  if (!normalizedGate.ok) return buildRequestedModeError(params, normalizedGate.error);
4614
4681
  let requestParams = normalizedGate.params;
4682
+ const capacityOverrideError = validateWorkflowCapacityOverrides(requestParams);
4683
+ if (capacityOverrideError) return buildRequestedModeError(requestParams, capacityOverrideError);
4615
4684
  let workflowPreflight: import("../../shared/types.ts").WorkflowPreflightV1 | undefined;
4616
4685
  try {
4617
4686
  if (requestParams.preflight !== undefined && requestParams.workflowScript === undefined && requestParams.workflowScriptPath === undefined) {
@@ -4643,6 +4712,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4643
4712
  }
4644
4713
  const normalizedAction = typeof requestParams.action === "string" ? requestParams.action.trim() : requestParams.action;
4645
4714
  if (normalizedAction === "resume" && requestParams.extensionBindings !== undefined) return buildRequestedModeError(requestParams, "extensionBindings is not supported with action='resume'; resume uses the original retained child binding.");
4715
+ let workflowResource: { permit: WorkflowResourcePermit; provenance: WorkflowResourceProvenanceV1; authority: WorkflowResourceAuthority } | undefined;
4716
+ if (workflowResourcePermit) {
4717
+ if (typeof requestParams.workflowScript !== "string") return buildRequestedModeError(requestParams, "Resolved workflow resource is missing its workflow script.");
4718
+ const consumed = consumeWorkflowResourcePermit(workflowResourcePermit, requestParams.workflowScript);
4719
+ if (typeof consumed === "string") return buildRequestedModeError(requestParams, consumed);
4720
+ workflowResource = { permit: workflowResourcePermit, ...consumed };
4721
+ }
4646
4722
  if (requestParams.workflowScript !== undefined && normalizedAction === undefined) {
4647
4723
  if (delegatedWorkflowPermit) {
4648
4724
  const permitError = validateWorkflowChildPermitRoot(delegatedWorkflowPermit, _id);
@@ -4722,7 +4798,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4722
4798
  try {
4723
4799
  workflowFanoutBudget = requestParams.runFanoutBudget
4724
4800
  ?? decodeRunFanoutBudgetDescriptor(process.env[RUN_FANOUT_BUDGET_ENV])
4725
- ?? createRunFanoutBudget(_id, resolveMaxSubagentSpawnsPerRun(deps.config.maxSubagentSpawnsPerRun));
4801
+ ?? createRunFanoutBudget(_id, requestParams.maxSubagentSpawnsPerRun ?? resolveMaxSubagentSpawnsPerRun(deps.config.maxSubagentSpawnsPerRun));
4726
4802
  } catch (error) {
4727
4803
  workflowCapacity?.rollback();
4728
4804
  return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "workflow", results: [] } };
@@ -4800,7 +4876,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4800
4876
  pid: process.pid,
4801
4877
  steps: [],
4802
4878
  ...(workflowPreflight ? { preflight: workflowPreflight } : {}),
4803
- workflow: { trace: [], emits: [], console: [] },
4879
+ workflow: { trace: [], emits: [], console: [], ...(workflowResource ? { resource: workflowResource.provenance } : {}) },
4804
4880
  workflowChildren: workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState: "running", inventoryComplete: false }),
4805
4881
  runFanoutBudget: getRunFanoutBudgetSnapshot(workflowFanoutBudget),
4806
4882
  };
@@ -4952,7 +5028,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4952
5028
  const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
4953
5029
  const workflowResults: SingleResult[] = [];
4954
5030
  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;
5031
+ 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
5032
  const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
4957
5033
  const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
4958
5034
  const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, parentCwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, workflowRunId));
@@ -4961,7 +5037,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4961
5037
  const childOutputClaimPaths = new Map<string, string>();
4962
5038
  const producedChildOutputPaths = new Set<string>();
4963
5039
  const workflowSteps = new Map<string, NonNullable<AsyncStatus["steps"]>[number]>();
4964
- const runHostCommand = workflowHostCommandRunner({ workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId, claimedOutputPaths, producedOutputPaths: producedChildOutputPaths });
5040
+ const runHostCommand = workflowHostCommandRunner({
5041
+ workflowCwd,
5042
+ artifactsDir: workflowArtifactsDir,
5043
+ workflowRunId,
5044
+ claimedOutputPaths,
5045
+ producedOutputPaths: producedChildOutputPaths,
5046
+ ...(workflowResource ? { authorize: (key, params) => authorizeWorkflowResourceHost(workflowResource!.permit, key, params.command) } : {}),
5047
+ });
5048
+ const workflowHost = workflowResource
5049
+ ? workflowResource.authority.host ? runHostCommand : undefined
5050
+ : publicExecution ? undefined : runHostCommand;
4965
5051
  let projectedTraceLength = 0;
4966
5052
  let projectedTraceTail: NonNullable<Details["workflow"]>["trace"][number] | undefined;
4967
5053
  const updateTrace = (trace: NonNullable<Details["workflow"]>["trace"]) => {
@@ -5067,7 +5153,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5067
5153
  try {
5068
5154
  const workflow = await runWorkflowScript({
5069
5155
  script: workflowScript,
5070
- globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
5156
+ globalConcurrencyLimit: requestParams.globalConcurrencyLimit ?? deps.config.globalConcurrencyLimit,
5071
5157
  timeoutMs: timeout,
5072
5158
  signal: controller.signal,
5073
5159
  registerStopChild: (stop) => {
@@ -5081,7 +5167,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5081
5167
  applyWorkflowLaneTrace(status.workflowGraph, status.workflow?.trace ?? []);
5082
5168
  persist({ tolerateStatusWriteFailure: true });
5083
5169
  },
5084
- host: runHostCommand,
5170
+ ...(workflowHost ? { host: workflowHost } : {}),
5085
5171
  onHostStep: (hostStep) => {
5086
5172
  status = upsertHostStep({ status, hostStep, persist: (nextStatus) => {
5087
5173
  status = nextStatus;
@@ -5206,8 +5292,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5206
5292
  const resultSummary = appendWorkflowOutputWarning(summary, outputWarning);
5207
5293
  const workflowUsage = sumResultsUsage(workflowResults);
5208
5294
  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));
5295
+ 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) };
5296
+ const receipt = terminalWorkflowReceipt(workflowRunId, "complete", workflow.children, workflowChildren, undefined, validHostStepNodes(status.workflowGraph), workflowResource?.provenance);
5211
5297
  let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
5212
5298
  try {
5213
5299
  workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
@@ -5241,7 +5327,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5241
5327
  const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState, inventoryComplete: true, trace: partial.trace, children: partial.children, steps: status.steps });
5242
5328
  const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, partial.trace, { settled: true });
5243
5329
  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 });
5330
+ 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
5331
  if (pauseForDetached) {
5246
5332
  const promoted = promotePausedWorkflowIfSettled(status);
5247
5333
  if (promoted) status = promoted;
@@ -5253,7 +5339,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5253
5339
  const resultSummary = appendWorkflowOutputWarning(terminalSummary, outputWarning);
5254
5340
  const receiptState: WorkflowReceiptState = status.state === "complete" ? "complete" : status.state === "paused" ? "paused" : status.state === "stopped" ? "stopped" : "failed";
5255
5341
  const terminalOutcome = workflowFailureTerminalOutcome(error, partial.children, usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)));
5256
- const receipt = terminalWorkflowReceipt(workflowRunId, receiptState, partial.children, workflowChildren, terminalOutcome, validHostStepNodes(status.workflowGraph));
5342
+ const receipt = terminalWorkflowReceipt(workflowRunId, receiptState, partial.children, workflowChildren, terminalOutcome, validHostStepNodes(status.workflowGraph), workflowResource?.provenance);
5257
5343
  let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
5258
5344
  try {
5259
5345
  workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
@@ -5275,10 +5361,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5275
5361
  });
5276
5362
  return attachWorkflowMission(withRunFanoutBudget({
5277
5363
  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 } : {}) },
5364
+ 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
5365
  }, workflowFanoutBudget));
5280
5366
  }
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;
5367
+ 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
5368
  const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
5283
5369
  const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
5284
5370
  const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, _id));
@@ -5288,9 +5374,21 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5288
5374
  const producedChildOutputPaths = new Set<string>();
5289
5375
  const workflowResults: SingleResult[] = [];
5290
5376
  const workflowChildRunIds = new Map<string, string>();
5291
- const runHostCommand = workflowHostCommandRunner({ workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId: _id, claimedOutputPaths, producedOutputPaths: producedChildOutputPaths });
5377
+ const runHostCommand = workflowHostCommandRunner({
5378
+ workflowCwd,
5379
+ artifactsDir: workflowArtifactsDir,
5380
+ workflowRunId: _id,
5381
+ claimedOutputPaths,
5382
+ producedOutputPaths: producedChildOutputPaths,
5383
+ ...(workflowResource ? { authorize: (key, params) => authorizeWorkflowResourceHost(workflowResource!.permit, key, params.command) } : {}),
5384
+ });
5385
+ const workflowHost = delegatedWorkflowPermit
5386
+ ? undefined
5387
+ : workflowResource
5388
+ ? workflowResource.authority.host ? runHostCommand : undefined
5389
+ : publicExecution ? undefined : runHostCommand;
5292
5390
  const workflowHostSteps = new Map<string, HostStepNodeV1>();
5293
- let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [] };
5391
+ let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [], ...(workflowResource ? { resource: workflowResource.provenance } : {}) };
5294
5392
  let liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "running", inventoryComplete: false });
5295
5393
  const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
5296
5394
  const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(resolveCurrentSessionId(ctx.sessionManager)));
@@ -5302,11 +5400,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5302
5400
  const workflow = await runWorkflowScript({
5303
5401
  script: requestParams.workflowScript,
5304
5402
  ...(delegatedWorkflowPermit ? { oneUsePermit: { claim: (key: string) => claimWorkflowChildPermit(delegatedWorkflowPermit, _id, key) } } : {}),
5305
- globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
5403
+ globalConcurrencyLimit: requestParams.globalConcurrencyLimit ?? deps.config.globalConcurrencyLimit,
5306
5404
  timeoutMs: timeout,
5307
5405
  signal,
5308
5406
  ...(workflowState ? { state: workflowState } : {}),
5309
- ...(!delegatedWorkflowPermit ? { host: runHostCommand } : {}),
5407
+ ...(workflowHost ? { host: workflowHost } : {}),
5310
5408
  onHostStep: (hostStep) => {
5311
5409
  workflowHostSteps.set(hostStep.id, { ...hostStep, ...(hostStep.freshness ? { freshness: { ...hostStep.freshness } } : {}) });
5312
5410
  },
@@ -5403,7 +5501,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5403
5501
  const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, workflow.trace, { settled: true });
5404
5502
  const finalPreflightTrace = annotateWorkflowPreflightTrace(workflow.trace, workflowPreflight);
5405
5503
  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()]);
5504
+ const receipt = terminalWorkflowReceipt(_id, "complete", workflow.children, workflowChildren, undefined, [...workflowHostSteps.values()], workflowResource?.provenance);
5407
5505
  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
5506
  const sections = [
5409
5507
  ...(workflowPreflight ? [formatWorkflowPreflight(workflowPreflight)] : []),
@@ -5421,7 +5519,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5421
5519
  const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
5422
5520
  return attachWorkflowMission(withRunFanoutBudget({
5423
5521
  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 }),
5522
+ 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
5523
  }, workflowFanoutBudget));
5426
5524
  } catch (error) {
5427
5525
  const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
@@ -5444,11 +5542,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5444
5542
  const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
5445
5543
  const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "failed", inventoryComplete: true, trace: partial.trace, children: partial.children });
5446
5544
  const terminalOutcome = workflowFailureTerminalOutcome(error, partial.children, usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)));
5447
- const receipt = terminalWorkflowReceipt(_id, "failed", partial.children, workflowChildren, terminalOutcome, [...workflowHostSteps.values()]);
5545
+ const receipt = terminalWorkflowReceipt(_id, "failed", partial.children, workflowChildren, terminalOutcome, [...workflowHostSteps.values()], workflowResource?.provenance);
5448
5546
  return attachWorkflowMission(withRunFanoutBudget({
5449
5547
  content: [{ type: "text", text: displayText }],
5450
5548
  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 }),
5549
+ 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
5550
  }, workflowFanoutBudget));
5453
5551
  }
5454
5552
  }
@@ -6208,34 +6306,47 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6208
6306
  let forkSessionFileForIndex: (idx?: number) => string | undefined = () => undefined;
6209
6307
  let prepareForkSessionForIndex: (idx?: number) => Promise<void> = async () => {};
6210
6308
  let forkThinkingOverrideForIndex: (idx?: number) => AgentConfig["thinking"] | undefined = () => undefined;
6211
- let prepareForkThinking = (_agentName: string, _index: number, _modelOverride?: string, _modelOverrideFromParent?: boolean): void => {};
6309
+ let prepareForkThinking = (_agentName: string, _index: number, _modelOverride?: string, _modelOverrideFromParent?: boolean, _modelOrigin?: ModelOrigin): void => {};
6212
6310
  const forkThinkingRequirements = new Map<number, boolean>();
6213
6311
  const forkThinkingDowngrades = new Map<number, string>();
6214
6312
  try {
6215
6313
  const forkAvailableModels = contextPolicy.usesFork ? ctx.modelRegistry.getAvailable().map(toModelInfo) : [];
6216
6314
  const parentModel = requestParentModel;
6217
- prepareForkThinking = (agentName, index, modelOverride, modelOverrideFromParent) => {
6315
+ prepareForkThinking = (agentName, index, modelOverride, modelOverrideFromParent, storedOrigin) => {
6218
6316
  const agentConfig = agents.find((agent) => agent.name === agentName);
6219
6317
  if (agentConfig?.runner?.type === "external-cli" || agentConfig?.runner?.type === "external-job") {
6220
6318
  forkThinkingRequirements.set(index, true);
6221
6319
  return;
6222
6320
  }
6321
+ const effectiveStoredOrigin = storedOrigin === "configured" && modelOverride === undefined && agentConfig?.model === undefined
6322
+ ? undefined
6323
+ : storedOrigin;
6324
+ const origin = resolveModelOrigin({
6325
+ fromParent: modelOverrideFromParent,
6326
+ storedOrigin: effectiveStoredOrigin,
6327
+ explicitModel: modelOverrideFromParent || effectiveStoredOrigin === "configured" ? undefined : modelOverride,
6328
+ agentModel: agentConfig?.model,
6329
+ parentModel,
6330
+ });
6223
6331
  const primaryModel = modelOverrideFromParent
6224
6332
  ? modelOverride
6225
6333
  : resolveEffectiveSubagentModel(
6226
- modelOverride,
6334
+ effectiveStoredOrigin === "configured" ? undefined : modelOverride,
6227
6335
  agentConfig?.model,
6228
6336
  parentModel,
6229
6337
  forkAvailableModels,
6230
6338
  agentConfig?.modelProvider ?? parentModel?.provider,
6231
- { source: "inherited" },
6339
+ { source: origin === "explicit" ? "explicit" : "inherited" },
6232
6340
  );
6233
6341
  const candidates = buildModelCandidates(
6234
6342
  primaryModel,
6235
6343
  agentConfig?.fallbackModels,
6236
6344
  forkAvailableModels,
6237
6345
  agentConfig?.modelProvider ?? parentModel?.provider,
6238
- { primaryModelFromParent: modelOverrideFromParent ?? inheritsParentModel(modelOverride, agentConfig?.model, parentModel) },
6346
+ {
6347
+ primaryModelFromParent: origin === "inherited",
6348
+ origin,
6349
+ },
6239
6350
  );
6240
6351
  forkThinkingRequirements.set(
6241
6352
  index,
@@ -6353,25 +6464,25 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6353
6464
  }
6354
6465
  const sessionDirForIndex = (idx?: number) =>
6355
6466
  path.join(sessionRoot, `run-${idx ?? 0}`);
6356
- const forkSessionFileForTask: ForkSessionFileForTask = (agentName, idx = 0, modelOverride, modelOverrideFromParent) => {
6467
+ const forkSessionFileForTask: ForkSessionFileForTask = (agentName, idx = 0, modelOverride, modelOverrideFromParent, modelOrigin) => {
6357
6468
  if (!shouldForkAgent(contextPolicy, agentName)) return undefined;
6358
- prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
6469
+ prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent, modelOrigin);
6359
6470
  return forkSessionFileForIndex(idx);
6360
6471
  };
6361
- const prepareForkSessionForTask: PrepareForkSessionForTask = async (agentName, idx = 0, modelOverride, modelOverrideFromParent) => {
6472
+ const prepareForkSessionForTask: PrepareForkSessionForTask = async (agentName, idx = 0, modelOverride, modelOverrideFromParent, modelOrigin) => {
6362
6473
  if (!shouldForkAgent(contextPolicy, agentName)) return;
6363
- prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
6474
+ prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent, modelOrigin);
6364
6475
  await prepareForkSessionForIndex(idx);
6365
6476
  };
6366
- const forkThinkingOverrideForTask: ForkThinkingOverrideForTask = (agentName, idx = 0, modelOverride, modelOverrideFromParent) => {
6477
+ const forkThinkingOverrideForTask: ForkThinkingOverrideForTask = (agentName, idx = 0, modelOverride, modelOverrideFromParent, modelOrigin) => {
6367
6478
  if (!shouldForkAgent(contextPolicy, agentName)) return delegatedThinkingOverride;
6368
- prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
6479
+ prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent, modelOrigin);
6369
6480
  const override = forkThinkingOverrideForIndex(idx);
6370
6481
  if (override === "off") forkThinkingDowngrades.set(idx, agentName);
6371
6482
  return override ?? delegatedThinkingOverride;
6372
6483
  };
6373
- const childSessionFileForTask: ForkSessionFileForTask = (agentName, idx, modelOverride, modelOverrideFromParent) =>
6374
- forkSessionFileForTask(agentName, idx, modelOverride, modelOverrideFromParent) ?? path.join(sessionDirForIndex(idx), "session.jsonl");
6484
+ const childSessionFileForTask: ForkSessionFileForTask = (agentName, idx, modelOverride, modelOverrideFromParent, modelOrigin) =>
6485
+ forkSessionFileForTask(agentName, idx, modelOverride, modelOverrideFromParent, modelOrigin) ?? path.join(sessionDirForIndex(idx), "session.jsonl");
6375
6486
  const childSessionFileForIndex = (idx?: number) =>
6376
6487
  path.join(sessionDirForIndex(idx), "session.jsonl");
6377
6488
  try {
@@ -6465,6 +6576,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6465
6576
  effectiveAsync,
6466
6577
  asyncRunId,
6467
6578
  controlConfig,
6579
+ ...(delegatedExecution ? { suppressUnchangedDelegationUpdates: true } : {}),
6468
6580
  intercomBridge,
6469
6581
  nestedRoute,
6470
6582
  timeoutMs: foregroundTimeout.timeoutMs,
@@ -6740,10 +6852,19 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6740
6852
  if (!normalized.ok) {
6741
6853
  return Promise.resolve({ content: [{ type: "text", text: normalized.error }], isError: true, details: { mode: normalized.mode, results: [] } });
6742
6854
  }
6743
- const loaded = loadWorkflowScriptPath(normalized.params, ctx.cwd);
6855
+ let publicParams = normalized.params as SubagentParamsLike;
6856
+ if (publicParams.workflow !== undefined) {
6857
+ const resolved = resolveWorkflowResource(publicParams.workflow, publicParams.args);
6858
+ if (!resolved.ok) return Promise.resolve({ content: [{ type: "text", text: resolved.error }], isError: true, details: { mode: "workflow", results: [] } });
6859
+ const { workflow: _workflow, args: _args, ...withoutResourceInput } = publicParams;
6860
+ publicParams = { ...withoutResourceInput, workflowScript: resolved.resource.script };
6861
+ workflowResourcePermits.set(publicParams, resolved.resource.permit);
6862
+ }
6863
+ const loaded = loadWorkflowScriptPath(publicParams, ctx.cwd);
6744
6864
  if (loaded.error) {
6745
- return Promise.resolve({ content: [{ type: "text", text: loaded.error }], isError: true, details: { mode: normalized.params.action ? "management" : "workflow", results: [] } });
6865
+ return Promise.resolve({ content: [{ type: "text", text: loaded.error }], isError: true, details: { mode: publicParams.action ? "management" : "workflow", results: [] } });
6746
6866
  }
6867
+ publicExecutions.add(loaded.params!);
6747
6868
  return executeWithSingleDispatchGuard(id, loaded.params!, signal, onUpdate, ctx);
6748
6869
  };
6749
6870
 
@@ -6788,7 +6909,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6788
6909
  });
6789
6910
  scheduledOwnerExecutors.set(ownerSessionId, ownerExecutor);
6790
6911
  }
6791
- return ownerExecutor.execute(id, params, signal, undefined, ctx);
6912
+ return ownerExecutor.executePublic(id, params, signal, undefined, ctx);
6792
6913
  };
6793
6914
 
6794
6915
  return { execute: executeWithSingleDispatchGuard, executePublic, executeDelegated, executeScheduled };