@wichayutdew/pi-workflows 2.5.1 → 2.6.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 (95) hide show
  1. package/README.md +4 -1
  2. package/dist/index.js +1133 -2409
  3. package/examples/starter-kit/agents/planner.md +7 -0
  4. package/examples/starter-kit/agents/reviewer.md +6 -0
  5. package/examples/starter-kit/agents/scout.md +6 -0
  6. package/examples/starter-kit/agents/worker.md +7 -0
  7. package/examples/starter-kit/agents/workspace-preparer.md +6 -0
  8. package/examples/starter-kit/investigate.workflow.yaml +114 -0
  9. package/examples/starter-kit/mr-comment.workflow.yaml +114 -54
  10. package/examples/starter-kit/mr-review.workflow.yaml +77 -56
  11. package/examples/starter-kit/settings.yaml +3 -0
  12. package/examples/starter-kit/steps/investigate/investigate.md +77 -0
  13. package/examples/starter-kit/steps/investigate/retrieve.md +63 -0
  14. package/examples/starter-kit/steps/investigate/validate.md +46 -0
  15. package/examples/starter-kit/steps/mr-comment/checkout-source.md +67 -0
  16. package/examples/starter-kit/steps/mr-comment/fetch.md +34 -26
  17. package/examples/starter-kit/steps/mr-comment/implement.md +34 -27
  18. package/examples/starter-kit/steps/mr-comment/plan.md +54 -40
  19. package/examples/starter-kit/steps/mr-comment/publish.md +28 -27
  20. package/examples/starter-kit/steps/mr-comment/verify.md +42 -35
  21. package/examples/starter-kit/steps/mr-review/fetch.md +52 -22
  22. package/examples/starter-kit/steps/mr-review/publish-approved.md +49 -0
  23. package/examples/starter-kit/steps/mr-review/review-for-approval.md +142 -0
  24. package/examples/starter-kit/steps/mr-review/verify-published.md +37 -0
  25. package/examples/starter-kit/steps/shared/prepare-workspace.md +91 -73
  26. package/examples/starter-kit/steps/shared/publish-remote.md +45 -0
  27. package/examples/starter-kit/steps/ticket/implement.md +59 -25
  28. package/examples/starter-kit/steps/ticket/plan.md +145 -72
  29. package/examples/starter-kit/steps/ticket/verify.md +80 -45
  30. package/examples/starter-kit/steps/work/implement.md +60 -24
  31. package/examples/starter-kit/steps/work/plan.md +129 -57
  32. package/examples/starter-kit/steps/work/verify.md +58 -22
  33. package/examples/starter-kit/ticket.workflow.yaml +75 -36
  34. package/examples/starter-kit/work.workflow.yaml +63 -33
  35. package/package.json +3 -16
  36. package/schemas/workflow.schema.json +2 -15
  37. package/src/agents/profile.ts +98 -0
  38. package/src/config/ceiling.ts +0 -82
  39. package/src/config/types.ts +6 -53
  40. package/src/config/validation/settings.ts +2 -14
  41. package/src/config/validation/step.ts +12 -8
  42. package/src/config/validation/workflow.ts +1 -10
  43. package/src/engine/run-workflow-validation.ts +0 -3
  44. package/src/engine/state-types.ts +1 -1
  45. package/src/harness/action-context.ts +1 -13
  46. package/src/harness/delegation-control-actions.ts +4 -63
  47. package/src/harness/delegation-plan.ts +26 -80
  48. package/src/harness/delegation-response-actions.ts +22 -135
  49. package/src/harness/dependencies.ts +1 -12
  50. package/src/harness/status-actions.ts +1 -0
  51. package/src/harness/step-execution-actions.ts +2 -9
  52. package/src/harness/types.ts +2 -40
  53. package/src/harness.ts +2 -17
  54. package/src/index.ts +4 -9
  55. package/src/integrations/subagents/child-policy-validation.ts +6 -7
  56. package/src/integrations/subagents/child-runtime-dependencies.ts +1 -1
  57. package/src/integrations/subagents/child-runtime-policy.ts +1 -7
  58. package/src/integrations/subagents/child-runtime.ts +18 -9
  59. package/src/integrations/subagents/client.ts +240 -98
  60. package/src/integrations/subagents/protocol-events.ts +32 -15
  61. package/src/integrations/subagents/protocol.ts +1 -1
  62. package/src/preflight.ts +0 -8
  63. package/src/prompt/main-workflow-notice.ts +8 -15
  64. package/src/prompt/step-task.ts +7 -6
  65. package/src/workflow-status/format-status.ts +5 -1
  66. package/src/workflow-status/render-step-detail.ts +94 -0
  67. package/src/workflow-status/types.ts +1 -0
  68. package/src/workflow-status/view.ts +49 -14
  69. package/agents/step.md +0 -32
  70. package/examples/mr-comments.workflow.yaml +0 -125
  71. package/examples/prompts/mr-comments/implement.md +0 -17
  72. package/examples/prompts/mr-comments/inspect.md +0 -5
  73. package/examples/prompts/mr-comments/plan.md +0 -54
  74. package/examples/prompts/mr-comments/verify.md +0 -9
  75. package/examples/settings.yaml +0 -27
  76. package/examples/starter-kit/steps/mr-review/publish.md +0 -48
  77. package/examples/starter-kit/steps/mr-review/review.md +0 -76
  78. package/examples/starter-kit/steps/mr-review/verify.md +0 -35
  79. package/src/config/validation/subagent.ts +0 -288
  80. package/src/harness/delegation-failure.ts +0 -248
  81. package/src/harness/delegation-recovery-validation.ts +0 -161
  82. package/src/harness/delegation-retry-policy.ts +0 -120
  83. package/src/integrations/subagents/client-delegation.ts +0 -181
  84. package/src/integrations/subagents/client-messages.ts +0 -66
  85. package/src/integrations/subagents/client-types.ts +0 -36
  86. package/src/integrations/subagents/diagnostic-format.ts +0 -45
  87. package/src/integrations/subagents/diagnostic-text.ts +0 -114
  88. package/src/integrations/subagents/diagnostic-types.ts +0 -83
  89. package/src/integrations/subagents/diagnostics.ts +0 -26
  90. package/src/integrations/subagents/failure-correlation.ts +0 -285
  91. package/src/integrations/subagents/failure-transcript.ts +0 -252
  92. package/src/integrations/subagents/hidden-bash-failure.ts +0 -98
  93. package/src/integrations/subagents/replay-audit.ts +0 -146
  94. package/src/integrations/subagents/replay-safety.ts +0 -67
  95. package/src/integrations/subagents/session-diagnostics.ts +0 -357
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wichayutdew/pi-workflows",
3
- "version": "2.5.1",
3
+ "version": "2.6.0",
4
4
  "description": "A declarative, pauseable workflow harness for Pi",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -21,7 +21,6 @@
21
21
  "files": [
22
22
  "dist",
23
23
  "src",
24
- "agents",
25
24
  "examples",
26
25
  "schemas",
27
26
  "README.md",
@@ -30,12 +29,7 @@
30
29
  "pi": {
31
30
  "extensions": [
32
31
  "./src/index.ts"
33
- ],
34
- "subagents": {
35
- "agents": [
36
- "./agents"
37
- ]
38
- }
32
+ ]
39
33
  },
40
34
  "scripts": {
41
35
  "build": "bun build ./src/index.ts --outdir=dist --target=node --format=esm --packages=external",
@@ -44,9 +38,9 @@
44
38
  "lint": "bun --bun eslint .",
45
39
  "lint:fix": "bun --bun eslint . --fix",
46
40
  "test": "bun test --no-orphans --reporter=dots",
41
+ "test:e2e": "bun --config=bunfig.e2e.toml test --no-orphans ./test/e2e/direct-worker-runtime.test.ts --reporter=dots",
47
42
  "test:coverage": "bun --config=bunfig.coverage.toml test --no-orphans --reporter=dots && bun run coverage:check",
48
43
  "coverage:check": "bun .github/scripts/check-coverage.mjs coverage/full/lcov.info",
49
- "test:e2e": "bun --config=bunfig.e2e.toml test --no-orphans test/e2e/workflow-runtime.test.ts --reporter=dots",
50
44
  "typecheck": "tsc --noEmit",
51
45
  "check": "bun run lint && bun run format:check && bun run typecheck && bun run test:coverage && bun run test:e2e && bun run build"
52
46
  },
@@ -56,14 +50,8 @@
56
50
  "peerDependencies": {
57
51
  "@earendil-works/pi-coding-agent": "*",
58
52
  "@earendil-works/pi-tui": "*",
59
- "pi-subagents": ">=0.36.0",
60
53
  "typebox": "*"
61
54
  },
62
- "peerDependenciesMeta": {
63
- "pi-subagents": {
64
- "optional": true
65
- }
66
- },
67
55
  "devDependencies": {
68
56
  "@earendil-works/pi-coding-agent": "^0.81.1",
69
57
  "@earendil-works/pi-tui": "^0.81.1",
@@ -72,7 +60,6 @@
72
60
  "@types/node": "^24.0.0",
73
61
  "eslint": "^10.7.0",
74
62
  "globals": "^17.7.0",
75
- "pi-subagents": "0.36.0",
76
63
  "prettier": "^3.9.5",
77
64
  "typebox": "^1.1.38",
78
65
  "typescript": "^5.9.3",
@@ -597,19 +597,6 @@
597
597
  "type": "object",
598
598
  "additionalProperties": false,
599
599
  "required": ["prompt", "transitions"],
600
- "allOf": [
601
- {
602
- "if": {
603
- "required": ["workspace"]
604
- },
605
- "then": {
606
- "required": ["subagent"],
607
- "not": {
608
- "required": ["gate"]
609
- }
610
- }
611
- }
612
- ],
613
600
  "properties": {
614
601
  "title": {
615
602
  "type": "string",
@@ -618,8 +605,8 @@
618
605
  "prompt": {
619
606
  "$ref": "#/$defs/prompt"
620
607
  },
621
- "subagent": {
622
- "$ref": "#/$defs/subagent"
608
+ "agent": {
609
+ "$ref": "#/$defs/subagentRuntimeName"
623
610
  },
624
611
  "permissions": {
625
612
  "$ref": "#/$defs/permissions"
@@ -0,0 +1,98 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { parse } from 'yaml';
5
+ import { defaultUserWorkflowDirectory } from '../config/load.ts';
6
+
7
+ export const THINKING_LEVELS = [
8
+ 'off',
9
+ 'minimal',
10
+ 'low',
11
+ 'medium',
12
+ 'high',
13
+ 'xhigh',
14
+ 'max',
15
+ ] as const;
16
+
17
+ export type ThinkingLevel = (typeof THINKING_LEVELS)[number];
18
+
19
+ export type AgentProfile = {
20
+ readonly prompt: string;
21
+ readonly model?: string;
22
+ readonly thinking?: ThinkingLevel;
23
+ };
24
+
25
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
26
+ value !== null && typeof value === 'object' && !Array.isArray(value);
27
+
28
+ /** Parses the optional YAML frontmatter and role prompt in one agent profile. */
29
+ export function parseAgentProfile(source: string, name: string): AgentProfile {
30
+ if (!source.startsWith('---\n')) return { prompt: source.trim() };
31
+ const end = source.indexOf('\n---\n', 4);
32
+ if (end === -1) {
33
+ throw new Error(`workflow agent profile is invalid: ${name}`);
34
+ }
35
+ const metadata: unknown = parse(source.slice(4, end));
36
+ if (!isRecord(metadata)) {
37
+ throw new Error(
38
+ `workflow agent profile metadata must be an object: ${name}`,
39
+ );
40
+ }
41
+ const unknownKey = Object.keys(metadata).find(
42
+ (key) => key !== 'model' && key !== 'thinking',
43
+ );
44
+ if (unknownKey) {
45
+ throw new Error(
46
+ `workflow agent profile has unknown metadata "${unknownKey}": ${name}`,
47
+ );
48
+ }
49
+ const model = metadata.model;
50
+ if (model !== undefined && (typeof model !== 'string' || !model.trim())) {
51
+ throw new Error(
52
+ `workflow agent profile model must be a non-empty string: ${name}`,
53
+ );
54
+ }
55
+ const thinking = metadata.thinking;
56
+ if (
57
+ thinking !== undefined &&
58
+ (typeof thinking !== 'string' ||
59
+ !THINKING_LEVELS.includes(thinking as ThinkingLevel))
60
+ ) {
61
+ throw new Error(
62
+ `workflow agent profile thinking must be one of ${THINKING_LEVELS.join(', ')}: ${name}`,
63
+ );
64
+ }
65
+ const prompt = source.slice(end + 5).trim();
66
+ if (!prompt)
67
+ throw new Error(`workflow agent profile prompt is empty: ${name}`);
68
+ return {
69
+ prompt,
70
+ ...(typeof model === 'string' ? { model: model.trim() } : {}),
71
+ ...(typeof thinking === 'string'
72
+ ? { thinking: thinking as ThinkingLevel }
73
+ : {}),
74
+ };
75
+ }
76
+
77
+ /** Loads a user-owned agent profile, with a bundled prompt-only fallback. */
78
+ export function loadAgentProfile(
79
+ name: string,
80
+ userDirectory = defaultUserWorkflowDirectory(),
81
+ ): AgentProfile {
82
+ const userPath = join(userDirectory, 'agents', `${name}.md`);
83
+ const bundledUrl = new URL(
84
+ `../../examples/starter-kit/agents/${name}.md`,
85
+ import.meta.url,
86
+ );
87
+ const path = existsSync(userPath) ? userPath : fileURLToPath(bundledUrl);
88
+ try {
89
+ return parseAgentProfile(readFileSync(path, 'utf8'), name);
90
+ } catch (error) {
91
+ if (error instanceof Error && error.message.startsWith('workflow agent')) {
92
+ throw error;
93
+ }
94
+ throw new Error(`workflow agent profile is unavailable: ${name}`, {
95
+ cause: error,
96
+ });
97
+ }
98
+ }
@@ -2,8 +2,6 @@ import type {
2
2
  BashPermission,
3
3
  BashRule,
4
4
  PermissionCeiling,
5
- StepSubagent,
6
- SubagentPermissionCeiling,
7
5
  WorkflowDefinition,
8
6
  } from './types.ts';
9
7
 
@@ -34,81 +32,6 @@ function bashWithinCeiling(
34
32
  return requested.allow.every((rule) => allowedRules.has(ruleKey(rule)));
35
33
  }
36
34
 
37
- function turnBudgetErrors(
38
- subagent: StepSubagent,
39
- ceiling: SubagentPermissionCeiling,
40
- path: string,
41
- ): Array<string> {
42
- if (!subagent.turnBudget) {
43
- return [`${path}.turnBudget: required for a project workflow`];
44
- }
45
- return [
46
- ...(subagent.turnBudget.maxTurns > ceiling.maxTurns
47
- ? [`${path}.turnBudget.maxTurns: exceeds the user permission ceiling`]
48
- : []),
49
- ...((subagent.turnBudget.graceTurns ?? 0) > ceiling.maxGraceTurns
50
- ? [`${path}.turnBudget.graceTurns: exceeds the user permission ceiling`]
51
- : []),
52
- ];
53
- }
54
-
55
- function toolBudgetErrors(
56
- subagent: StepSubagent,
57
- ceiling: SubagentPermissionCeiling,
58
- path: string,
59
- ): Array<string> {
60
- if (!subagent.toolBudget) {
61
- return [`${path}.toolBudget: required for a project workflow`];
62
- }
63
- return [
64
- ...(subagent.toolBudget.hard > ceiling.maxToolCalls
65
- ? [`${path}.toolBudget.hard: exceeds the user permission ceiling`]
66
- : []),
67
- ...(subagent.toolBudget.block !== '*'
68
- ? [`${path}.toolBudget.block: must be "*" for a project workflow`]
69
- : []),
70
- ];
71
- }
72
-
73
- function subagentErrors(
74
- subagent: StepSubagent | undefined,
75
- ceiling: SubagentPermissionCeiling | undefined,
76
- path: string,
77
- ): Array<string> {
78
- if (!subagent) return [];
79
- if (!ceiling) {
80
- return [`${path}: subagent execution exceeds the user permission ceiling`];
81
- }
82
- return [
83
- ...(!ceiling.agents.includes(subagent.agent)
84
- ? [
85
- `${path}.agent: "${subagent.agent}" exceeds the user permission ceiling`,
86
- ]
87
- : []),
88
- ...(!ceiling.contexts.includes(subagent.context)
89
- ? [
90
- `${path}.context: "${subagent.context}" exceeds the user permission ceiling`,
91
- ]
92
- : []),
93
- ...(subagent.model && !ceiling.models.includes(subagent.model)
94
- ? [
95
- `${path}.model: "${subagent.model}" exceeds the user permission ceiling`,
96
- ]
97
- : []),
98
- ...(subagent.timeoutMs > ceiling.maxTimeoutMs
99
- ? [`${path}.timeoutMs: exceeds the user permission ceiling`]
100
- : []),
101
- ...(subagent.artifacts && !ceiling.artifacts
102
- ? [`${path}.artifacts: exceeds the user permission ceiling`]
103
- : []),
104
- ...(subagent.retryToolFailures && !ceiling.retryToolFailures
105
- ? [`${path}.retryToolFailures: exceeds the user permission ceiling`]
106
- : []),
107
- ...turnBudgetErrors(subagent, ceiling, path),
108
- ...toolBudgetErrors(subagent, ceiling, path),
109
- ];
110
- }
111
-
112
35
  /** Return every project-workflow permission that exceeds the user ceiling. */
113
36
  export function checkWorkflowAgainstCeiling(
114
37
  workflow: WorkflowDefinition,
@@ -149,11 +72,6 @@ export function checkWorkflowAgainstCeiling(
149
72
  ...(!bashWithinCeiling(step.permissions.bash, ceiling.bash)
150
73
  ? [`${path}.bash: exceeds the user permission ceiling`]
151
74
  : []),
152
- ...subagentErrors(
153
- step.subagent,
154
- ceiling.subagent,
155
- `workflow.steps.${stepId}.subagent`,
156
- ),
157
75
  ];
158
76
  });
159
77
  }
@@ -2,7 +2,7 @@ import type { KeyId } from '@earendil-works/pi-tui';
2
2
 
3
3
  export const WORKFLOW_SCHEMA_VERSION = 1 as const;
4
4
  export const DEFAULT_STATUS_SHORTCUT = 'ctrl+alt+w' as const satisfies KeyId;
5
- export const SUBAGENT_RUNTIME_NAME_PATTERN =
5
+ export const AGENT_PROFILE_NAME_PATTERN =
6
6
  /^[a-z0-9][a-z0-9-]*(?:\.[a-z0-9][a-z0-9-]*)*$/;
7
7
  export const MAX_WORKSPACE_PATH_CHARS = 4_096;
8
8
  export const MAX_WORKSPACE_ALLOWED_ROOTS = 32;
@@ -41,35 +41,9 @@ export type StepRequirements = {
41
41
  readonly skills: ReadonlyArray<string>;
42
42
  };
43
43
 
44
- export type SubagentContext = 'fresh';
45
-
46
- export type SubagentTurnBudget = {
47
- readonly maxTurns: number;
48
- readonly graceTurns?: number;
49
- };
50
-
51
- export type SubagentToolBudget = {
52
- readonly hard: number;
53
- readonly soft?: number;
54
- readonly block?: ReadonlyArray<string> | '*';
55
- };
56
-
57
- export type StepSubagent = {
58
- /** Pi Subagents agent profile launched for this isolated step. */
59
- readonly agent: string;
60
- /** Workflow steps always use a fresh context. */
61
- readonly context: SubagentContext;
62
- readonly model?: string;
63
- readonly timeoutMs: number;
64
- readonly turnBudget?: SubagentTurnBudget;
65
- readonly toolBudget?: SubagentToolBudget;
66
- readonly artifacts: boolean;
67
- /**
68
- * Authorizes bounded automatic recovery when Bash can execute commands.
69
- * Every failed attempt must still have a complete, mutation-safe replay
70
- * audit.
71
- */
72
- readonly retryToolFailures: boolean;
44
+ export type StepAgent = {
45
+ /** Workflow-owned role prompt applied to this main-agent step. */
46
+ readonly name: string;
73
47
  };
74
48
 
75
49
  export type PromptSpec =
@@ -102,8 +76,8 @@ export type StepWorkspaceBinding = {
102
76
  export type WorkflowStep = {
103
77
  readonly title: string;
104
78
  readonly prompt: PromptSpec;
105
- /** Omit to execute this step in the main Pi agent. */
106
- readonly subagent?: StepSubagent;
79
+ /** Optional workflow-owned role prompt for this main-agent step. */
80
+ readonly agent?: StepAgent;
107
81
  readonly permissions: StepPermissions;
108
82
  readonly requires: StepRequirements;
109
83
  readonly transitions: Readonly<Record<string, StepTarget>>;
@@ -142,19 +116,6 @@ export type PermissionCeiling = {
142
116
  readonly extensions: ReadonlyArray<string>;
143
117
  readonly skills: ReadonlyArray<string>;
144
118
  readonly bash: BashPermission;
145
- readonly subagent?: SubagentPermissionCeiling;
146
- };
147
-
148
- export type SubagentPermissionCeiling = {
149
- readonly agents: ReadonlyArray<string>;
150
- readonly contexts: ReadonlyArray<SubagentContext>;
151
- readonly models: ReadonlyArray<string>;
152
- readonly maxTimeoutMs: number;
153
- readonly maxTurns: number;
154
- readonly maxGraceTurns: number;
155
- readonly maxToolCalls: number;
156
- readonly artifacts: boolean;
157
- readonly retryToolFailures: boolean;
158
119
  };
159
120
 
160
121
  export type WorkflowSettings = {
@@ -192,14 +153,6 @@ export const EMPTY_REQUIREMENTS = {
192
153
  skills: [],
193
154
  } as const satisfies StepRequirements;
194
155
 
195
- export const DEFAULT_STEP_SUBAGENT = {
196
- agent: 'pi-workflows.step',
197
- context: 'fresh',
198
- timeoutMs: 900_000,
199
- artifacts: false,
200
- retryToolFailures: false,
201
- } as const satisfies StepSubagent;
202
-
203
156
  export const DEFAULT_SETTINGS = {
204
157
  version: WORKFLOW_SCHEMA_VERSION,
205
158
  allowProjectWorkflows: false,
@@ -12,7 +12,6 @@ import {
12
12
  type ValidationResult,
13
13
  } from './shared.ts';
14
14
  import { readStatusShortcut } from './shortcut.ts';
15
- import { parseSubagentPermissionCeiling } from './subagent.ts';
16
15
 
17
16
  function parsePermissionCeiling(
18
17
  value: unknown,
@@ -26,7 +25,7 @@ function parsePermissionCeiling(
26
25
  }
27
26
  rejectUnknownKeys(
28
27
  value,
29
- ['tools', 'mcp', 'extensions', 'skills', 'bash', 'subagent'],
28
+ ['tools', 'mcp', 'extensions', 'skills', 'bash'],
30
29
  path,
31
30
  errors,
32
31
  );
@@ -43,18 +42,7 @@ function parsePermissionCeiling(
43
42
  path,
44
43
  errors,
45
44
  );
46
- const subagent =
47
- value.subagent === undefined
48
- ? undefined
49
- : parseSubagentPermissionCeiling(
50
- value.subagent,
51
- `${path}.subagent`,
52
- errors,
53
- );
54
- return {
55
- ...permissions,
56
- ...(subagent ? { subagent } : {}),
57
- };
45
+ return permissions;
58
46
  }
59
47
 
60
48
  /** Validate and normalize untrusted user workflow settings. */
@@ -17,7 +17,7 @@ import {
17
17
  rejectUnknownKeys,
18
18
  type ValidationErrors,
19
19
  } from './shared.ts';
20
- import { parseStepSubagent } from './subagent.ts';
20
+ import { AGENT_PROFILE_NAME_PATTERN, type StepAgent } from '../types.ts';
21
21
 
22
22
  function parsePrompt(
23
23
  value: unknown,
@@ -248,7 +248,7 @@ export function parseWorkflowStep(
248
248
  [
249
249
  'title',
250
250
  'prompt',
251
- 'subagent',
251
+ 'agent',
252
252
  'permissions',
253
253
  'requires',
254
254
  'transitions',
@@ -264,11 +264,15 @@ export function parseWorkflowStep(
264
264
  ? stepId
265
265
  : readString(value.title, `${path}.title`, errors);
266
266
  const prompt = parsePrompt(value.prompt, `${path}.prompt`, errors);
267
- const subagent = parseStepSubagent(
268
- value.subagent,
269
- `${path}.subagent`,
270
- errors,
271
- );
267
+ const agentName =
268
+ value.agent === undefined
269
+ ? undefined
270
+ : readString(value.agent, `${path}.agent`, errors, {
271
+ pattern: AGENT_PROFILE_NAME_PATTERN,
272
+ });
273
+ const agent: StepAgent | undefined = agentName
274
+ ? { name: agentName }
275
+ : undefined;
272
276
  const permissions = parsePermissions(
273
277
  value.permissions,
274
278
  `${path}.permissions`,
@@ -323,7 +327,7 @@ export function parseWorkflowStep(
323
327
  return {
324
328
  title,
325
329
  prompt,
326
- ...(subagent ? { subagent } : {}),
330
+ ...(agent ? { agent } : {}),
327
331
  permissions,
328
332
  requires,
329
333
  transitions,
@@ -91,9 +91,6 @@ function validateWorkspaceGraph(
91
91
  `${workspacePath}: only one workspace-binding step is allowed; "${firstBinderId}" also configures workspace binding`,
92
92
  );
93
93
  }
94
- if (!step.subagent) {
95
- errors.push(`${workspacePath}: workspace binding requires a subagent`);
96
- }
97
94
  if (step.gate) {
98
95
  errors.push(
99
96
  `${workspacePath}: workspace binding is not allowed on a gated step`,
@@ -110,14 +107,13 @@ function validateWorkspaceGraph(
110
107
  }
111
108
  return target && Object.hasOwn(steps, target) ? [target] : [];
112
109
  });
113
- validateWorkspaceDescendants(steps, downstream, errors);
110
+ validateWorkspaceDescendants(steps, downstream);
114
111
  });
115
112
  }
116
113
 
117
114
  function validateWorkspaceDescendants(
118
115
  steps: Readonly<Record<string, WorkflowStep>>,
119
116
  initialStepIds: ReadonlyArray<string>,
120
- errors: ValidationErrors,
121
117
  ): void {
122
118
  const pending = [...initialStepIds];
123
119
  const visited = new Set<string>();
@@ -127,11 +123,6 @@ function validateWorkspaceDescendants(
127
123
  visited.add(stepId);
128
124
  const step = steps[stepId];
129
125
  if (!step) continue;
130
- if (!step.subagent) {
131
- errors.push(
132
- `workflow.steps.${stepId}.subagent: every nonterminal step reachable after workspace binding must use a subagent`,
133
- );
134
- }
135
126
  Object.values(step.transitions).forEach((target) => {
136
127
  if (
137
128
  target !== '$done' &&
@@ -183,9 +183,6 @@ export function validateRunWorkflowSemantics(
183
183
  if (sameWorkflowDigest && currentStepChanged) {
184
184
  return `current step "${run.currentStepId}" does not match the active workflow digest`;
185
185
  }
186
- if (boundWorkspaceCwd && !currentStep.subagent) {
187
- return `bound workflow current step "${run.currentStepId}" must use a subagent`;
188
- }
189
186
  if (currentStepChanged) return undefined;
190
187
  return validatePendingGate(run, currentStep);
191
188
  }
@@ -46,7 +46,7 @@ export type SubagentTranscriptReference = {
46
46
  readonly trustedRoot: string;
47
47
  /** Exact child session returned by the delegation protocol. */
48
48
  readonly sessionFile: string;
49
- /** Pi-subagents run identity used to confine the session path. */
49
+ /** Legacy child-run identity used to confine the session path. */
50
50
  readonly runId: string;
51
51
  readonly childIndex: number;
52
52
  };
@@ -20,14 +20,11 @@ import type { MainStepRuntimeController } from '../runtime/main-step-runtime.ts'
20
20
  import type { SerialTaskQueueController } from '../runtime/serial-task-queue.ts';
21
21
  import type { WorkflowStepResult } from '../runtime/step-result.ts';
22
22
  import type { WorkflowStatusSnapshot } from '../workflow-status.ts';
23
- import type { DelegationFailureActions } from './delegation-failure.ts';
24
23
  import type { WorkflowHarnessDependencies } from './dependencies.ts';
25
24
  import type { SettledStepReport } from './step-reporting.ts';
26
25
  import type {
27
26
  ActiveDelegation,
28
27
  ActivePromptReview,
29
- DelegationRecovery,
30
- DelegationFailureDetails,
31
28
  MainStepIdentity,
32
29
  WorkflowStartContext,
33
30
  } from './types.ts';
@@ -41,7 +38,6 @@ import type {
41
38
  export type HarnessActionContext = {
42
39
  pi: ExtensionAPI;
43
40
  dependencies: WorkflowHarnessDependencies;
44
- delegationFailures: DelegationFailureActions;
45
41
  subagents: SubagentDelegationClientController;
46
42
  mainSteps: MainStepRuntimeController;
47
43
  catalog: WorkflowCatalog;
@@ -98,10 +94,7 @@ export type HarnessActionContext = {
98
94
  registerLifecycle: () => void;
99
95
  registerPolicy: () => void;
100
96
  registerMultilineCommandInput: () => void;
101
- launchCurrentStep: (
102
- workflow: LoadedWorkflow,
103
- recovery?: DelegationRecovery,
104
- ) => void;
97
+ launchCurrentStep: (workflow: LoadedWorkflow) => void;
105
98
  launchMainStep: (
106
99
  workflow: LoadedWorkflow,
107
100
  run: WorkflowRun,
@@ -142,11 +135,6 @@ export type HarnessActionContext = {
142
135
  ) => Promise<void>;
143
136
  cancelActiveDelegation: (reason: string) => Promise<boolean>;
144
137
  cleanupDelegation: (active: ActiveDelegation) => Promise<void>;
145
- retryDelegationAfterFailure: (
146
- active: ActiveDelegation,
147
- failure: DelegationFailureDetails | undefined,
148
- reason: string,
149
- ) => boolean;
150
138
  pauseForDelegationFailure: (reason: string, failureSummary?: string) => void;
151
139
  pauseForExecutionFailure: (
152
140
  label: string,
@@ -1,33 +1,28 @@
1
1
  import { failRun } from '../engine/transitions.ts';
2
2
  import type { HarnessActionContext as FullHarnessActionContext } from './action-context.ts';
3
- import {
4
- boundedFailureField,
5
- MAX_DELEGATION_RECOVERY_ATTEMPTS,
6
- } from './delegation-retry-policy.ts';
7
3
  import {
8
4
  conciseStepFailureSummary,
9
5
  reportFailedStep,
10
6
  } from './step-reporting.ts';
11
- import type { ActiveDelegation, DelegationFailureDetails } from './types.ts';
7
+ import type { ActiveDelegation } from './types.ts';
8
+
9
+ const boundedFailureField = (value: string): string =>
10
+ value.length <= 500 ? value : `${value.slice(0, 499)}…`;
12
11
 
13
12
  type HarnessActionContext = Pick<
14
13
  FullHarnessActionContext,
15
14
  | 'activeDelegation'
16
15
  | 'catalog'
17
16
  | 'cleanupDelegation'
18
- | 'delegationFailures'
19
17
  | 'dependencies'
20
- | 'isSessionActive'
21
18
  | 'isolateMainSessionTools'
22
19
  | 'latestContext'
23
- | 'launchCurrentStep'
24
20
  | 'mainSteps'
25
21
  | 'pauseForExecutionFailure'
26
22
  | 'persist'
27
23
  | 'pi'
28
24
  | 'restoreBaselineTools'
29
25
  | 'run'
30
- | 'sessionEpoch'
31
26
  | 'subagents'
32
27
  | 'updateStatus'
33
28
  >;
@@ -41,12 +36,6 @@ export type DelegationControlActions = {
41
36
  this: HarnessActionContext,
42
37
  active: ActiveDelegation,
43
38
  ) => Promise<void>;
44
- retryDelegationAfterFailure: (
45
- this: HarnessActionContext,
46
- active: ActiveDelegation,
47
- failure: DelegationFailureDetails | undefined,
48
- reason: string,
49
- ) => boolean;
50
39
  pauseForDelegationFailure: (
51
40
  this: HarnessActionContext,
52
41
  reason: string,
@@ -119,53 +108,6 @@ async function cleanupDelegation(
119
108
  }
120
109
  }
121
110
 
122
- function retryDelegationAfterFailure(
123
- this: HarnessActionContext,
124
- active: ActiveDelegation,
125
- failure: DelegationFailureDetails | undefined,
126
- reason: string,
127
- ): boolean {
128
- if (!failure) return false;
129
- const fingerprint =
130
- this.delegationFailures.delegationFailureFingerprint(failure);
131
- const isRepeatedFailure = active.recoveryFailures.some(
132
- (previousFailure) => previousFailure.fingerprint === fingerprint,
133
- );
134
- if (
135
- active.recoveryAttemptCount >= MAX_DELEGATION_RECOVERY_ATTEMPTS ||
136
- isRepeatedFailure ||
137
- !this.delegationFailures.isRetryableTerminalFailure(failure) ||
138
- !this.delegationFailures.isSafeToRetryDelegation(
139
- active.policy,
140
- active.broadRecoveryAuthorized,
141
- failure.replayAudit,
142
- ) ||
143
- !this.isSessionActive ||
144
- this.sessionEpoch !== active.sessionEpoch ||
145
- !this.run ||
146
- this.run.status !== 'running' ||
147
- this.run.runId !== active.runId ||
148
- this.run.currentStepId !== active.stepId ||
149
- this.run.currentStepDigest !== active.stepDigest ||
150
- this.activeDelegation
151
- ) {
152
- return false;
153
- }
154
- const workflow = this.catalog.workflows.get(this.run.workflowId);
155
- if (!workflow) return false;
156
-
157
- const attempt = active.recoveryAttemptCount + 1;
158
- this.latestContext?.ui.notify(
159
- `Automatic recovery for "${active.stepId}" after a subagent failure (${attempt}/${MAX_DELEGATION_RECOVERY_ATTEMPTS})`,
160
- 'warning',
161
- );
162
- this.launchCurrentStep(workflow, {
163
- attempt,
164
- failures: [...active.recoveryFailures, { fingerprint, reason }],
165
- });
166
- return true;
167
- }
168
-
169
111
  function pauseForDelegationFailure(
170
112
  this: HarnessActionContext,
171
113
  reason: string,
@@ -266,7 +208,6 @@ export function createDelegationControlActions(): DelegationControlActions {
266
208
  return {
267
209
  cancelActiveDelegation,
268
210
  cleanupDelegation,
269
- retryDelegationAfterFailure,
270
211
  pauseForDelegationFailure,
271
212
  pauseForExecutionFailure,
272
213
  retainUnconfirmedDelegation,