@agent-relay/sdk 2.3.14 → 3.0.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 (179) hide show
  1. package/README.md +68 -838
  2. package/bin/agent-relay-broker +0 -0
  3. package/dist/__tests__/contract-fixtures.test.d.ts +2 -0
  4. package/dist/__tests__/contract-fixtures.test.d.ts.map +1 -0
  5. package/dist/__tests__/contract-fixtures.test.js +85 -0
  6. package/dist/__tests__/contract-fixtures.test.js.map +1 -0
  7. package/dist/__tests__/facade.test.d.ts +2 -0
  8. package/dist/__tests__/facade.test.d.ts.map +1 -0
  9. package/dist/__tests__/facade.test.js +257 -0
  10. package/dist/__tests__/facade.test.js.map +1 -0
  11. package/dist/__tests__/integration.test.d.ts +2 -0
  12. package/dist/__tests__/integration.test.d.ts.map +1 -0
  13. package/dist/__tests__/integration.test.js +164 -0
  14. package/dist/__tests__/integration.test.js.map +1 -0
  15. package/dist/__tests__/pty.test.d.ts +2 -0
  16. package/dist/__tests__/pty.test.d.ts.map +1 -0
  17. package/dist/__tests__/pty.test.js +20 -0
  18. package/dist/__tests__/pty.test.js.map +1 -0
  19. package/dist/__tests__/quickstart.test.d.ts +2 -0
  20. package/dist/__tests__/quickstart.test.d.ts.map +1 -0
  21. package/dist/__tests__/quickstart.test.js +176 -0
  22. package/dist/__tests__/quickstart.test.js.map +1 -0
  23. package/dist/__tests__/spawn-from-env.test.d.ts +2 -0
  24. package/dist/__tests__/spawn-from-env.test.d.ts.map +1 -0
  25. package/dist/__tests__/spawn-from-env.test.js +206 -0
  26. package/dist/__tests__/spawn-from-env.test.js.map +1 -0
  27. package/dist/__tests__/unit.test.d.ts +2 -0
  28. package/dist/__tests__/unit.test.d.ts.map +1 -0
  29. package/dist/__tests__/unit.test.js +311 -0
  30. package/dist/__tests__/unit.test.js.map +1 -0
  31. package/dist/browser.d.ts +16 -0
  32. package/dist/browser.d.ts.map +1 -0
  33. package/dist/browser.js +19 -0
  34. package/dist/browser.js.map +1 -0
  35. package/dist/client.d.ts +138 -526
  36. package/dist/client.d.ts.map +1 -1
  37. package/dist/client.js +407 -1509
  38. package/dist/client.js.map +1 -1
  39. package/dist/consensus-helpers.d.ts +103 -0
  40. package/dist/consensus-helpers.d.ts.map +1 -0
  41. package/dist/consensus-helpers.js +147 -0
  42. package/dist/consensus-helpers.js.map +1 -0
  43. package/dist/consensus.d.ts +72 -0
  44. package/dist/consensus.d.ts.map +1 -0
  45. package/dist/consensus.js +378 -0
  46. package/dist/consensus.js.map +1 -0
  47. package/dist/examples/demo.d.ts +2 -0
  48. package/dist/examples/demo.d.ts.map +1 -0
  49. package/dist/examples/demo.js +63 -0
  50. package/dist/examples/demo.js.map +1 -0
  51. package/dist/examples/example.d.ts +2 -0
  52. package/dist/examples/example.d.ts.map +1 -0
  53. package/dist/examples/example.js +80 -0
  54. package/dist/examples/example.js.map +1 -0
  55. package/dist/examples/quickstart.d.ts +2 -0
  56. package/dist/examples/quickstart.d.ts.map +1 -0
  57. package/dist/examples/quickstart.js +56 -0
  58. package/dist/examples/quickstart.js.map +1 -0
  59. package/dist/examples/ralph-loop.d.ts +2 -0
  60. package/dist/examples/ralph-loop.d.ts.map +1 -0
  61. package/dist/examples/ralph-loop.js +281 -0
  62. package/dist/examples/ralph-loop.js.map +1 -0
  63. package/dist/examples/workflow-superiority.d.ts +32 -0
  64. package/dist/examples/workflow-superiority.d.ts.map +1 -0
  65. package/dist/examples/workflow-superiority.js +1421 -0
  66. package/dist/examples/workflow-superiority.js.map +1 -0
  67. package/dist/index.d.ts +13 -20
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +12 -26
  70. package/dist/index.js.map +1 -1
  71. package/dist/logs.d.ts +70 -25
  72. package/dist/logs.d.ts.map +1 -1
  73. package/dist/logs.js +238 -42
  74. package/dist/logs.js.map +1 -1
  75. package/dist/models.d.ts +9 -0
  76. package/dist/models.d.ts.map +1 -0
  77. package/dist/models.js +17 -0
  78. package/dist/models.js.map +1 -0
  79. package/dist/protocol.d.ts +366 -0
  80. package/dist/protocol.d.ts.map +1 -0
  81. package/dist/protocol.js +2 -0
  82. package/dist/protocol.js.map +1 -0
  83. package/dist/pty.d.ts +8 -0
  84. package/dist/pty.d.ts.map +1 -0
  85. package/dist/pty.js +26 -0
  86. package/dist/pty.js.map +1 -0
  87. package/dist/relay-adapter.d.ts +139 -0
  88. package/dist/relay-adapter.d.ts.map +1 -0
  89. package/dist/relay-adapter.js +210 -0
  90. package/dist/relay-adapter.js.map +1 -0
  91. package/dist/relay.d.ts +277 -0
  92. package/dist/relay.d.ts.map +1 -0
  93. package/dist/relay.js +853 -0
  94. package/dist/relay.js.map +1 -0
  95. package/dist/shadow.d.ts +101 -0
  96. package/dist/shadow.d.ts.map +1 -0
  97. package/dist/shadow.js +174 -0
  98. package/dist/shadow.js.map +1 -0
  99. package/dist/spawn-from-env.d.ts +77 -0
  100. package/dist/spawn-from-env.d.ts.map +1 -0
  101. package/dist/spawn-from-env.js +172 -0
  102. package/dist/spawn-from-env.js.map +1 -0
  103. package/dist/workflows/barrier.d.ts +72 -0
  104. package/dist/workflows/barrier.d.ts.map +1 -0
  105. package/dist/workflows/barrier.js +162 -0
  106. package/dist/workflows/barrier.js.map +1 -0
  107. package/dist/workflows/builder.d.ts +114 -0
  108. package/dist/workflows/builder.d.ts.map +1 -0
  109. package/dist/workflows/builder.js +201 -0
  110. package/dist/workflows/builder.js.map +1 -0
  111. package/dist/workflows/cli.d.ts +11 -0
  112. package/dist/workflows/cli.d.ts.map +1 -0
  113. package/dist/workflows/cli.js +144 -0
  114. package/dist/workflows/cli.js.map +1 -0
  115. package/dist/workflows/coordinator.d.ts +73 -0
  116. package/dist/workflows/coordinator.d.ts.map +1 -0
  117. package/dist/workflows/coordinator.js +647 -0
  118. package/dist/workflows/coordinator.js.map +1 -0
  119. package/dist/workflows/custom-steps.d.ts +73 -0
  120. package/dist/workflows/custom-steps.d.ts.map +1 -0
  121. package/dist/workflows/custom-steps.js +321 -0
  122. package/dist/workflows/custom-steps.js.map +1 -0
  123. package/dist/workflows/dry-run-format.d.ts +6 -0
  124. package/dist/workflows/dry-run-format.d.ts.map +1 -0
  125. package/dist/workflows/dry-run-format.js +68 -0
  126. package/dist/workflows/dry-run-format.js.map +1 -0
  127. package/dist/workflows/file-db.d.ts +33 -0
  128. package/dist/workflows/file-db.d.ts.map +1 -0
  129. package/dist/workflows/file-db.js +108 -0
  130. package/dist/workflows/file-db.js.map +1 -0
  131. package/dist/workflows/index.d.ts +15 -0
  132. package/dist/workflows/index.d.ts.map +1 -0
  133. package/dist/workflows/index.js +15 -0
  134. package/dist/workflows/index.js.map +1 -0
  135. package/dist/workflows/memory-db.d.ts +17 -0
  136. package/dist/workflows/memory-db.d.ts.map +1 -0
  137. package/dist/workflows/memory-db.js +33 -0
  138. package/dist/workflows/memory-db.js.map +1 -0
  139. package/dist/workflows/run.d.ts +38 -0
  140. package/dist/workflows/run.d.ts.map +1 -0
  141. package/dist/workflows/run.js +25 -0
  142. package/dist/workflows/run.js.map +1 -0
  143. package/dist/workflows/runner.d.ts +320 -0
  144. package/dist/workflows/runner.d.ts.map +1 -0
  145. package/dist/workflows/runner.js +2821 -0
  146. package/dist/workflows/runner.js.map +1 -0
  147. package/dist/workflows/state.d.ts +77 -0
  148. package/dist/workflows/state.d.ts.map +1 -0
  149. package/dist/workflows/state.js +140 -0
  150. package/dist/workflows/state.js.map +1 -0
  151. package/dist/workflows/templates.d.ts +47 -0
  152. package/dist/workflows/templates.d.ts.map +1 -0
  153. package/dist/workflows/templates.js +405 -0
  154. package/dist/workflows/templates.js.map +1 -0
  155. package/dist/workflows/trajectory.d.ts +87 -0
  156. package/dist/workflows/trajectory.d.ts.map +1 -0
  157. package/dist/workflows/trajectory.js +441 -0
  158. package/dist/workflows/trajectory.js.map +1 -0
  159. package/dist/workflows/types.d.ts +306 -0
  160. package/dist/workflows/types.d.ts.map +1 -0
  161. package/dist/workflows/types.js +23 -0
  162. package/dist/workflows/types.js.map +1 -0
  163. package/dist/workflows/validator.d.ts +11 -0
  164. package/dist/workflows/validator.d.ts.map +1 -0
  165. package/dist/workflows/validator.js +128 -0
  166. package/dist/workflows/validator.js.map +1 -0
  167. package/package.json +59 -53
  168. package/dist/discovery.d.ts +0 -10
  169. package/dist/discovery.d.ts.map +0 -1
  170. package/dist/discovery.js +0 -22
  171. package/dist/discovery.js.map +0 -1
  172. package/dist/errors.d.ts +0 -9
  173. package/dist/errors.d.ts.map +0 -1
  174. package/dist/errors.js +0 -9
  175. package/dist/errors.js.map +0 -1
  176. package/dist/protocol/index.d.ts +0 -8
  177. package/dist/protocol/index.d.ts.map +0 -1
  178. package/dist/protocol/index.js +0 -8
  179. package/dist/protocol/index.js.map +0 -1
@@ -0,0 +1,1421 @@
1
+ /**
2
+ * Workflow Superiority — Multi-Agent Implementation Campaign
3
+ *
4
+ * A fully specified DAG workflow that orchestrates Claude (lead + reviewer)
5
+ * and Codex workers across five implementation tiers to make the relay
6
+ * broker-sdk workflow system decisively superior to Agno and Swarms AI.
7
+ *
8
+ * Architecture:
9
+ * - Claude lead: orchestrates each phase, approves trajectories, makes
10
+ * final architectural decisions
11
+ * - Codex workers: implement code changes, one per specialization domain
12
+ * - Claude code-reviewer: independent review after every implementation phase
13
+ * (separate from lead to avoid confirmation bias)
14
+ *
15
+ * DAG phases:
16
+ * Phase 0 → Codebase analysis + spec approval
17
+ * Phase 1 → Type system extension (condition/loop/router/hitl/sub-workflow)
18
+ * Phase 2 → Execution engine (runner handles new primitives + session)
19
+ * Phase 3 → Meta-orchestration (parallel with Phase 4)
20
+ * Phase 4 → Storage backends (parallel with Phase 3)
21
+ * Phase 5 → Deployment & observability
22
+ * Phase 6 → Integration validation + final lead sign-off
23
+ *
24
+ * Run:
25
+ * npx tsx src/examples/workflow-superiority.ts
26
+ *
27
+ * Environment:
28
+ * RELAY_API_KEY — optional. If absent the runner auto-provisions a
29
+ * fresh Relaycast workspace for each run (fully isolated, no caching).
30
+ */
31
+ import { workflow } from '../workflows/builder.js';
32
+ // ── Spec constants ────────────────────────────────────────────────────────────
33
+ const WORKFLOW_ROOT = 'packages/sdk/src/workflows';
34
+ const TYPES_FILE = `${WORKFLOW_ROOT}/types.ts`;
35
+ const RUNNER_FILE = `${WORKFLOW_ROOT}/runner.ts`;
36
+ const BUILDER_FILE = `${WORKFLOW_ROOT}/builder.ts`;
37
+ const SCHEMA_FILE = `${WORKFLOW_ROOT}/schema.json`;
38
+ const INDEX_FILE = `${WORKFLOW_ROOT}/index.ts`;
39
+ const MEMORY_DB = `${WORKFLOW_ROOT}/memory-db.ts`;
40
+ const COORDINATOR = `${WORKFLOW_ROOT}/coordinator.ts`;
41
+ const BARRIER_FILE = `${WORKFLOW_ROOT}/barrier.ts`;
42
+ const TEMPLATES = `${WORKFLOW_ROOT}/templates.ts`;
43
+ // NOTE: No withExit() wrapper needed — the WorkflowRunner automatically
44
+ // appends self-termination instructions in spawnAndWait() with the agent's
45
+ // actual runtime name. Adding a second exit instruction wastes tokens.
46
+ // ── Event handler ─────────────────────────────────────────────────────────────
47
+ const onEvent = (event) => {
48
+ const ts = new Date().toISOString();
49
+ switch (event.type) {
50
+ case 'run:started':
51
+ console.log(`[${ts}] 🚀 run started runId=${event.runId}`);
52
+ break;
53
+ case 'run:completed':
54
+ console.log(`[${ts}] ✅ run complete runId=${event.runId}`);
55
+ break;
56
+ case 'run:failed':
57
+ console.error(`[${ts}] ❌ run failed runId=${event.runId} error=${event.error}`);
58
+ break;
59
+ case 'step:started':
60
+ console.log(`[${ts}] → ${event.stepName}`);
61
+ break;
62
+ case 'step:completed':
63
+ console.log(`[${ts}] ✓ ${event.stepName}`);
64
+ break;
65
+ case 'step:failed':
66
+ console.error(`[${ts}] ✗ ${event.stepName}: ${event.error}`);
67
+ break;
68
+ case 'step:skipped':
69
+ console.log(`[${ts}] ⊘ ${event.stepName} (skipped)`);
70
+ break;
71
+ case 'step:retrying':
72
+ console.log(`[${ts}] ↺ ${event.stepName} attempt=${event.attempt}`);
73
+ break;
74
+ }
75
+ };
76
+ // ── Workflow definition ───────────────────────────────────────────────────────
77
+ const result = await workflow('broker-sdk-superiority')
78
+ .description('Five-phase multi-agent campaign to make relay broker-sdk workflow system ' +
79
+ 'decisively superior to Agno and Swarms AI. Claude leads; Codex implements; ' +
80
+ 'Claude reviews after every phase.')
81
+ .pattern('dag')
82
+ .channel('wf-broker-sdk-superiority')
83
+ .maxConcurrency(3)
84
+ .timeout(28_800_000) // 8 hours — this is a large implementation campaign
85
+ // ── Agents ────────────────────────────────────────────────────────────────
86
+ .agent('lead', {
87
+ cli: 'claude',
88
+ role: 'Lead architect. Sets direction, reviews each phase output, approves ' +
89
+ 'trajectories, and resolves architectural conflicts. Has final say on ' +
90
+ 'all design decisions.',
91
+ retries: 2,
92
+ })
93
+ .agent('code-reviewer', {
94
+ cli: 'claude',
95
+ role: 'Independent code reviewer. Reviews implementation quality, correctness, ' +
96
+ 'TypeScript type safety, test coverage, and integration coherence after ' +
97
+ 'every phase. Catches issues the lead may have missed.',
98
+ retries: 2,
99
+ })
100
+ .agent('spec-analyst', {
101
+ cli: 'codex',
102
+ role: 'Codebase analyst. Reads the existing workflow source files and produces ' +
103
+ 'a precise, file-by-file implementation plan for all five improvement tiers.',
104
+ retries: 2,
105
+ })
106
+ .agent('schema-implementer', {
107
+ cli: 'codex',
108
+ role: 'Type system specialist. Extends TypeScript interfaces and JSON Schema ' +
109
+ 'definitions to support new workflow primitives.',
110
+ retries: 2,
111
+ })
112
+ .agent('engine-implementer', {
113
+ cli: 'codex',
114
+ role: 'Execution engine specialist. Implements new step-type execution logic ' +
115
+ 'inside WorkflowRunner, adds session concept, and expands the event system.',
116
+ retries: 3,
117
+ })
118
+ .agent('meta-implementer', {
119
+ cli: 'codex',
120
+ role: 'Meta-orchestration specialist. Implements sub-workflow composition, ' +
121
+ 'AutoWorkflowBuilder, and semantic pattern selection.',
122
+ retries: 2,
123
+ })
124
+ .agent('storage-implementer', {
125
+ cli: 'codex',
126
+ role: 'Storage backend specialist. Implements PostgresWorkflowDb, ' +
127
+ 'SqliteWorkflowDb, and RedisWorkflowDb adapters.',
128
+ retries: 2,
129
+ })
130
+ .agent('deploy-implementer', {
131
+ cli: 'codex',
132
+ role: 'Deployment and observability specialist. Implements relay workflow serve ' +
133
+ 'HTTP server, OTel tracing integration, and CLI improvements.',
134
+ retries: 2,
135
+ })
136
+ .agent('test-validator', {
137
+ cli: 'codex',
138
+ role: 'Integration test specialist. Validates all phases compile, tests pass, ' + 'and exports are correct.',
139
+ retries: 2,
140
+ })
141
+ // ── Phase 0: Codebase Analysis ────────────────────────────────────────────
142
+ .step('codebase-analysis', {
143
+ agent: 'spec-analyst',
144
+ task: `
145
+ You are the first step in a large improvement campaign for the relay broker-sdk
146
+ workflow system. Your job is to read the existing source files and produce a
147
+ concrete, file-by-file implementation plan.
148
+
149
+ READ THESE FILES THOROUGHLY:
150
+ - ${TYPES_FILE}
151
+ - ${RUNNER_FILE}
152
+ - ${BUILDER_FILE}
153
+ - ${SCHEMA_FILE}
154
+ - ${INDEX_FILE}
155
+ - ${MEMORY_DB}
156
+ - ${COORDINATOR}
157
+ - ${BARRIER_FILE}
158
+ - ${TEMPLATES}
159
+
160
+ PRODUCE A DETAILED PLAN covering the following improvements (in priority order):
161
+
162
+ TIER 1 — Schema Primitives (new step types in types.ts + schema.json):
163
+ a) condition step: WorkflowStep gains optional "condition" string (CEL expr)
164
+ and optional "type" discriminant. When condition evaluates false → skip.
165
+ b) loop step: new LoopStepGroup interface { type:"loop", name, steps[],
166
+ until?: string (CEL expr on step outputs), maxIterations: number }
167
+ c) router step: new RouterStepGroup interface { type:"router", name,
168
+ selector: string (CEL expr), routes: RouterRoute[], default?: string[] }
169
+ RouterRoute: { match: string, steps: string[] }
170
+ d) parallel step group: new ParallelStepGroup interface { type:"parallel",
171
+ name, barrier?: "all"|"any"|"majority", timeout?: number, steps[] }
172
+ e) hitl step: new HitlStep interface { type:"hitl", name, message: string,
173
+ channel?: string, timeout?: number, onTimeout?: "skip"|"fail" }
174
+ f) flow shorthand: RelayYamlConfig gains optional "flow?: string" field,
175
+ parsed as "A -> B, C -> D" notation generating dependsOn edges
176
+ g) sub-workflow step: new SubWorkflowStep interface { type:"sub-workflow",
177
+ name, workflow: string (path or registry name), vars?: Record<string,string> }
178
+ h) Promote retries + timeoutMs to top-level WorkflowStep fields (they already
179
+ exist — verify they are exported and used correctly in runner.ts)
180
+
181
+ TIER 2 — Execution Engine (runner.ts):
182
+ a) Condition evaluation: before executing a step, if step.condition is set,
183
+ evaluate the CEL expression; if false, mark step skipped
184
+ b) Loop execution: LoopStepGroup runs its steps[].steps repeatedly until
185
+ until-condition is true or maxIterations reached
186
+ c) Router execution: evaluate selector, match route, execute branch steps
187
+ d) Parallel group: run steps concurrently with barrier semantics
188
+ e) HITL execution: pause run, write hitl-pending file, poll for response file
189
+ f) Sub-workflow: recursively create a WorkflowRunner and execute the referenced
190
+ config, return its output as the step output
191
+ g) Session concept: WorkflowRunner gains optional sessionId param; a session
192
+ groups multiple runs and shares state across them via SessionStore
193
+ h) Input schema validation: RelayYamlConfig gains optional inputSchema field
194
+ (JSON Schema object); validate vars before execution begins
195
+ i) Fallback agent: WorkflowStep gains optional fallbackAgent?: string;
196
+ if primary agent fails all retries, retry once with fallbackAgent
197
+ j) Expanded events: add 15+ new WorkflowEvent union members for loop iterations,
198
+ condition evaluations, router selections, hitl pauses, session updates
199
+
200
+ TIER 3 — Meta-Orchestration:
201
+ a) AutoWorkflowBuilder: new exported class that takes a task string and
202
+ available CLIs, calls a meta-agent to generate RelayYamlConfig, validates
203
+ it, and returns it (or optionally executes immediately)
204
+ b) Workflow registry: simple JSON file-based registry at
205
+ ~/.agent-relay/workflow-registry.json that maps name→path; CLI commands
206
+ relay workflow list, search, install, publish
207
+
208
+ TIER 4 — Storage Backends:
209
+ a) PostgresWorkflowDb: implements WorkflowDb interface using node-postgres (pg)
210
+ with schema migration on first connect
211
+ b) SqliteWorkflowDb: implements WorkflowDb using better-sqlite3 (sync API
212
+ wrapped in async interface)
213
+ c) RedisWorkflowDb: implements WorkflowDb using ioredis with JSON serialization
214
+ and optional TTL on run records
215
+ All three: exported from packages/sdk/src/workflows/db/ submodule
216
+
217
+ TIER 5 — Deployment & Observability:
218
+ a) relay workflow serve: new CLI subcommand that starts an Express/Fastify HTTP
219
+ server exposing POST /run, GET /runs/:id, GET /runs/:id/events (SSE),
220
+ POST /runs/:id/hitl/:step, POST /runs/:id/abort, GET /health
221
+ b) OTel tracing: optional otel?: { exportTo: string, endpoint: string,
222
+ serviceName: string } in RelayYamlConfig; runner creates spans for
223
+ run start/end and each step start/end using @opentelemetry/sdk-node
224
+ c) CLI improvements: relay workflow dry-run, relay workflow inspect,
225
+ relay workflow replay --from <step>
226
+
227
+ For each tier, specify:
228
+ 1. Which existing files change and what lines/sections to modify
229
+ 2. Which new files to create and their full path
230
+ 3. New TypeScript interface/type definitions (exact syntax)
231
+ 4. New function signatures with JSDoc
232
+
233
+ Output a structured plan with clear section headers.
234
+ End your output with: ANALYSIS_COMPLETE
235
+ `,
236
+ retries: 2,
237
+ verification: { type: 'output_contains', value: 'ANALYSIS_COMPLETE' },
238
+ })
239
+ .step('spec-approval', {
240
+ agent: 'lead',
241
+ task: `
242
+ Review the codebase analysis produced by the spec-analyst:
243
+
244
+ {{steps.codebase-analysis.output}}
245
+
246
+ Your job:
247
+ 1. Validate that the plan covers all five tiers correctly
248
+ 2. Identify any architectural risks or conflicts (e.g., breaking changes to
249
+ WorkflowStep that would break existing templates)
250
+ 3. Clarify the execution order for each tier
251
+ 4. Approve or amend the plan — note any changes clearly
252
+ 5. Establish a non-negotiable constraint: ALL existing tests in
253
+ packages/sdk/src/__tests__/ must remain passing after each phase
254
+
255
+ Key architectural decisions to make explicit:
256
+ - How should WorkflowStep handle the new type discriminant without breaking
257
+ existing YAML that has no "type" field? (answer: type defaults to "agent")
258
+ - Should LoopStepGroup and RouterStepGroup be separate from WorkflowStep in
259
+ the types, or unified via a discriminated union?
260
+ - Is the session concept stored in the same WorkflowDb or a separate SessionDb?
261
+ - For the hitl step, what is the polling mechanism? (file-based, HTTP endpoint,
262
+ or webhook?) Choose the simplest that works without requiring a server.
263
+
264
+ Output a concise approved plan with your decisions. End with: SPEC_APPROVED
265
+ `,
266
+ dependsOn: ['codebase-analysis'],
267
+ retries: 2,
268
+ verification: { type: 'output_contains', value: 'SPEC_APPROVED' },
269
+ })
270
+ // ── Phase 1: Type System Extension ────────────────────────────────────────
271
+ .step('p1-type-system', {
272
+ agent: 'schema-implementer',
273
+ task: `
274
+ Phase 1a: Extend the TypeScript type system for new workflow primitives.
275
+
276
+ Approved spec context:
277
+ {{steps.spec-approval.output}}
278
+
279
+ YOUR TASK: Modify ${TYPES_FILE} to add all new types.
280
+
281
+ SPECIFIC CHANGES:
282
+
283
+ 1. Add a StepType discriminant:
284
+ export type StepType = "agent" | "condition" | "loop" | "router" | "parallel" | "hitl" | "sub-workflow";
285
+
286
+ 2. Extend WorkflowStep to include new optional fields:
287
+ - type?: StepType (default "agent" when absent)
288
+ - condition?: string (CEL expression; step skipped if evaluates false)
289
+ - fallbackAgent?: string (agent name to use if primary fails all retries)
290
+
291
+ 3. Add new composite step group interfaces:
292
+
293
+ export interface LoopStepGroup {
294
+ type: "loop";
295
+ name: string;
296
+ description?: string;
297
+ steps: WorkflowStep[];
298
+ until?: string; // CEL expression evaluated after each iteration
299
+ maxIterations: number; // required — prevents runaway loops
300
+ timeoutMs?: number;
301
+ }
302
+
303
+ export interface RouterRoute {
304
+ match: string; // CEL expression or substring match
305
+ steps: string[]; // names of steps to execute for this route
306
+ }
307
+
308
+ export interface RouterStepGroup {
309
+ type: "router";
310
+ name: string;
311
+ description?: string;
312
+ selector: string; // CEL expression producing a string value
313
+ routes: RouterRoute[];
314
+ default?: string[]; // step names to run if no route matches
315
+ timeoutMs?: number;
316
+ }
317
+
318
+ export interface ParallelStepGroup {
319
+ type: "parallel";
320
+ name: string;
321
+ description?: string;
322
+ barrier?: "all" | "any" | "majority"; // default "all"
323
+ steps: WorkflowStep[];
324
+ timeoutMs?: number;
325
+ }
326
+
327
+ export interface HitlStep {
328
+ type: "hitl";
329
+ name: string;
330
+ description?: string;
331
+ message: string; // human-readable prompt shown to approver
332
+ channel?: string; // notification target e.g. "slack:#approvals"
333
+ timeoutMs?: number; // how long to wait before applying onTimeout
334
+ onTimeout?: "skip" | "fail" | "use-default"; // default "fail"
335
+ defaultResponse?: string; // used when onTimeout is "use-default"
336
+ }
337
+
338
+ export interface SubWorkflowStep {
339
+ type: "sub-workflow";
340
+ name: string;
341
+ description?: string;
342
+ workflow: string; // path to relay.yaml or registry name
343
+ vars?: Record<string, string>; // variable substitutions for the sub-workflow
344
+ timeoutMs?: number;
345
+ }
346
+
347
+ 4. Create a union type for any step or step group:
348
+ export type AnyWorkflowStep =
349
+ | WorkflowStep
350
+ | LoopStepGroup
351
+ | RouterStepGroup
352
+ | ParallelStepGroup
353
+ | HitlStep
354
+ | SubWorkflowStep;
355
+
356
+ 5. Update WorkflowDefinition.steps to use AnyWorkflowStep[]:
357
+ steps: AnyWorkflowStep[];
358
+
359
+ 6. Add session types:
360
+ export interface SessionConfig {
361
+ persist?: boolean; // default false
362
+ historyRuns?: number; // how many prior runs to inject as context
363
+ ttlMs?: number;
364
+ }
365
+
366
+ export interface SessionRow {
367
+ id: string;
368
+ workflowName: string;
369
+ runIds: string[];
370
+ stateSnapshot: Record<string, unknown>;
371
+ createdAt: string;
372
+ updatedAt: string;
373
+ }
374
+
375
+ 7. Add inputSchema to RelayYamlConfig:
376
+ inputSchema?: Record<string, unknown>; // JSON Schema object
377
+
378
+ 8. Add session to RelayYamlConfig:
379
+ session?: SessionConfig;
380
+
381
+ 9. Expand WorkflowEvent union in runner.ts — ADD to the existing union
382
+ (note: WorkflowEvent is defined in runner.ts, not types.ts, so create a
383
+ comment in types.ts pointing to runner.ts where these will be added):
384
+ // New events will be added to WorkflowEvent in runner.ts:
385
+ // loop:iteration-started, loop:iteration-completed, loop:ended,
386
+ // condition:evaluated, condition:skipped, router:evaluated,
387
+ // parallel:branch-started, parallel:branch-completed,
388
+ // hitl:paused, hitl:responded, hitl:timeout,
389
+ // subworkflow:started, subworkflow:completed,
390
+ // session:state-updated, validation:failed, fallback:agent-switched
391
+
392
+ 10. Export all new types from ${INDEX_FILE}
393
+
394
+ Make ALL changes. Run: npx tsc --noEmit to verify no type errors.
395
+ End your output with: TYPES_COMPLETE
396
+ `,
397
+ dependsOn: ['spec-approval'],
398
+ retries: 2,
399
+ verification: { type: 'output_contains', value: 'TYPES_COMPLETE' },
400
+ })
401
+ .step('p1-json-schema', {
402
+ agent: 'schema-implementer',
403
+ task: `
404
+ Phase 1b: Update the JSON Schema to match the new TypeScript types.
405
+
406
+ Prior type changes:
407
+ {{steps.p1-type-system.output}}
408
+
409
+ YOUR TASK: Update ${SCHEMA_FILE} to add definitions for all new step group types.
410
+
411
+ Add new $defs entries:
412
+ - StepType enum definition
413
+ - LoopStepGroup object with required: [type, name, steps, maxIterations]
414
+ - RouterRoute object
415
+ - RouterStepGroup object with required: [type, name, selector, routes]
416
+ - ParallelStepGroup object with required: [type, name, steps]
417
+ - HitlStep object with required: [type, name, message]
418
+ - SubWorkflowStep object with required: [type, name, workflow]
419
+ - SessionConfig object
420
+ - AnyWorkflowStep as oneOf the above plus existing WorkflowStep
421
+
422
+ Update workflow.steps array to use anyOf: [WorkflowStep, AnyWorkflowStep]
423
+ Update top-level RelayYamlConfig to include inputSchema and session properties.
424
+
425
+ Also update packages/sdk/src/workflows/builder.ts:
426
+ - Add a new builder method for sessions:
427
+ session(config: SessionConfig): this
428
+ - Add a new builder method for input schema:
429
+ inputSchema(schema: Record<string, unknown>): this
430
+
431
+ Verify with: cat ${SCHEMA_FILE} | python3 -m json.tool (or equivalent JSON lint)
432
+ End your output with: SCHEMA_COMPLETE
433
+ `,
434
+ dependsOn: ['p1-type-system'],
435
+ retries: 2,
436
+ verification: { type: 'output_contains', value: 'SCHEMA_COMPLETE' },
437
+ })
438
+ .step('p1-lead-review', {
439
+ agent: 'lead',
440
+ task: `
441
+ Phase 1 Lead Review: Validate the type system and schema changes.
442
+
443
+ Type system changes:
444
+ {{steps.p1-type-system.output}}
445
+
446
+ Schema changes:
447
+ {{steps.p1-json-schema.output}}
448
+
449
+ REVIEW CRITERIA:
450
+ 1. Do the new TypeScript interfaces correctly model the intended semantics?
451
+ 2. Is the AnyWorkflowStep discriminated union correctly structured? Each variant
452
+ must have a unique "type" literal so TypeScript can narrow the type.
453
+ 3. Does the WorkflowStep backward compatibility hold? (existing YAML with no
454
+ "type" field should still work — type defaults to "agent")
455
+ 4. Are the new fields in RelayYamlConfig (inputSchema, session) properly optional?
456
+ 5. Does the builder extension make sense? Are the new methods ergonomic?
457
+ 6. Any naming inconsistencies between the TypeScript types and JSON Schema?
458
+
459
+ If you find issues, describe them specifically with file:line references.
460
+ If the phase is acceptable, state what should be fixed in the engine phase.
461
+ End with: PHASE_1_APPROVED (even if you request minor fixes — fixes go to
462
+ the code-reviewer, who will direct the implementer if needed)
463
+ `,
464
+ dependsOn: ['p1-json-schema'],
465
+ retries: 1,
466
+ verification: { type: 'output_contains', value: 'PHASE_1_APPROVED' },
467
+ })
468
+ .step('p1-code-review', {
469
+ agent: 'code-reviewer',
470
+ task: `
471
+ Phase 1 Independent Code Review: TypeScript type system extension.
472
+ (Running in parallel with lead review — review the code independently.)
473
+
474
+ JSON Schema changes:
475
+ {{steps.p1-json-schema.output}}
476
+
477
+ YOUR INDEPENDENT REVIEW of ${TYPES_FILE}, ${SCHEMA_FILE}, ${BUILDER_FILE}:
478
+
479
+ Check:
480
+ 1. TypeScript strict-mode compliance — no implicit any, all fields typed
481
+ 2. JSDoc on every exported interface and type
482
+ 3. Correct use of discriminated unions (each variant's type field is a
483
+ string literal, not just string)
484
+ 4. AnyWorkflowStep is correctly exported from ${INDEX_FILE}
485
+ 5. Builder methods follow existing patterns (return this for chaining)
486
+ 6. JSON Schema $defs are correctly referenced in anyOf arrays
487
+ 7. No circular references in the type definitions
488
+ 8. Run: npx tsc --noEmit from packages/sdk and confirm zero errors
489
+
490
+ List any issues found. For each: file, line (if known), problem, fix required.
491
+ If zero issues: explicitly state "No issues found."
492
+ End with: CODE_REVIEW_1_COMPLETE
493
+ `,
494
+ dependsOn: ['p1-json-schema'],
495
+ retries: 1,
496
+ verification: { type: 'output_contains', value: 'CODE_REVIEW_1_COMPLETE' },
497
+ })
498
+ // ── Phase 2: Execution Engine ─────────────────────────────────────────────
499
+ .step('p2-condition-loop', {
500
+ agent: 'engine-implementer',
501
+ task: `
502
+ Phase 2a: Implement condition + loop execution in the WorkflowRunner.
503
+
504
+ Phase 1 review context:
505
+ {{steps.p1-code-review.output}}
506
+
507
+ YOUR TASK: Modify ${RUNNER_FILE} to handle condition and loop step types.
508
+
509
+ CONDITION STEP EXECUTION:
510
+ In the executeStep method (or wherever individual steps are dispatched):
511
+ 1. Check if step.type === "condition" OR if step.condition is set on a regular step
512
+ 2. If step.condition exists, evaluate the CEL expression using a lightweight
513
+ evaluator. Use the cel-js npm package (add to package.json if not present)
514
+ OR implement a minimal evaluator that handles:
515
+ - String contains: "X in Y.output"
516
+ - String equality: "steps.X.output == 'VALUE'"
517
+ - Boolean AND/OR
518
+ The expression context object should include:
519
+ { steps: Record<name, { output: string, status: string }>,
520
+ vars: VariableContext }
521
+ 3. If condition evaluates to false: mark step as "skipped", emit step:skipped event
522
+ 4. If condition evaluates to true (or no condition): proceed normally
523
+
524
+ Also add to WorkflowEvent union in runner.ts:
525
+ | { type: "condition:evaluated"; runId: string; stepName: string; result: boolean; expression: string }
526
+ | { type: "condition:skipped"; runId: string; stepName: string }
527
+
528
+ LOOP STEP GROUP EXECUTION:
529
+ Add a new method: private async executeLoopGroup(loop: LoopStepGroup, ...): Promise<string>
530
+ 1. Run loop.steps sequentially up to loop.maxIterations times
531
+ 2. After each iteration, evaluate loop.until CEL expression if provided
532
+ 3. If until evaluates to true, break and return last step output
533
+ 4. If maxIterations reached without satisfaction, fail with descriptive error
534
+ 5. Track step outputs by iteration: steps in context include "loop.STEP.output"
535
+ for the current iteration
536
+
537
+ Add to WorkflowEvent:
538
+ | { type: "loop:iteration-started"; runId: string; loopName: string; iteration: number }
539
+ | { type: "loop:iteration-completed"; runId: string; loopName: string; iteration: number; continuing: boolean }
540
+ | { type: "loop:ended"; runId: string; loopName: string; reason: "condition-met" | "max-iterations" }
541
+
542
+ In the main execution loop (findReadySteps / executeSteps), detect when a step
543
+ is a LoopStepGroup and route it to executeLoopGroup.
544
+
545
+ Run: npx tsc --noEmit to verify.
546
+ End your output with: CONDITION_LOOP_COMPLETE
547
+ `,
548
+ dependsOn: ['p1-code-review'],
549
+ retries: 2,
550
+ verification: { type: 'output_contains', value: 'CONDITION_LOOP_COMPLETE' },
551
+ })
552
+ .step('p2-input-validation', {
553
+ agent: 'schema-implementer',
554
+ task: `
555
+ Phase 2b: Implement input schema validation (runs in parallel with p2-condition-loop).
556
+
557
+ Phase 1 review context:
558
+ {{steps.p1-code-review.output}}
559
+
560
+ YOUR TASK: Add input schema validation to WorkflowRunner before execution starts.
561
+
562
+ In ${RUNNER_FILE}, in the execute() method, BEFORE the first findReadySteps call:
563
+
564
+ 1. If config.inputSchema is defined:
565
+ a. Import or inline a minimal JSON Schema validator. Use the ajv npm package
566
+ (add to package.json dependencies if not present).
567
+ b. Compile the schema: const validate = ajv.compile(config.inputSchema)
568
+ c. Validate the vars object against the schema
569
+ d. If invalid: emit { type: "validation:failed", runId, errors: AjvError[] }
570
+ and throw WorkflowValidationError with a human-readable message listing
571
+ each field error
572
+
573
+ 2. Add WorkflowValidationError class (extends Error) to runner.ts:
574
+ export class WorkflowValidationError extends Error {
575
+ constructor(public readonly errors: unknown[]) {
576
+ super("Workflow input validation failed: " + JSON.stringify(errors));
577
+ this.name = "WorkflowValidationError";
578
+ }
579
+ }
580
+
581
+ 3. Add to WorkflowEvent:
582
+ | { type: "validation:failed"; runId: string; errors: unknown[] }
583
+ | { type: "validation:passed"; runId: string }
584
+
585
+ 4. Export WorkflowValidationError from ${INDEX_FILE}
586
+
587
+ 5. Update builder.ts: add the inputSchema method to WorkflowBuilder that sets
588
+ config.inputSchema (this was noted in Phase 1 but implement it now if not done)
589
+
590
+ Run: npx tsc --noEmit to verify.
591
+ End your output with: INPUT_VALIDATION_COMPLETE
592
+ `,
593
+ dependsOn: ['p1-code-review'],
594
+ retries: 2,
595
+ verification: { type: 'output_contains', value: 'INPUT_VALIDATION_COMPLETE' },
596
+ })
597
+ .step('p2-router-hitl', {
598
+ agent: 'engine-implementer',
599
+ task: `
600
+ Phase 2c: Implement router + HITL step execution.
601
+
602
+ Condition/loop implementation:
603
+ {{steps.p2-condition-loop.output}}
604
+
605
+ YOUR TASK: Add router and HITL execution to ${RUNNER_FILE}.
606
+
607
+ ROUTER STEP GROUP EXECUTION:
608
+ Add: private async executeRouterGroup(router: RouterStepGroup, context, ...): Promise<string>
609
+ 1. Evaluate router.selector CEL expression to get a string value
610
+ 2. Iterate router.routes, find first route where match expression is true
611
+ (match can be substring check: selectorValue.includes(route.match) or
612
+ full CEL evaluation depending on complexity)
613
+ 3. Collect the branch step names to execute (from route.steps or router.default)
614
+ 4. Execute those steps (they must exist in the parent workflow's step list)
615
+ 5. Return concatenated outputs of the executed branch steps
616
+
617
+ Add to WorkflowEvent:
618
+ | { type: "router:evaluated"; runId: string; routerName: string; selectorValue: string; matchedRoute: string | null }
619
+ | { type: "router:branch-started"; runId: string; routerName: string; stepName: string }
620
+
621
+ HITL STEP EXECUTION:
622
+ Add: private async executeHitlStep(hitl: HitlStep, runId: string, ...): Promise<string>
623
+ 1. Write a file: {summaryDir}/{runId}/hitl-{hitl.name}-pending.json containing:
624
+ { runId, stepName: hitl.name, message: hitl.message, channel: hitl.channel,
625
+ pendingSince: ISO timestamp, timeoutMs: hitl.timeoutMs }
626
+ 2. Emit { type: "hitl:paused", runId, stepName, message, channel }
627
+ 3. Poll every 5 seconds for a response file:
628
+ {summaryDir}/{runId}/hitl-{hitl.name}-response.json
629
+ The response file should contain: { response: string, respondedBy?: string }
630
+ 4. If timeoutMs elapses without response:
631
+ - If onTimeout === "skip": mark step skipped, emit hitl:timeout, return ""
632
+ - If onTimeout === "use-default": emit hitl:timeout, return hitl.defaultResponse ?? ""
633
+ - Otherwise (default "fail"): throw Error("HITL step timed out")
634
+ 5. On response: delete pending file, emit hitl:responded, return response.response
635
+
636
+ Add to WorkflowEvent:
637
+ | { type: "hitl:paused"; runId: string; stepName: string; message: string; channel?: string }
638
+ | { type: "hitl:responded"; runId: string; stepName: string; response: string; respondedBy?: string }
639
+ | { type: "hitl:timeout"; runId: string; stepName: string; action: string }
640
+
641
+ Run: npx tsc --noEmit
642
+ End your output with: ROUTER_HITL_COMPLETE
643
+ `,
644
+ dependsOn: ['p2-condition-loop'],
645
+ retries: 2,
646
+ verification: { type: 'output_contains', value: 'ROUTER_HITL_COMPLETE' },
647
+ })
648
+ .step('p2-session-fallback', {
649
+ agent: 'engine-implementer',
650
+ task: `
651
+ Phase 2d: Implement session concept and fallback agent switching.
652
+
653
+ Router/HITL implementation:
654
+ {{steps.p2-router-hitl.output}}
655
+
656
+ YOUR TASK: Extend WorkflowRunner with session support and fallback agents.
657
+
658
+ SESSION CONCEPT:
659
+ A session groups multiple workflow runs, shares state across them, and can
660
+ inject prior run outputs as context into new runs.
661
+
662
+ 1. Extend WorkflowRunnerOptions in runner.ts:
663
+ sessionId?: string; // if provided, this run joins a named session
664
+
665
+ 2. Add a simple SessionStore (in-memory, backed by WorkflowDb if available):
666
+ - getSession(sessionId): returns { runIds: string[], stateSnapshot: Record }
667
+ - addRunToSession(sessionId, runId): appends runId to session run list
668
+ - getSessionHistory(sessionId, n): returns last n run outputs as context string
669
+
670
+ 3. In execute(), if sessionId is provided:
671
+ a. Load session history (last config.session?.historyRuns ?? 3 runs)
672
+ b. Prepend history context to the first step's task:
673
+ "[Session history - prior runs]\n{history}\n[Current task]\n{task}"
674
+ c. After run completes, call addRunToSession(sessionId, runId)
675
+
676
+ 4. Add to WorkflowEvent:
677
+ | { type: "session:run-added"; sessionId: string; runId: string }
678
+ | { type: "session:history-injected"; sessionId: string; historyRuns: number }
679
+
680
+ FALLBACK AGENT:
681
+ In the step execution method, after all retries on the primary agent are
682
+ exhausted:
683
+ 1. Check if step.fallbackAgent is defined
684
+ 2. If yes: look up the fallback agent definition by name from config.agents
685
+ 3. Re-attempt execution once with the fallback agent
686
+ 4. Emit { type: "fallback:agent-switched"; runId, stepName, fromAgent, toAgent }
687
+ 5. If fallback also fails: mark step as failed
688
+
689
+ Add to WorkflowEvent:
690
+ | { type: "fallback:agent-switched"; runId: string; stepName: string; fromAgent: string; toAgent: string }
691
+
692
+ Also implement the flow shorthand parser (if RelayYamlConfig.flow is set):
693
+ parseFlowString("A -> B, C -> D"): WorkflowStep[]
694
+ - "A -> B" means B.dependsOn = [A]
695
+ - "B, C" means B and C are in parallel (no dependency between them)
696
+ - "A, B -> C" means C.dependsOn = [A, B]
697
+ - Call this in execute() before building the step graph if config.flow is set
698
+
699
+ Run: npx tsc --noEmit
700
+ End your output with: SESSION_FALLBACK_COMPLETE
701
+ `,
702
+ dependsOn: ['p2-router-hitl'],
703
+ retries: 2,
704
+ verification: { type: 'output_contains', value: 'SESSION_FALLBACK_COMPLETE' },
705
+ })
706
+ .step('p2-lead-review', {
707
+ agent: 'lead',
708
+ task: `
709
+ Phase 2 Lead Review: Execution engine implementation.
710
+
711
+ Input validation:
712
+ {{steps.p2-input-validation.output}}
713
+
714
+ Session + fallback:
715
+ {{steps.p2-session-fallback.output}}
716
+
717
+ REVIEW:
718
+ 1. Is the CEL condition evaluator robust enough? Does it handle the common
719
+ patterns we need (output contains, equality, AND/OR)?
720
+ 2. Is the loop execution correctly isolated — do step names inside a loop
721
+ conflict with top-level step names in the output context?
722
+ 3. Is the HITL polling approach acceptable? (file-based polling every 5s)
723
+ Or should it use a simple readline/stdin approach instead?
724
+ 4. Does the session history injection make sense — will agents be overwhelmed
725
+ by injected context if historyRuns is large?
726
+ 5. Is the flow string parser correct for all cases:
727
+ "A -> B, C -> D" and "A, B -> C"?
728
+ 6. Are all new WorkflowEvent variants added to the union consistently?
729
+
730
+ Note any critical fixes needed before Phase 3 begins.
731
+ End with: PHASE_2_APPROVED
732
+ `,
733
+ dependsOn: ['p2-session-fallback', 'p2-input-validation'],
734
+ retries: 1,
735
+ verification: { type: 'output_contains', value: 'PHASE_2_APPROVED' },
736
+ })
737
+ .step('p2-code-review', {
738
+ agent: 'code-reviewer',
739
+ task: `
740
+ Phase 2 Independent Code Review: Execution engine.
741
+
742
+ Lead's review:
743
+ {{steps.p2-lead-review.output}}
744
+
745
+ INDEPENDENT REVIEW of ${RUNNER_FILE}:
746
+
747
+ 1. Correctness: does the condition evaluator correctly handle edge cases
748
+ (undefined step output, empty strings, non-boolean CEL results)?
749
+ 2. Loop safety: is there a guard against infinite loops if maxIterations is 0?
750
+ 3. HITL polling: does the polling correctly clean up pending files on both
751
+ success and timeout paths? No file handle leaks?
752
+ 4. Fallback agent: is the agent definition lookup null-safe? What if
753
+ fallbackAgent name doesn't exist in config.agents?
754
+ 5. Session history injection: is it correctly skipped when sessionId is absent?
755
+ 6. Flow parser: does it handle edge cases — empty string, single agent,
756
+ spaces around arrows?
757
+ 7. TypeScript: run npx tsc --noEmit and report results
758
+ 8. Existing tests: run npx jest packages/sdk and report pass/fail counts
759
+
760
+ List all issues with specific fixes. State "No issues" for clean sections.
761
+ End with: CODE_REVIEW_2_COMPLETE
762
+ `,
763
+ dependsOn: ['p2-lead-review'],
764
+ retries: 1,
765
+ verification: { type: 'output_contains', value: 'CODE_REVIEW_2_COMPLETE' },
766
+ })
767
+ // ── Phase 3: Meta-Orchestration (parallel with Phase 4) ───────────────────
768
+ .step('p3-sub-workflow', {
769
+ agent: 'meta-implementer',
770
+ task: `
771
+ Phase 3a: Implement sub-workflow step execution.
772
+
773
+ Phase 2 review context:
774
+ {{steps.p2-code-review.output}}
775
+
776
+ YOUR TASK: Add sub-workflow composition to ${RUNNER_FILE}.
777
+
778
+ SUB-WORKFLOW EXECUTION:
779
+ Add: private async executeSubWorkflow(step: SubWorkflowStep, vars, runId, ...): Promise<string>
780
+ 1. Resolve the workflow reference:
781
+ - If step.workflow starts with "./" or "/": treat as file path
782
+ - Otherwise: look up in the workflow registry at
783
+ ~/.agent-relay/workflow-registry.json (create if absent; plain JSON map)
784
+ 2. Load and parse the referenced relay.yaml file
785
+ 3. Merge step.vars with the current run's vars (step.vars take precedence)
786
+ 4. Create a new WorkflowRunner instance (child runner), share the same DB
787
+ 5. Execute the sub-workflow config via child runner
788
+ 6. Return the sub-workflow's run output (join all step outputs)
789
+
790
+ Add to WorkflowEvent:
791
+ | { type: "subworkflow:started"; runId: string; stepName: string; workflowRef: string }
792
+ | { type: "subworkflow:completed"; runId: string; stepName: string; output: string }
793
+ | { type: "subworkflow:failed"; runId: string; stepName: string; error: string }
794
+
795
+ WORKFLOW REGISTRY:
796
+ Create packages/sdk/src/workflows/registry.ts:
797
+
798
+ export interface WorkflowRegistryEntry {
799
+ name: string;
800
+ path: string;
801
+ description?: string;
802
+ tags?: string[];
803
+ installedAt: string;
804
+ }
805
+
806
+ export class WorkflowRegistry {
807
+ private readonly registryPath: string;
808
+ // Load registry from ~/.agent-relay/workflow-registry.json
809
+ async list(): Promise<WorkflowRegistryEntry[]>
810
+ async get(name: string): Promise<WorkflowRegistryEntry | null>
811
+ async register(entry: WorkflowRegistryEntry): Promise<void>
812
+ async unregister(name: string): Promise<void>
813
+ async resolvePath(nameOrPath: string): Promise<string>
814
+ // If starts with ./ or / return as-is; else look up in registry
815
+ }
816
+
817
+ Export WorkflowRegistry from ${INDEX_FILE}.
818
+
819
+ Run: npx tsc --noEmit
820
+ End with: SUB_WORKFLOW_COMPLETE
821
+ `,
822
+ dependsOn: ['p2-code-review'],
823
+ retries: 2,
824
+ verification: { type: 'output_contains', value: 'SUB_WORKFLOW_COMPLETE' },
825
+ })
826
+ .step('p3-auto-builder', {
827
+ agent: 'meta-implementer',
828
+ task: `
829
+ Phase 3b: Implement AutoWorkflowBuilder.
830
+
831
+ Sub-workflow implementation:
832
+ {{steps.p3-sub-workflow.output}}
833
+
834
+ YOUR TASK: Create packages/sdk/src/workflows/auto-builder.ts
835
+
836
+ The AutoWorkflowBuilder analyzes a task description and uses a meta-agent
837
+ to generate a complete RelayYamlConfig automatically.
838
+
839
+ export interface AutoBuildOptions {
840
+ availableClis?: AgentCli[]; // defaults to ["claude", "codex"]
841
+ maxAgents?: number; // defaults to 5
842
+ maxSteps?: number; // defaults to 10
843
+ preferredPattern?: SwarmPattern;
844
+ dryRun?: boolean; // if true, return config without executing
845
+ metaCli?: AgentCli; // CLI to use for the meta-agent (default "claude")
846
+ }
847
+
848
+ export interface AutoBuildResult {
849
+ config: RelayYamlConfig;
850
+ yaml: string;
851
+ reasoning: string; // why the meta-agent chose this structure
852
+ run?: WorkflowRunRow; // present if dryRun is false
853
+ }
854
+
855
+ export class AutoWorkflowBuilder {
856
+ constructor(private readonly options: AutoBuildOptions = {}) {}
857
+
858
+ async build(task: string): Promise<AutoBuildResult> {
859
+ // 1. Construct a meta-prompt that instructs the meta-agent to:
860
+ // a. Analyze the task
861
+ // b. Select the best swarm pattern
862
+ // c. Define agents (using available CLIs)
863
+ // d. Define workflow steps with appropriate dependencies
864
+ // e. Output a valid relay.yaml string (fenced in \`\`\`yaml...\`\`\`)
865
+ // f. Explain its reasoning
866
+ // 2. Spawn the meta-agent via AgentRelay
867
+ // 3. Extract the YAML from the response (find \`\`\`yaml ... \`\`\` block)
868
+ // 4. Parse and validate the config using WorkflowRunner.parseYamlString
869
+ // 5. If dryRun: return { config, yaml, reasoning }
870
+ // 6. Else: execute via WorkflowRunner and return { config, yaml, reasoning, run }
871
+ }
872
+
873
+ // Convenience: build and run immediately
874
+ async run(task: string): Promise<WorkflowRunRow> {
875
+ const result = await this.build(task);
876
+ if (!result.run) throw new Error("Set dryRun: false to execute");
877
+ return result.run;
878
+ }
879
+ }
880
+
881
+ // Convenience export
882
+ export async function autoWorkflow(task: string, options?: AutoBuildOptions): Promise<AutoBuildResult> {
883
+ return new AutoWorkflowBuilder(options).build(task);
884
+ }
885
+
886
+ Export AutoWorkflowBuilder and autoWorkflow from ${INDEX_FILE}.
887
+
888
+ Also add the meta-workflow type to types.ts if not already done:
889
+ export type MetaWorkflowConfig = RelayYamlConfig & { type: "meta-workflow" };
890
+ (A meta-workflow is a relay.yaml where steps are sub-workflow steps)
891
+
892
+ Run: npx tsc --noEmit
893
+ End with: AUTO_BUILDER_COMPLETE
894
+ `,
895
+ dependsOn: ['p3-sub-workflow'],
896
+ retries: 2,
897
+ verification: { type: 'output_contains', value: 'AUTO_BUILDER_COMPLETE' },
898
+ })
899
+ // ── Phase 4: Storage Backends (parallel with Phase 3) ────────────────────
900
+ .step('p4-db-adapters', {
901
+ agent: 'storage-implementer',
902
+ task: `
903
+ Phase 4: Implement production-ready WorkflowDb adapters.
904
+ (Runs in parallel with Phase 3 — no dependency between them.)
905
+
906
+ Phase 2 review context:
907
+ {{steps.p2-code-review.output}}
908
+
909
+ Create a new directory: packages/sdk/src/workflows/db/
910
+
911
+ Create these files:
912
+
913
+ 1. packages/sdk/src/workflows/db/postgres.ts
914
+ export class PostgresWorkflowDb implements WorkflowDb {
915
+ constructor(options: { connectionString: string; tablePrefix?: string })
916
+ // Creates tables on first connect if they don't exist:
917
+ // {prefix}workflow_runs, {prefix}workflow_steps
918
+ // Schema mirrors WorkflowRunRow and WorkflowStepRow
919
+ // Uses node-postgres (pg) — add to package.json if absent
920
+ async insertRun(run: WorkflowRunRow): Promise<void>
921
+ async updateRun(id: string, patch: Partial<WorkflowRunRow>): Promise<void>
922
+ async getRun(id: string): Promise<WorkflowRunRow | null>
923
+ async insertStep(step: WorkflowStepRow): Promise<void>
924
+ async updateStep(id: string, patch: Partial<WorkflowStepRow>): Promise<void>
925
+ async getStepsByRunId(runId: string): Promise<WorkflowStepRow[]>
926
+ async close(): Promise<void>
927
+ }
928
+
929
+ 2. packages/sdk/src/workflows/db/sqlite.ts
930
+ export class SqliteWorkflowDb implements WorkflowDb {
931
+ constructor(options: { path: string; tablePrefix?: string })
932
+ // Uses better-sqlite3 — synchronous API wrapped in async methods
933
+ // Same schema as PostgresWorkflowDb
934
+ // Creates tables if they don't exist on construction
935
+ }
936
+
937
+ 3. packages/sdk/src/workflows/db/redis.ts
938
+ export class RedisWorkflowDb implements WorkflowDb {
939
+ constructor(options: {
940
+ url: string;
941
+ keyPrefix?: string; // default "relay:workflow:"
942
+ runTtlMs?: number; // optional TTL on run records
943
+ })
944
+ // Uses ioredis — add to package.json if absent
945
+ // Stores runs as JSON strings at key: {prefix}run:{id}
946
+ // Stores step lists at key: {prefix}steps:{runId} (Redis list)
947
+ // Stores individual steps at: {prefix}step:{id}
948
+ }
949
+
950
+ 4. packages/sdk/src/workflows/db/index.ts
951
+ export { PostgresWorkflowDb } from './postgres.js';
952
+ export { SqliteWorkflowDb } from './sqlite.js';
953
+ export { RedisWorkflowDb } from './redis.js';
954
+
955
+ 5. Update packages/sdk/src/workflows/index.ts to export from db/:
956
+ export * from './db/index.js';
957
+
958
+ IMPORTANT: Add the three packages to package.json as optional peer dependencies
959
+ with peerDependenciesMeta markings optional: true, so users only need to install
960
+ the adapter they use.
961
+
962
+ Run: npx tsc --noEmit (adapters will have type errors only if packages are absent;
963
+ mark them as type-only imports with // @ts-expect-error if needed with clear comment)
964
+ End with: DB_ADAPTERS_COMPLETE
965
+ `,
966
+ dependsOn: ['p2-code-review'],
967
+ retries: 2,
968
+ verification: { type: 'output_contains', value: 'DB_ADAPTERS_COMPLETE' },
969
+ })
970
+ // ── Phase 3+4 Combined Review ─────────────────────────────────────────────
971
+ .step('p34-lead-review', {
972
+ agent: 'lead',
973
+ task: `
974
+ Phase 3+4 Lead Review: Meta-orchestration and storage backends.
975
+
976
+ Phase 3 — AutoBuilder:
977
+ {{steps.p3-auto-builder.output}}
978
+
979
+ Phase 4 — DB adapters:
980
+ {{steps.p4-db-adapters.output}}
981
+
982
+ REVIEW:
983
+
984
+ META-ORCHESTRATION:
985
+ 1. Is the sub-workflow execution correctly isolated? (child runner should not
986
+ share the parent runner's event listeners, but should share the DB)
987
+ 2. Does the WorkflowRegistry file path (~/.agent-relay/) correctly use os.homedir()?
988
+ 3. Is the AutoWorkflowBuilder meta-prompt clear enough to reliably generate
989
+ valid relay.yaml configs? What guardrails are needed?
990
+ 4. Does the YAML extraction from meta-agent output handle cases where the
991
+ agent outputs multiple code blocks?
992
+
993
+ DB ADAPTERS:
994
+ 5. Are the SQL schemas in PostgresWorkflowDb and SqliteWorkflowDb correct?
995
+ (JSON columns for config/stateSnapshot, TEXT for status, ISO timestamps)
996
+ 6. Is the Redis adapter correctly handling concurrent updates?
997
+ (Multiple parallel steps updating the same run record — race condition risk)
998
+ 7. Are optional peer dependencies correctly marked in package.json?
999
+ 8. Do the adapters correctly handle NULL/missing optional fields?
1000
+
1001
+ State fixes needed for the code reviewer to validate.
1002
+ End with: PHASE_34_APPROVED
1003
+ `,
1004
+ dependsOn: ['p3-auto-builder', 'p4-db-adapters'],
1005
+ retries: 1,
1006
+ verification: { type: 'output_contains', value: 'PHASE_34_APPROVED' },
1007
+ })
1008
+ .step('p34-code-review', {
1009
+ agent: 'code-reviewer',
1010
+ task: `
1011
+ Phase 3+4 Independent Code Review.
1012
+
1013
+ Lead's notes:
1014
+ {{steps.p34-lead-review.output}}
1015
+
1016
+ REVIEW packages/sdk/src/workflows/registry.ts,
1017
+ packages/sdk/src/workflows/auto-builder.ts,
1018
+ packages/sdk/src/workflows/db/:
1019
+
1020
+ 1. Registry: does resolvePath correctly return file paths unchanged and look up
1021
+ by name for non-path strings? Is the registry JSON correctly pretty-printed?
1022
+ 2. AutoBuilder: is the meta-prompt templating safe from injection if the task
1023
+ string contains special characters or YAML-like content?
1024
+ 3. PostgresWorkflowDb: are SQL queries parameterized? (No string interpolation
1025
+ in SQL — security requirement)
1026
+ 4. SqliteWorkflowDb: does the sync API wrap correctly without blocking the event
1027
+ loop for extended periods?
1028
+ 5. RedisWorkflowDb: is the JSON serialization of WorkflowRunRow round-trip safe?
1029
+ (dates become strings, Record<string,unknown> stays correct)
1030
+ 6. All three adapters: does getStepsByRunId return steps in consistent order?
1031
+ 7. Run npx tsc --noEmit and report results
1032
+
1033
+ List issues with specific file + line references.
1034
+ End with: CODE_REVIEW_34_COMPLETE
1035
+ `,
1036
+ dependsOn: ['p34-lead-review'],
1037
+ retries: 1,
1038
+ verification: { type: 'output_contains', value: 'CODE_REVIEW_34_COMPLETE' },
1039
+ })
1040
+ // ── Phase 5: Deployment and Observability ─────────────────────────────────
1041
+ .step('p5-serve-command', {
1042
+ agent: 'deploy-implementer',
1043
+ task: `
1044
+ Phase 5a: Implement "relay workflow serve" HTTP server.
1045
+
1046
+ Phase 3+4 review context:
1047
+ {{steps.p34-code-review.output}}
1048
+
1049
+ YOUR TASK: Create packages/sdk/src/workflows/server.ts
1050
+
1051
+ Implement a lightweight HTTP server (use Node.js built-in http module — no
1052
+ Express/Fastify dependency) that exposes:
1053
+
1054
+ POST /run
1055
+ Body: { workflowPath: string, vars?: VariableContext, sessionId?: string }
1056
+ Response: { runId: string, status: "started" }
1057
+ Behavior: parse the relay.yaml at workflowPath, start execution (non-blocking),
1058
+ return runId immediately
1059
+
1060
+ GET /runs/:runId
1061
+ Response: WorkflowRunRow | { error: "not found" }
1062
+
1063
+ GET /runs/:runId/events
1064
+ Response: text/event-stream (Server-Sent Events)
1065
+ Each WorkflowEvent becomes a SSE data: {json}\n\n line
1066
+ Connection stays open until run completes or client disconnects
1067
+
1068
+ POST /runs/:runId/hitl/:stepName/respond
1069
+ Body: { response: string, respondedBy?: string }
1070
+ Behavior: write {summaryDir}/{runId}/hitl-{stepName}-response.json
1071
+ Response: { ok: true }
1072
+
1073
+ POST /runs/:runId/abort
1074
+ Behavior: call runner.abort(runId) if supported
1075
+ Response: { ok: true }
1076
+
1077
+ GET /health
1078
+ Response: { status: "ok", uptime: process.uptime() }
1079
+
1080
+ Also create packages/sdk/src/workflows/serve.ts — the CLI entry point:
1081
+ export async function serveWorkflows(options: {
1082
+ port?: number; // default 3747
1083
+ host?: string; // default "0.0.0.0"
1084
+ db?: WorkflowDb;
1085
+ }): Promise<void>
1086
+
1087
+ Export serveWorkflows from ${INDEX_FILE}.
1088
+
1089
+ The serve command will be integrated into the relay CLI in the next step.
1090
+
1091
+ Run: npx tsc --noEmit
1092
+ End with: SERVE_COMMAND_COMPLETE
1093
+ `,
1094
+ dependsOn: ['p34-code-review'],
1095
+ retries: 2,
1096
+ verification: { type: 'output_contains', value: 'SERVE_COMMAND_COMPLETE' },
1097
+ })
1098
+ .step('p5-otel-tracing', {
1099
+ agent: 'deploy-implementer',
1100
+ task: `
1101
+ Phase 5b: Implement OpenTelemetry tracing integration.
1102
+ (Running in parallel with p5-serve-command — these are independent.)
1103
+
1104
+ Phase 3+4 review context:
1105
+ {{steps.p34-code-review.output}}
1106
+
1107
+ YOUR TASK: Add optional OTel tracing to WorkflowRunner.
1108
+
1109
+ 1. Add otel config to RelayYamlConfig in types.ts (if not already present):
1110
+ telemetry?: {
1111
+ otel?: boolean; // enable OTel tracing
1112
+ endpoint?: string; // OTLP endpoint, default "http://localhost:4318"
1113
+ serviceName?: string; // default "relay-workflows"
1114
+ exportTo?: "otlp" | "console" | "none"; // default "otlp"
1115
+ }
1116
+
1117
+ 2. Create packages/sdk/src/workflows/tracing.ts:
1118
+
1119
+ export interface TracingOptions {
1120
+ enabled: boolean;
1121
+ endpoint?: string;
1122
+ serviceName?: string;
1123
+ exportTo?: "otlp" | "console" | "none";
1124
+ }
1125
+
1126
+ export class WorkflowTracer {
1127
+ constructor(options: TracingOptions) {}
1128
+
1129
+ // Create root span for workflow run
1130
+ startRun(runId: string, workflowName: string, pattern: string): Span
1131
+
1132
+ // Create child span for a step
1133
+ startStep(parentSpan: Span, stepName: string, agentName: string): Span
1134
+
1135
+ // Record events on spans
1136
+ recordEvent(span: Span, event: WorkflowEvent): void
1137
+
1138
+ // End spans
1139
+ endSpan(span: Span, status: "ok" | "error", error?: string): void
1140
+
1141
+ // Shutdown exporter cleanly
1142
+ async shutdown(): Promise<void>
1143
+ }
1144
+
1145
+ Use @opentelemetry/sdk-node and @opentelemetry/api (add as optional peer deps
1146
+ with peerDependenciesMeta optional: true in package.json).
1147
+
1148
+ Guard all OTel imports with a try/catch or dynamic import so the runner works
1149
+ without OTel installed:
1150
+ let tracer: WorkflowTracer | null = null;
1151
+ try {
1152
+ const { WorkflowTracer } = await import('./tracing.js');
1153
+ tracer = new WorkflowTracer(config.telemetry?.otel ? { enabled: true, ...config.telemetry } : { enabled: false });
1154
+ } catch {
1155
+ // OTel packages not installed — tracing disabled
1156
+ }
1157
+
1158
+ 3. In WorkflowRunner.execute():
1159
+ - If tracer enabled: create root run span
1160
+ - In executeStep(): create child step span, record start/complete/fail events
1161
+ - On run complete: end root span
1162
+
1163
+ Export WorkflowTracer from ${INDEX_FILE}.
1164
+
1165
+ Run: npx tsc --noEmit
1166
+ End with: OTEL_TRACING_COMPLETE
1167
+ `,
1168
+ dependsOn: ['p34-code-review'],
1169
+ retries: 2,
1170
+ verification: { type: 'output_contains', value: 'OTEL_TRACING_COMPLETE' },
1171
+ })
1172
+ .step('p5-cli-improvements', {
1173
+ agent: 'deploy-implementer',
1174
+ task: `
1175
+ Phase 5c: Implement CLI improvements.
1176
+
1177
+ OTel tracing implementation:
1178
+ {{steps.p5-otel-tracing.output}}
1179
+
1180
+ YOUR TASK: Find the CLI entry point for the relay workflow commands and add:
1181
+
1182
+ First, locate the CLI code (likely in packages/sdk/src/workflows/cli.ts):
1183
+ cat packages/sdk/src/workflows/cli.ts
1184
+
1185
+ Add these subcommands to the workflow CLI:
1186
+
1187
+ 1. relay workflow dry-run <yaml-path> [--var KEY=VALUE...]
1188
+ - Parse and validate the YAML (run validateConfig)
1189
+ - Resolve variable templates (show substituted values)
1190
+ - Show the resolved DAG: step names, dependencies, agent assignments
1191
+ - Show which steps can run in parallel
1192
+ - Do NOT actually execute — print "DRY RUN: would execute N steps across M agents"
1193
+
1194
+ 2. relay workflow inspect <yaml-path>
1195
+ - Show full config parsed and pretty-printed as JSON
1196
+ - Show detected swarm pattern with reason
1197
+ - Show agent topology (edges from SwarmCoordinator)
1198
+ - Show barrier definitions
1199
+ - Show coordination config
1200
+
1201
+ 3. relay workflow replay <runId> --from <step-name> [--db-path <sqlite-path>]
1202
+ - Load existing run record from DB (requires SqliteWorkflowDb)
1203
+ - Skip steps that completed successfully before the target step
1204
+ - Re-execute from the specified step name onwards
1205
+ - Useful for resuming failed runs without restarting from scratch
1206
+
1207
+ 4. relay workflow serve [--port <N>] [--db sqlite:<path>|postgres:<url>|redis:<url>]
1208
+ - Starts the HTTP server from p5-serve-command
1209
+ - Accepts DB connection string via --db flag, parses the scheme prefix
1210
+
1211
+ Also update the README at packages/sdk/src/workflows/README.md to document
1212
+ all new CLI commands with usage examples.
1213
+
1214
+ Run: npx tsc --noEmit
1215
+ End with: CLI_IMPROVEMENTS_COMPLETE
1216
+ `,
1217
+ dependsOn: ['p5-serve-command', 'p5-otel-tracing'],
1218
+ retries: 2,
1219
+ verification: { type: 'output_contains', value: 'CLI_IMPROVEMENTS_COMPLETE' },
1220
+ })
1221
+ .step('p5-lead-review', {
1222
+ agent: 'lead',
1223
+ task: `
1224
+ Phase 5 Lead Review: Deployment and observability.
1225
+
1226
+ Serve command:
1227
+ {{steps.p5-serve-command.output}}
1228
+
1229
+ OTel tracing:
1230
+ {{steps.p5-otel-tracing.output}}
1231
+
1232
+ CLI improvements:
1233
+ {{steps.p5-cli-improvements.output}}
1234
+
1235
+ REVIEW:
1236
+ 1. HTTP server: is SSE implemented correctly? (headers: Content-Type: text/event-stream,
1237
+ Cache-Control: no-cache, Connection: keep-alive; proper flushing with res.write)
1238
+ 2. HTTP server: is the HITL respond endpoint correctly writing the response file
1239
+ to the same path the runner is polling?
1240
+ 3. OTel: is the dynamic import guard (try/catch) robust? Will it work in CJS builds?
1241
+ 4. OTel: are span hierarchies correct — run span is parent, step spans are children?
1242
+ 5. CLI dry-run: does it show enough information to be useful for debugging?
1243
+ 6. CLI replay: what happens if a replay step depends on a step that was NOT
1244
+ completed in the prior run? (Should fail with clear error message.)
1245
+ 7. Is the README updated with accurate examples?
1246
+
1247
+ Note critical fixes. End with: PHASE_5_APPROVED
1248
+ `,
1249
+ dependsOn: ['p5-cli-improvements'],
1250
+ retries: 1,
1251
+ verification: { type: 'output_contains', value: 'PHASE_5_APPROVED' },
1252
+ })
1253
+ .step('p5-code-review', {
1254
+ agent: 'code-reviewer',
1255
+ task: `
1256
+ Phase 5 Independent Code Review: Deployment and observability.
1257
+ (Running in parallel with lead review — review the code independently.)
1258
+
1259
+ CLI improvements:
1260
+ {{steps.p5-cli-improvements.output}}
1261
+
1262
+ REVIEW packages/sdk/src/workflows/server.ts,
1263
+ packages/sdk/src/workflows/tracing.ts,
1264
+ packages/sdk/src/workflows/cli.ts:
1265
+
1266
+ 1. HTTP server: no prototype pollution risk in request body parsing?
1267
+ (Validate Content-Type, parse JSON safely with try/catch)
1268
+ 2. SSE endpoint: does it correctly handle client disconnect without leaving
1269
+ zombie event listeners on the WorkflowRunner?
1270
+ 3. HITL respond: path traversal risk? (runId and stepName used in file path —
1271
+ must sanitize to alphanumeric + hyphen only)
1272
+ 4. OTel: does shutdown() await the exporter flush before process.exit?
1273
+ 5. CLI replay: is the step-skip logic correct? (A skipped-previously-completed
1274
+ step should return its stored output for downstream template resolution)
1275
+ 6. Run npx jest packages/sdk and report pass/fail counts
1276
+ 7. Run npx tsc --noEmit and report results
1277
+
1278
+ End with: CODE_REVIEW_5_COMPLETE
1279
+ `,
1280
+ dependsOn: ['p5-cli-improvements'],
1281
+ retries: 1,
1282
+ verification: { type: 'output_contains', value: 'CODE_REVIEW_5_COMPLETE' },
1283
+ })
1284
+ // ── Phase 6: Integration Validation + Final Sign-Off ─────────────────────
1285
+ .step('integration-validation', {
1286
+ agent: 'test-validator',
1287
+ task: `
1288
+ Phase 6: Integration validation across all five tiers.
1289
+
1290
+ Phase 5 review:
1291
+ {{steps.p5-code-review.output}}
1292
+
1293
+ YOUR TASK: Run comprehensive validation of the full implementation.
1294
+
1295
+ 1. TYPE CHECK:
1296
+ cd packages/sdk && npx tsc --noEmit
1297
+ Report: zero errors or list all errors
1298
+
1299
+ 2. EXISTING TESTS:
1300
+ npx jest packages/sdk/src/__tests__/
1301
+ Report: pass count, fail count, any failures
1302
+
1303
+ 3. BUILD:
1304
+ cd packages/sdk && npm run build
1305
+ Report: success or errors
1306
+
1307
+ 4. INTEGRATION SMOKE TESTS — run each of these and report output:
1308
+
1309
+ a. Condition step test:
1310
+ Create a temporary relay.yaml with a condition step that checks if
1311
+ "SKIP" is in a prior step's output, run it via the WorkflowRunner
1312
+ in TypeScript (programmatic, not CLI), verify the step is skipped.
1313
+
1314
+ b. Loop step test:
1315
+ Create a temporary relay.yaml with a loop that runs max 3 iterations,
1316
+ verify it runs exactly 3 times when until-condition is never met.
1317
+
1318
+ c. Input validation test:
1319
+ Create a RelayYamlConfig with inputSchema requiring a "task" field,
1320
+ call runner.execute() without "task" in vars, verify WorkflowValidationError
1321
+ is thrown.
1322
+
1323
+ d. Flow shorthand test:
1324
+ Parse the flow string "planner -> developer, reviewer -> lead" and verify
1325
+ that developer.dependsOn = ["planner"], reviewer.dependsOn = ["planner"],
1326
+ lead.dependsOn = ["developer", "reviewer"].
1327
+
1328
+ e. SqliteWorkflowDb test:
1329
+ Create an in-memory SQLite DB, insert a run, update it, retrieve it,
1330
+ verify round-trip fidelity of all fields.
1331
+
1332
+ 5. EXPORTS CHECK:
1333
+ Verify that the following are exported from packages/sdk/src/workflows/index.ts:
1334
+ - WorkflowBuilder, workflow
1335
+ - WorkflowRunner, WorkflowRunnerOptions, WorkflowEvent, WorkflowEventListener
1336
+ - WorkflowValidationError
1337
+ - AutoWorkflowBuilder, autoWorkflow
1338
+ - WorkflowRegistry
1339
+ - SqliteWorkflowDb, PostgresWorkflowDb, RedisWorkflowDb
1340
+ - WorkflowTracer
1341
+ - All new types: AnyWorkflowStep, LoopStepGroup, RouterStepGroup,
1342
+ ParallelStepGroup, HitlStep, SubWorkflowStep, SessionConfig
1343
+
1344
+ Report full results. End with: INTEGRATION_VALIDATED
1345
+ `,
1346
+ dependsOn: ['p5-code-review', 'p5-lead-review'],
1347
+ retries: 2,
1348
+ verification: { type: 'output_contains', value: 'INTEGRATION_VALIDATED' },
1349
+ })
1350
+ .step('final-lead-review', {
1351
+ agent: 'lead',
1352
+ task: `
1353
+ FINAL LEAD REVIEW: Complete broker-sdk workflow superiority implementation.
1354
+
1355
+ Integration validation results:
1356
+ {{steps.integration-validation.output}}
1357
+
1358
+ This is the culmination of a five-phase implementation campaign. Your job:
1359
+
1360
+ 1. CAPABILITY AUDIT — verify we now have all of these (check integration results):
1361
+ □ condition step type (CEL-based conditional execution)
1362
+ □ loop step type (iterative with until-condition)
1363
+ □ router step type (runtime branch selection)
1364
+ □ parallel step group (explicit with any/majority barriers)
1365
+ □ HITL step type (human-in-the-loop with file-based pause/resume)
1366
+ □ sub-workflow step (workflow composition)
1367
+ □ flow shorthand (string notation "A -> B, C")
1368
+ □ session concept (multi-run state sharing)
1369
+ □ input schema validation (JSON Schema via Ajv)
1370
+ □ fallback agent switching
1371
+ □ AutoWorkflowBuilder (LLM-generated workflows)
1372
+ □ WorkflowRegistry (name-to-path resolution)
1373
+ □ PostgresWorkflowDb, SqliteWorkflowDb, RedisWorkflowDb
1374
+ □ HTTP serve command with SSE event streaming
1375
+ □ OTel tracing (optional, dynamic import)
1376
+ □ CLI: dry-run, inspect, replay, serve
1377
+ □ 30+ WorkflowEvent types
1378
+ □ All exports correct from index.ts
1379
+
1380
+ 2. COMPETITIVE POSITION — confirm we now surpass:
1381
+ AGNO: We have everything Agno has (condition=Condition, loop=Loop,
1382
+ router=Router, session, input validation, serve, OTel) PLUS barriers,
1383
+ consensus, HITL, polyglot backends, YAML portability, sub-workflow composition.
1384
+
1385
+ SWARMS: We have equivalent patterns PLUS out-of-process PTY isolation,
1386
+ YAML portability, true relay protocol, HITL, OTel, sub-workflow composition,
1387
+ HTTP serve. Swarms has more built-in swarm types (15+ vs our 10) but we
1388
+ cover all critical execution patterns with richer composition primitives.
1389
+
1390
+ 3. REMAINING GAPS (if any) — list anything that was not fully implemented
1391
+ and should be tracked as future work.
1392
+
1393
+ 4. DOCUMENTATION — confirm README.md in the workflows directory is updated
1394
+ with examples for all new features.
1395
+
1396
+ Produce a final capability report with the checklist above filled in.
1397
+ End with: IMPLEMENTATION_COMPLETE
1398
+ `,
1399
+ dependsOn: ['integration-validation'],
1400
+ retries: 1,
1401
+ verification: { type: 'output_contains', value: 'IMPLEMENTATION_COMPLETE' },
1402
+ })
1403
+ .onError('retry', { maxRetries: 2, retryDelayMs: 10_000 })
1404
+ .run({
1405
+ onEvent,
1406
+ vars: {
1407
+ // Override these at runtime if needed:
1408
+ // workflowRoot: 'packages/sdk/src/workflows',
1409
+ },
1410
+ });
1411
+ console.log('\n── Run complete ─────────────────────────────────────────────────');
1412
+ console.log(`status: ${result.status}`);
1413
+ console.log(`runId: ${result.id}`);
1414
+ console.log(`workflow: ${result.workflowName}`);
1415
+ console.log(`pattern: ${result.pattern}`);
1416
+ console.log(`started: ${result.startedAt}`);
1417
+ console.log(`completed: ${result.completedAt ?? '—'}`);
1418
+ if (result.error) {
1419
+ console.error(`error: ${result.error}`);
1420
+ }
1421
+ //# sourceMappingURL=workflow-superiority.js.map