@wichayutdew/pi-workflows 2.5.1 → 2.7.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 (103) hide show
  1. package/README.md +34 -1
  2. package/dist/index.js +1315 -2467
  3. package/examples/starter-kit/agents/planner.md +11 -0
  4. package/examples/starter-kit/agents/reviewer.md +10 -0
  5. package/examples/starter-kit/agents/scout.md +10 -0
  6. package/examples/starter-kit/agents/worker.md +11 -0
  7. package/examples/starter-kit/agents/workspace-preparer.md +10 -0
  8. package/examples/starter-kit/investigate.workflow.yaml +84 -0
  9. package/examples/starter-kit/jira.workflow.yaml +75 -0
  10. package/examples/starter-kit/mr-comment.workflow.yaml +86 -93
  11. package/examples/starter-kit/mr-review.workflow.yaml +52 -88
  12. package/examples/starter-kit/settings.yaml +4 -0
  13. package/examples/starter-kit/steps/investigate/investigate.md +40 -0
  14. package/examples/starter-kit/steps/investigate/retrieve.md +28 -0
  15. package/examples/starter-kit/steps/investigate/validate.md +20 -0
  16. package/examples/starter-kit/steps/jira/create.md +25 -0
  17. package/examples/starter-kit/steps/jira/draft.md +18 -0
  18. package/examples/starter-kit/steps/jira/plan.md +30 -0
  19. package/examples/starter-kit/steps/mr-comment/checkout-source.md +14 -0
  20. package/examples/starter-kit/steps/mr-comment/fetch.md +11 -28
  21. package/examples/starter-kit/steps/mr-comment/implement.md +9 -26
  22. package/examples/starter-kit/steps/mr-comment/plan.md +46 -55
  23. package/examples/starter-kit/steps/mr-comment/publish.md +11 -31
  24. package/examples/starter-kit/steps/mr-comment/verify.md +7 -34
  25. package/examples/starter-kit/steps/mr-review/fetch.md +13 -21
  26. package/examples/starter-kit/steps/mr-review/publish-approved.md +18 -0
  27. package/examples/starter-kit/steps/mr-review/review-for-approval.md +53 -0
  28. package/examples/starter-kit/steps/mr-review/verify-published.md +16 -0
  29. package/examples/starter-kit/steps/shared/prepare-workspace.md +11 -89
  30. package/examples/starter-kit/steps/shared/publish-remote.md +16 -0
  31. package/examples/starter-kit/steps/ticket/implement.md +10 -23
  32. package/examples/starter-kit/steps/ticket/plan.md +49 -98
  33. package/examples/starter-kit/steps/ticket/verify.md +14 -65
  34. package/examples/starter-kit/steps/work/implement.md +11 -22
  35. package/examples/starter-kit/steps/work/plan.md +43 -59
  36. package/examples/starter-kit/steps/work/verify.md +14 -25
  37. package/examples/starter-kit/ticket.workflow.yaml +57 -61
  38. package/examples/starter-kit/work.workflow.yaml +49 -57
  39. package/package.json +9 -19
  40. package/schemas/workflow.schema.json +63 -15
  41. package/scripts/patch-herdr-agent-state.mjs +36 -0
  42. package/src/agents/profile.ts +98 -0
  43. package/src/config/ceiling.ts +0 -82
  44. package/src/config/types.ts +15 -53
  45. package/src/config/validation/settings.ts +2 -14
  46. package/src/config/validation/step.ts +129 -8
  47. package/src/config/validation/workflow.ts +1 -10
  48. package/src/engine/run-workflow-validation.ts +0 -3
  49. package/src/engine/state-types.ts +1 -1
  50. package/src/harness/action-context.ts +1 -13
  51. package/src/harness/artifact-contract.ts +46 -0
  52. package/src/harness/delegation-control-actions.ts +4 -63
  53. package/src/harness/delegation-plan.ts +26 -80
  54. package/src/harness/delegation-response-actions.ts +22 -135
  55. package/src/harness/dependencies.ts +1 -12
  56. package/src/harness/gate-submission-action.ts +28 -0
  57. package/src/harness/status-actions.ts +20 -0
  58. package/src/harness/step-execution-actions.ts +2 -9
  59. package/src/harness/types.ts +2 -40
  60. package/src/harness.ts +2 -17
  61. package/src/herdr-workflow-state.ts +65 -0
  62. package/src/index.ts +4 -9
  63. package/src/integrations/subagents/child-policy-validation.ts +6 -7
  64. package/src/integrations/subagents/child-runtime-dependencies.ts +1 -1
  65. package/src/integrations/subagents/child-runtime-policy.ts +1 -7
  66. package/src/integrations/subagents/child-runtime.ts +18 -9
  67. package/src/integrations/subagents/client.ts +240 -98
  68. package/src/integrations/subagents/protocol-events.ts +32 -15
  69. package/src/integrations/subagents/protocol.ts +1 -1
  70. package/src/preflight.ts +0 -8
  71. package/src/prompt/main-workflow-notice.ts +8 -15
  72. package/src/prompt/step-task.ts +7 -6
  73. package/src/workflow-status/format-status.ts +5 -1
  74. package/src/workflow-status/render-step-detail.ts +94 -0
  75. package/src/workflow-status/types.ts +1 -0
  76. package/src/workflow-status/view.ts +49 -14
  77. package/agents/step.md +0 -32
  78. package/examples/mr-comments.workflow.yaml +0 -125
  79. package/examples/prompts/mr-comments/implement.md +0 -17
  80. package/examples/prompts/mr-comments/inspect.md +0 -5
  81. package/examples/prompts/mr-comments/plan.md +0 -54
  82. package/examples/prompts/mr-comments/verify.md +0 -9
  83. package/examples/settings.yaml +0 -27
  84. package/examples/starter-kit/steps/mr-review/publish.md +0 -48
  85. package/examples/starter-kit/steps/mr-review/review.md +0 -76
  86. package/examples/starter-kit/steps/mr-review/verify.md +0 -35
  87. package/src/config/validation/subagent.ts +0 -288
  88. package/src/harness/delegation-failure.ts +0 -248
  89. package/src/harness/delegation-recovery-validation.ts +0 -161
  90. package/src/harness/delegation-retry-policy.ts +0 -120
  91. package/src/integrations/subagents/client-delegation.ts +0 -181
  92. package/src/integrations/subagents/client-messages.ts +0 -66
  93. package/src/integrations/subagents/client-types.ts +0 -36
  94. package/src/integrations/subagents/diagnostic-format.ts +0 -45
  95. package/src/integrations/subagents/diagnostic-text.ts +0 -114
  96. package/src/integrations/subagents/diagnostic-types.ts +0 -83
  97. package/src/integrations/subagents/diagnostics.ts +0 -26
  98. package/src/integrations/subagents/failure-correlation.ts +0 -285
  99. package/src/integrations/subagents/failure-transcript.ts +0 -252
  100. package/src/integrations/subagents/hidden-bash-failure.ts +0 -98
  101. package/src/integrations/subagents/replay-audit.ts +0 -146
  102. package/src/integrations/subagents/replay-safety.ts +0 -67
  103. package/src/integrations/subagents/session-diagnostics.ts +0 -357
@@ -15,7 +15,7 @@ import type { MainStepExecution } from '../runtime/main-step-runtime.ts';
15
15
  import type { WorkflowStepResult } from '../runtime/step-result.ts';
16
16
  import type { HarnessActionContext as FullHarnessActionContext } from './action-context.ts';
17
17
  import { createDelegationPlan } from './delegation-plan.ts';
18
- import type { DelegationRecovery, MainStepIdentity } from './types.ts';
18
+ import type { MainStepIdentity } from './types.ts';
19
19
  import { resolveStepEffects } from './step-effects.ts';
20
20
 
21
21
  type HarnessActionContext = Pick<
@@ -50,7 +50,6 @@ export type StepExecutionActions = {
50
50
  launchCurrentStep: (
51
51
  this: HarnessActionContext,
52
52
  workflow: LoadedWorkflow,
53
- recovery?: DelegationRecovery,
54
53
  ) => void;
55
54
  launchMainStep: (
56
55
  this: HarnessActionContext,
@@ -87,7 +86,6 @@ export type StepExecutionActions = {
87
86
  function launchCurrentStep(
88
87
  this: HarnessActionContext,
89
88
  workflow: LoadedWorkflow,
90
- recovery?: DelegationRecovery,
91
89
  ): void {
92
90
  const run = this.run;
93
91
  if (
@@ -106,11 +104,6 @@ function launchCurrentStep(
106
104
  );
107
105
  return;
108
106
  }
109
- if (!step.subagent) {
110
- this.launchMainStep(workflow, run, step);
111
- return;
112
- }
113
-
114
107
  const plan = createDelegationPlan(
115
108
  {
116
109
  workflow,
@@ -118,7 +111,6 @@ function launchCurrentStep(
118
111
  step,
119
112
  sessionEpoch: this.sessionEpoch,
120
113
  latestContext: this.latestContext,
121
- recovery,
122
114
  },
123
115
  this.dependencies,
124
116
  );
@@ -243,6 +235,7 @@ function launchMainStep(
243
235
  outcomes: allowedOutcomes(workflow, run),
244
236
  summaryMaxChars: workflow.definition.summaryMaxChars,
245
237
  ...(step.gate ? { gateSubmitOutcome: step.gate.submitOutcome } : {}),
238
+ ...(step.workspace ? { workspace: structuredClone(step.workspace) } : {}),
246
239
  onTrace: (lines, context) =>
247
240
  this.queueMainStepLog(identity, lines, context),
248
241
  onSettled: (result, context) =>
@@ -1,10 +1,5 @@
1
1
  import type { ExtensionContext } from '@earendil-works/pi-coding-agent';
2
- import type { DelegationReplayAudit } from '../integrations/subagents/diagnostics.ts';
3
- import type {
4
- ChildStepPolicy,
5
- SubagentDelegationStatus,
6
- } from '../integrations/subagents/protocol.ts';
7
- import type { WorkflowHarnessDependencies } from './dependencies.ts';
2
+ import type { ChildStepPolicy } from '../integrations/subagents/protocol.ts';
8
3
 
9
4
  export type ActiveDelegation = {
10
5
  requestId: string;
@@ -16,34 +11,11 @@ export type ActiveDelegation = {
16
11
  policy: ChildStepPolicy;
17
12
  transcriptTask: string;
18
13
  agent: string;
19
- trustedSessionRoot?: string;
20
- broadRecoveryAuthorized: boolean;
21
- recoveryAttemptCount: number;
22
- recoveryFailures: ReadonlyArray<DelegationRecoveryFailure>;
23
14
  progress?: string;
15
+ activityLog?: Array<string>;
24
16
  cancelling?: boolean;
25
17
  };
26
18
 
27
- export type DelegationRecoveryBlocker =
28
- | 'cancelled'
29
- | 'detached'
30
- | 'inconsistent-timeout'
31
- | 'interrupted'
32
- | 'reported-mutation'
33
- | 'stopped';
34
-
35
- export type DelegationFailureDetails = {
36
- reason: string;
37
- status: SubagentDelegationStatus;
38
- error?: string;
39
- exitCode?: number;
40
- recoveryBlocker?: DelegationRecoveryBlocker;
41
- diagnostic?: Awaited<
42
- ReturnType<WorkflowHarnessDependencies['readToolFailureDiagnostic']>
43
- >;
44
- replayAudit?: DelegationReplayAudit;
45
- };
46
-
47
19
  export type MainStepIdentity = {
48
20
  requestId: string;
49
21
  runId: string;
@@ -65,13 +37,3 @@ export type WorkflowStartContext = {
65
37
  skills: () => ReadonlyArray<{ name: string }> | undefined;
66
38
  waitForIdle: () => Promise<void>;
67
39
  };
68
-
69
- export type DelegationRecoveryFailure = {
70
- readonly fingerprint: string;
71
- readonly reason: string;
72
- };
73
-
74
- export type DelegationRecovery = {
75
- readonly attempt: number;
76
- readonly failures: ReadonlyArray<DelegationRecoveryFailure>;
77
- };
package/src/harness.ts CHANGED
@@ -24,10 +24,6 @@ import {
24
24
  import type { MainStepRuntimeController } from './runtime/main-step-runtime.ts';
25
25
  import type { SerialTaskQueueController } from './runtime/serial-task-queue.ts';
26
26
  import type { WorkflowStepResult } from './runtime/step-result.ts';
27
- import {
28
- createDelegationFailureActions,
29
- type DelegationFailureActions,
30
- } from './harness/delegation-failure.ts';
31
27
  import { createEmptyCatalog } from './harness/catalog.ts';
32
28
  import {
33
29
  createWorkflowHarnessDependencies,
@@ -36,8 +32,6 @@ import {
36
32
  import type {
37
33
  ActiveDelegation,
38
34
  ActivePromptReview,
39
- DelegationRecovery,
40
- DelegationFailureDetails,
41
35
  MainStepIdentity,
42
36
  WorkflowStartContext,
43
37
  } from './harness/types.ts';
@@ -79,7 +73,6 @@ const CORE_ACTIONS = createCoreActions();
79
73
  export class WorkflowHarness implements WorkflowCommandController {
80
74
  private readonly pi: ExtensionAPI;
81
75
  private readonly dependencies: WorkflowHarnessDependencies;
82
- private readonly delegationFailures: DelegationFailureActions;
83
76
  private readonly subagents: SubagentDelegationClientController;
84
77
  private readonly mainSteps: MainStepRuntimeController;
85
78
  private catalog: WorkflowCatalog = createEmptyCatalog();
@@ -149,10 +142,8 @@ export class WorkflowHarness implements WorkflowCommandController {
149
142
  LIFECYCLE_ACTIONS.registerLifecycle;
150
143
  private readonly registerPolicy: () => void =
151
144
  LIFECYCLE_ACTIONS.registerPolicy;
152
- private readonly launchCurrentStep: (
153
- workflow: LoadedWorkflow,
154
- recovery?: DelegationRecovery,
155
- ) => void = STEP_EXECUTION_ACTIONS.launchCurrentStep;
145
+ private readonly launchCurrentStep: (workflow: LoadedWorkflow) => void =
146
+ STEP_EXECUTION_ACTIONS.launchCurrentStep;
156
147
  private readonly launchMainStep: (
157
148
  workflow: LoadedWorkflow,
158
149
  run: WorkflowRun,
@@ -200,11 +191,6 @@ export class WorkflowHarness implements WorkflowCommandController {
200
191
  private readonly cleanupDelegation: (
201
192
  active: ActiveDelegation,
202
193
  ) => Promise<void> = DELEGATION_CONTROL_ACTIONS.cleanupDelegation;
203
- private readonly retryDelegationAfterFailure: (
204
- active: ActiveDelegation,
205
- failure: DelegationFailureDetails | undefined,
206
- reason: string,
207
- ) => boolean = DELEGATION_CONTROL_ACTIONS.retryDelegationAfterFailure;
208
194
  private readonly pauseForDelegationFailure: (
209
195
  reason: string,
210
196
  failureSummary?: string,
@@ -298,7 +284,6 @@ export class WorkflowHarness implements WorkflowCommandController {
298
284
  ) {
299
285
  this.pi = pi;
300
286
  this.dependencies = createWorkflowHarnessDependencies(dependencyOverrides);
301
- this.delegationFailures = createDelegationFailureActions(this.dependencies);
302
287
  this.statusShortcut = statusShortcut;
303
288
  this.statusShortcutLabel = formatShortcutLabel(statusShortcut);
304
289
  this.subagents = this.dependencies.createSubagentClient(pi);
@@ -0,0 +1,65 @@
1
+ import net from 'node:net';
2
+ import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
3
+
4
+ const source = 'herdr:pi-workflows';
5
+
6
+ type WorkflowState = {
7
+ state: 'working' | 'blocked' | 'completed' | 'interrupted';
8
+ workflowId?: string;
9
+ stepId?: string;
10
+ message?: string;
11
+ };
12
+
13
+ export default function herdrWorkflowState(pi: ExtensionAPI): void {
14
+ const socketPath = process.env.HERDR_SOCKET_PATH;
15
+ const paneId = process.env.HERDR_PANE_ID;
16
+ if (process.env.HERDR_ENV !== '1' || !socketPath || !paneId) return;
17
+
18
+ let seq = Date.now() * 1000;
19
+ let last = '';
20
+ pi.events.on('pi-workflows:state', (data: unknown) => {
21
+ if (!data || typeof data !== 'object') return;
22
+ const event = data as WorkflowState;
23
+ if (
24
+ !['working', 'blocked', 'completed', 'interrupted'].includes(event.state)
25
+ )
26
+ return;
27
+ const state =
28
+ event.state === 'working'
29
+ ? 'working'
30
+ : event.state === 'blocked'
31
+ ? 'blocked'
32
+ : 'idle';
33
+ const message =
34
+ event.message ??
35
+ (event.workflowId
36
+ ? `Workflow "${event.workflowId}" ${event.state}`
37
+ : undefined);
38
+ const fingerprint = `${state}:${message ?? ''}`;
39
+ if (fingerprint === last) return;
40
+ last = fingerprint;
41
+ const request = {
42
+ id: `${source}:${Date.now()}:${++seq}`,
43
+ method: 'pane.report_agent',
44
+ params: {
45
+ pane_id: paneId,
46
+ source,
47
+ agent: 'pi-workflows',
48
+ state,
49
+ message,
50
+ seq,
51
+ },
52
+ };
53
+ const socket = net.createConnection(socketPath);
54
+ const timeout = setTimeout(() => socket.destroy(), 1500);
55
+ timeout.unref();
56
+ socket.on('connect', () => socket.write(`${JSON.stringify(request)}\n`));
57
+ socket.on('data', () => socket.destroy());
58
+ socket.on('close', () => {
59
+ clearTimeout(timeout);
60
+ });
61
+ socket.on('error', () => {
62
+ clearTimeout(timeout);
63
+ });
64
+ });
65
+ }
package/src/index.ts CHANGED
@@ -3,7 +3,6 @@ import type { KeyId } from '@earendil-works/pi-tui';
3
3
  import { defaultUserWorkflowDirectory, loadSettings } from './config/load.ts';
4
4
  import { WorkflowHarness } from './harness.ts';
5
5
  import { registerSubagentChildRuntime } from './integrations/subagents/child-runtime.ts';
6
- import { isSubagentRuntimeName } from './integrations/subagents/protocol.ts';
7
6
 
8
7
  /**
9
8
  * Asynchronous entry point accepted by Pi's extension loader.
@@ -25,7 +24,6 @@ export type PiWorkflowsExtensionDependencies = {
25
24
  readonly loadSettings: typeof loadSettings;
26
25
  readonly userWorkflowDirectory: () => string;
27
26
  readonly runtimeEnvironment: () => PiWorkflowsRuntimeEnvironment;
28
- readonly isSubagentRuntimeName: typeof isSubagentRuntimeName;
29
27
  readonly registerChildRuntime: (pi: ExtensionAPI, childAgent: string) => void;
30
28
  readonly createHarness: (pi: ExtensionAPI, statusShortcut: KeyId) => void;
31
29
  };
@@ -34,10 +32,9 @@ const DEFAULT_DEPENDENCIES = {
34
32
  loadSettings,
35
33
  userWorkflowDirectory: defaultUserWorkflowDirectory,
36
34
  runtimeEnvironment: (): PiWorkflowsRuntimeEnvironment => ({
37
- isSubagentChild: process.env.PI_SUBAGENT_CHILD === '1',
38
- childAgent: process.env.PI_SUBAGENT_CHILD_AGENT?.trim(),
35
+ isSubagentChild: process.env.PI_WORKFLOWS_CHILD === '1',
36
+ childAgent: process.env.PI_WORKFLOWS_CHILD_AGENT?.trim(),
39
37
  }),
40
- isSubagentRuntimeName,
41
38
  registerChildRuntime: (pi, childAgent): void => {
42
39
  registerSubagentChildRuntime(pi, { childAgent });
43
40
  },
@@ -59,10 +56,8 @@ export function createPiWorkflowsExtension(
59
56
  return async (pi): Promise<void> => {
60
57
  const environment = dependencies.runtimeEnvironment();
61
58
 
62
- if (environment.isSubagentChild) {
63
- if (dependencies.isSubagentRuntimeName(environment.childAgent)) {
64
- dependencies.registerChildRuntime(pi, environment.childAgent);
65
- }
59
+ if (environment.isSubagentChild && environment.childAgent) {
60
+ dependencies.registerChildRuntime(pi, environment.childAgent);
66
61
  return;
67
62
  }
68
63
 
@@ -1,5 +1,5 @@
1
1
  import { dirname, isAbsolute, resolve } from 'node:path';
2
- import { SUBAGENT_RUNTIME_NAME_PATTERN } from '../../config/types.ts';
2
+ import { AGENT_PROFILE_NAME_PATTERN } from '../../config/types.ts';
3
3
  import {
4
4
  DEFAULT_CHILD_POLICY_ENVIRONMENT,
5
5
  isSafeStepCapabilityPath,
@@ -61,11 +61,10 @@ const requiredString = (
61
61
 
62
62
  /**
63
63
  * Returns whether a name follows the runtime naming contract used by
64
- * pi-subagents.
64
+ * workflow agent profiles.
65
65
  */
66
- export const isSubagentRuntimeName = (
67
- name: string | undefined,
68
- ): name is string => Boolean(name && SUBAGENT_RUNTIME_NAME_PATTERN.test(name));
66
+ export const isAgentProfileName = (name: string | undefined): name is string =>
67
+ Boolean(name && AGENT_PROFILE_NAME_PATTERN.test(name));
69
68
 
70
69
  const rejectUnknownProperties = (
71
70
  value: Readonly<Record<string, unknown>>,
@@ -115,8 +114,8 @@ const parseIdentityAndPaths = (
115
114
  if (!POLICY_DIGEST_PATTERN.test(policyDigest)) {
116
115
  throw new Error('child policy digest is invalid');
117
116
  }
118
- if (!isSubagentRuntimeName(agent)) {
119
- throw new Error('child policy agent is not a valid subagent runtime name');
117
+ if (!isAgentProfileName(agent)) {
118
+ throw new Error('child policy agent is not a valid agent profile name');
120
119
  }
121
120
  if (!CAPABILITY_TOKEN_PATTERN.test(capabilityToken)) {
122
121
  throw new Error('child policy capability token is invalid');
@@ -41,7 +41,7 @@ export const DEFAULT_CHILD_RUNTIME_DEPENDENCIES = {
41
41
  createUniqueId: randomUUID,
42
42
  currentWorkingDirectory: () => process.cwd(),
43
43
  environmentChildAgent: () =>
44
- process.env.PI_SUBAGENT_CHILD_AGENT?.trim() || undefined,
44
+ process.env.PI_WORKFLOWS_CHILD_AGENT?.trim() || undefined,
45
45
  temporaryDirectory: tmpdir,
46
46
  tokensAreEqual,
47
47
  } as const satisfies SubagentChildRuntimeDependencies;
@@ -8,13 +8,7 @@ import type { ChildStepPolicy } from './child-policy-types.ts';
8
8
  export const childPolicyStep = (policy: ChildStepPolicy): WorkflowStep => ({
9
9
  title: policy.stepTitle,
10
10
  prompt: { inline: 'Delegated workflow step' },
11
- subagent: {
12
- agent: policy.agent,
13
- context: 'fresh',
14
- timeoutMs: 900_000,
15
- artifacts: false,
16
- retryToolFailures: false,
17
- },
11
+ agent: { name: policy.agent },
18
12
  permissions: policy.permissions,
19
13
  requires: { tools: [], extensions: [], skills: [] },
20
14
  transitions: {},
@@ -3,6 +3,7 @@ import type {
3
3
  InputEvent,
4
4
  InputEventResult,
5
5
  } from '@earendil-works/pi-coding-agent';
6
+ import { Type } from 'typebox';
6
7
  import { invalidCompletionCallIds } from '../../policy/completion-batch.ts';
7
8
  import { freezeToolInput } from '../../policy/immutable-input.ts';
8
9
  import { authorizeToolCall, resolveActiveTools } from '../../policy/tools.ts';
@@ -24,7 +25,6 @@ import type {
24
25
  } from './child-runtime-types.ts';
25
26
  import { extractChildPolicy } from './child-policy-envelope.ts';
26
27
  import type { ChildStepPolicy } from './child-policy-types.ts';
27
- import { isSubagentRuntimeName } from './child-policy-validation.ts';
28
28
 
29
29
  export { CHILD_COMPLETION_TOOL } from './child-runtime-completion.ts';
30
30
  export type {
@@ -85,6 +85,21 @@ export const registerSubagentChildRuntime = (
85
85
  const childAgent = resolveChildAgent(options, dependencies);
86
86
  let state = INITIAL_STATE;
87
87
 
88
+ pi.registerTool({
89
+ name: CHILD_COMPLETION_TOOL,
90
+ label: 'Complete Workflow Step',
91
+ description: 'Return the one structured result for this workflow step',
92
+ parameters: Type.Object({ value: Type.Any() }),
93
+ executionMode: 'sequential',
94
+ execute: async () => ({
95
+ content: [
96
+ { type: 'text' as const, text: 'Captured workflow step result.' },
97
+ ],
98
+ details: {},
99
+ terminate: true,
100
+ }),
101
+ });
102
+
88
103
  pi.on('input', (event) => {
89
104
  let extracted;
90
105
  try {
@@ -104,11 +119,7 @@ export const registerSubagentChildRuntime = (
104
119
  }
105
120
 
106
121
  try {
107
- if (!isSubagentRuntimeName(childAgent)) {
108
- throw new Error(
109
- 'child agent does not match the delegated workflow policy',
110
- );
111
- }
122
+ if (!childAgent) throw new Error('workflow worker agent is unavailable');
112
123
  verifyChildWorkingDirectory(extracted.policy, dependencies);
113
124
  verifyChildCapability({
114
125
  policy: extracted.policy,
@@ -117,9 +128,7 @@ export const registerSubagentChildRuntime = (
117
128
  });
118
129
  const profileTools = new Set(pi.getActiveTools());
119
130
  if (!profileTools.has(CHILD_COMPLETION_TOOL)) {
120
- throw new Error(
121
- 'pi-subagents structured_output completion is unavailable',
122
- );
131
+ throw new Error('workflow worker completion tool is unavailable');
123
132
  }
124
133
  const effectiveTools = new Set(
125
134
  resolveActiveTools(