@botpress/adk-cli 1.19.0-beta.4 → 1.19.0-beta.5

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 (65) hide show
  1. package/dist/{README-wjb7z7f0.md → README-824wa4jj.md} +1 -1
  2. package/dist/SKILL-75ym6tcj.md +257 -0
  3. package/dist/SKILL-chnwzmt1.md +548 -0
  4. package/dist/SKILL-d4bs53y3.md +244 -0
  5. package/dist/SKILL-h4b2f1n9.md +114 -0
  6. package/dist/SKILL-hbw5nt95.md +111 -0
  7. package/dist/actions-h7hxxvxr.md +823 -0
  8. package/dist/adk-build-emhwd0tg.md +39 -0
  9. package/dist/adk-debug-yrrdtpmb.md +23 -0
  10. package/dist/adk-dev-console-7z9rtjpy.md +17 -0
  11. package/dist/adk-document-ras6zzzh.md +68 -0
  12. package/dist/adk-eval-vx76dwq0.md +17 -0
  13. package/dist/adk-explain-fcsaw852.md +22 -0
  14. package/dist/adk-init-9kmxmbbe.md +30 -0
  15. package/dist/adk-ship-dybzn884.md +29 -0
  16. package/dist/adk-test-xq67savs.md +27 -0
  17. package/dist/adk-validate-1p4skv4m.md +31 -0
  18. package/dist/advanced-patterns-z86m9h1a.md +669 -0
  19. package/dist/agent-config-1aqvkrgf.md +450 -0
  20. package/dist/agent-steps-zj4c1n9t.md +115 -0
  21. package/dist/assets-q7jt8r2q.md +237 -0
  22. package/dist/autonomous-execution-fymc12e5.md +747 -0
  23. package/dist/cli-c31e06yr.md +1334 -0
  24. package/dist/cli.js +970 -566
  25. package/dist/commands.json +24 -3
  26. package/dist/common-failures-ht29w2db.md +231 -0
  27. package/dist/component-registry-bbpw0see.md +43 -0
  28. package/dist/context-api-760cvh0h.md +461 -0
  29. package/dist/conversation-analysis-8ka4nrm4.md +420 -0
  30. package/dist/conversation-lifecycle-4seqqjc1.md +311 -0
  31. package/dist/conversations-gwfvxaee.md +1111 -0
  32. package/dist/custom-components-y5wdgpey.md +167 -0
  33. package/dist/debug-workflow-vz84q80v.md +239 -0
  34. package/dist/desk-k54ktvsd.md +852 -0
  35. package/dist/doc-standards-ck6ax2jd.md +124 -0
  36. package/dist/eval-format-7n9brja4.md +354 -0
  37. package/dist/evals-y1zcr1mc.md +11 -0
  38. package/dist/explain-config-1yaq3zzd.md +424 -0
  39. package/dist/files-dzt0wxn3.md +918 -0
  40. package/dist/integration-actions-xang7tma.md +659 -0
  41. package/dist/integrations-k5ss07f4.md +266 -0
  42. package/dist/interfaces-6t0bpxs4.md +97 -0
  43. package/dist/knowledge-bases-20rg8p5c.md +456 -0
  44. package/dist/llm-debugging-76ng8rzm.md +209 -0
  45. package/dist/mcp-server-n1asyzhv.md +112 -0
  46. package/dist/messages-gkkg0b54.md +769 -0
  47. package/dist/model-configuration-f273kgd0.md +201 -0
  48. package/dist/multi-agent-dashboard-1mdfjdts.md +70 -0
  49. package/dist/pages-xn18xn73.md +273 -0
  50. package/dist/patterns-mistakes-5vb99dhf.md +469 -0
  51. package/dist/plugins-ttnx8f49.md +280 -0
  52. package/dist/tables-x45t36dd.md +1136 -0
  53. package/dist/tags-46td3qrg.md +516 -0
  54. package/dist/test-patterns-hj47w4nc.md +355 -0
  55. package/dist/testing-workflow-2vkp932f.md +225 -0
  56. package/dist/tools-wk7bq24z.md +581 -0
  57. package/dist/trace-summarization-hdyx1c2e.md +313 -0
  58. package/dist/traces-and-logs-f2kjfh19.md +203 -0
  59. package/dist/triggers-4j2cc86x.md +719 -0
  60. package/dist/ui-server-entry.js +952 -137
  61. package/dist/workflow-steps-mrdrf1c5.md +851 -0
  62. package/dist/workflows-gadx3s69.md +1408 -0
  63. package/dist/zai-agent-reference-e6644aad.md +454 -0
  64. package/dist/zai-complete-guide-mnna5d6f.md +1408 -0
  65. package/package.json +4 -4
@@ -0,0 +1,851 @@
1
+ # Workflow Step API
2
+
3
+ Complete reference for every method on the `step` object inside workflow handlers.
4
+
5
+ > **Prerequisites:** See [workflows.md](./workflows.md) for workflow basics — creating workflows, input/output/state schemas, request/notification definitions, instance management, and scheduling. This file covers only the step execution surface.
6
+
7
+ ## Table of Contents
8
+ - [step() — Named Steps](#step--named-steps)
9
+ - [step.request() — Pause for Conversation Data](#steprequest--pause-for-conversation-data)
10
+ - [step.notify() — Send Typed Notifications](#stepnotify--send-typed-notifications)
11
+ - [step.listen() — Pause Until External Resume](#steplisten--pause-until-external-resume)
12
+ - [step.sleep() / step.sleepUntil()](#stepsleep--stepsleepuntil)
13
+ - [step.map()](#stepmap)
14
+ - [step.forEach()](#stepforeach)
15
+ - [step.batch()](#stepbatch)
16
+ - [step.executeWorkflow()](#stepexecuteworkflow)
17
+ - [step.waitForWorkflow()](#stepwaitforworkflow)
18
+ - [step.progress()](#stepprogress)
19
+ - [step.fail()](#stepfail)
20
+ - [step.abort()](#stepabort)
21
+ - [Step Execution Model](#step-execution-model)
22
+ - [Patterns](#patterns)
23
+ - [Pitfalls](#pitfalls)
24
+
25
+ ---
26
+
27
+ ## step() — Named Steps
28
+
29
+ The base `step()` function executes a named unit of work with automatic retry, caching, and state persistence.
30
+
31
+ ### Signature
32
+
33
+ ```typescript
34
+ step<T>(
35
+ name: string,
36
+ run: ({ attempt }: { attempt: number }) => T | Promise<T>,
37
+ options?: { maxAttempts?: number }
38
+ ): Promise<T>
39
+ ```
40
+
41
+ ### Parameters
42
+
43
+ | Parameter | Type | Default | Description |
44
+ |-----------|------|---------|-------------|
45
+ | `name` | `string` | required | Unique identifier within the workflow. Must be stable across executions. |
46
+ | `run` | `({ attempt }) => T \| Promise<T>` | required | Function to execute. Receives current attempt number (0-indexed). |
47
+ | `options.maxAttempts` | `number` | `5` | Maximum retry attempts before the step fails permanently. |
48
+
49
+ ### Behavior
50
+
51
+ - **Idempotent**: If a step with this name already completed, its cached result is returned immediately.
52
+ - **Retries**: On failure, retries with exponential backoff (up to 5s between attempts).
53
+ - **Persistence**: Completed step output is serialized and persisted in workflow state. Date objects are automatically serialized/deserialized.
54
+ - **Nesting**: Steps can contain other steps. Sub-steps get their own retry scope.
55
+
56
+ ### Examples
57
+
58
+ ```typescript
59
+ // Basic step — fetch and cache a value
60
+ const user = await step('fetch-user', async () => {
61
+ return await api.getUser(input.userId)
62
+ })
63
+
64
+ // Step with retry awareness
65
+ const result = await step('call-api', async ({ attempt }) => {
66
+ console.log(`Attempt ${attempt}`)
67
+ return await externalApi.call(input.data)
68
+ }, { maxAttempts: 3 })
69
+
70
+ // Nested steps — each sub-step is independently retried and cached
71
+ const processed = await step('process', async () => {
72
+ const enriched = await step('enrich', async () => {
73
+ return await enrichData(user)
74
+ })
75
+ const validated = await step('validate', async () => {
76
+ return await validateData(enriched)
77
+ })
78
+ return { enriched, validated }
79
+ })
80
+ ```
81
+
82
+ ---
83
+
84
+ ## step.request() — Pause for Conversation Data
85
+
86
+ Pauses the workflow and asks the linked conversation for typed data. The workflow enters `listening` mode until data is provided via `workflow.provide()`.
87
+
88
+ ### Signature
89
+
90
+ ```typescript
91
+ step.request(
92
+ request: string,
93
+ message: string,
94
+ stepName?: string
95
+ ): Promise<RequestPayload>
96
+ ```
97
+
98
+ ### Parameters
99
+
100
+ | Parameter | Type | Default | Description |
101
+ |-----------|------|---------|-------------|
102
+ | `request` | `string` | required | Name of a request type defined in the workflow's `requests` schema. |
103
+ | `message` | `string` | required | Prompt message sent to the conversation as context. |
104
+ | `stepName` | `string` | defaults to the value of `request` | Custom step name. Required when the same request type is used multiple times. |
105
+
106
+ ### Behavior
107
+
108
+ 1. Creates a `WorkflowDataRequestEvent` on the conversation with the prompt, step name, and JSON schema.
109
+ 2. Sets workflow status to `listening`.
110
+ 3. Aborts the current execution. The workflow is frozen.
111
+ 4. When `workflow.provide(request, data, stepName?)` is called from the conversation handler, the data is written to the step's output and the workflow resumes.
112
+ 5. The returned value is validated against the request's Zod schema.
113
+
114
+ ### Conversation-Side Handling
115
+
116
+ The conversation handler receives a `type === "workflow_request"` event:
117
+
118
+ ```typescript
119
+ async handler({ type, request, conversation }) {
120
+ if (type === 'workflow_request') {
121
+ // request.workflow — the workflow instance
122
+ // request.name — the request type name
123
+ // request.step — the step name that is waiting
124
+ // request.prompt — the message string
125
+
126
+ // Provide data back to the workflow
127
+ await request.workflow.provide(request.name, userData, request.step)
128
+ }
129
+ }
130
+ ```
131
+
132
+ ### Examples
133
+
134
+ An IT request workflow — collecting multiple inputs using the same request type with custom step names:
135
+
136
+ ```typescript
137
+ export const SoftwareRequestWorkflow = new Workflow({
138
+ name: 'softwareRequest',
139
+ input: z.object({
140
+ requestedBy: z.string(),
141
+ department: z.string(),
142
+ }),
143
+ output: z.object({
144
+ status: z.enum(['approved', 'cancelled']),
145
+ summary: z.string(),
146
+ }),
147
+ requests: {
148
+ text_input: z.string(),
149
+ confirmation: z.boolean(),
150
+ },
151
+ handler: async ({ input, step }) => {
152
+ // Same request type, different step names — required for disambiguation
153
+ const software = await step.request(
154
+ 'text_input',
155
+ 'Which software or tool do you need access to?',
156
+ 'collect-software'
157
+ )
158
+
159
+ const justification = await step.request(
160
+ 'text_input',
161
+ 'Please provide a brief business justification.',
162
+ 'collect-justification'
163
+ )
164
+
165
+ const manager = await step.request(
166
+ 'text_input',
167
+ "Who is your manager?",
168
+ 'collect-manager'
169
+ )
170
+
171
+ // Different request type — step name defaults to 'confirmation'
172
+ const confirmed = await step.request(
173
+ 'confirmation',
174
+ `Submit request for ${software}?`
175
+ )
176
+
177
+ if (!confirmed) {
178
+ return { status: 'cancelled', summary: 'Cancelled by user.' }
179
+ }
180
+
181
+ const requestId = await step('submit-request', async () => {
182
+ // ... create record ...
183
+ return id
184
+ })
185
+
186
+ return { status: 'approved', summary: `Request ${requestId} submitted.` }
187
+ },
188
+ })
189
+ ```
190
+
191
+ ### Step Name Resolution Rules
192
+
193
+ When `workflow.provide(request, data)` is called without an explicit step name:
194
+ 1. If exactly one pending step matches the request type, it resolves automatically.
195
+ 2. If multiple pending steps match, it throws — you must pass the step name explicitly.
196
+ 3. If no pending step matches, it throws.
197
+
198
+ Always pass a custom `stepName` when using the same request type more than once.
199
+
200
+ ---
201
+
202
+ ## step.notify() — Send Typed Notifications
203
+
204
+ Sends a typed notification event to the linked conversation without pausing the workflow.
205
+
206
+ ### Signature
207
+
208
+ ```typescript
209
+ step.notify(
210
+ notification: string,
211
+ payload: NotificationPayload,
212
+ stepName?: string
213
+ ): Promise<void>
214
+ ```
215
+
216
+ ### Parameters
217
+
218
+ | Parameter | Type | Default | Description |
219
+ |-----------|------|---------|-------------|
220
+ | `notification` | `string` | required | Name of a notification defined in the workflow's `notifications` schema. |
221
+ | `payload` | `object` | required | Payload validated against the notification's Zod schema. |
222
+ | `stepName` | `string` | defaults to the value of `notification` | Custom step name. Required when emitting the same notification multiple times. |
223
+
224
+ ### Behavior
225
+
226
+ - Idempotent per step name — re-executing the same step returns cached state.
227
+ - Creates a `WorkflowNotifyEvent` on the conversation.
228
+ - Does NOT pause the workflow. Execution continues immediately.
229
+ - If the workflow has no `conversationId`, the notification is silently skipped (non-fatal).
230
+ - Notification delivery failures are logged but do not fail the workflow.
231
+
232
+ ### Examples
233
+
234
+ A support workflow — streaming progress updates during iteration:
235
+
236
+ ```typescript
237
+ export const TicketReviewWorkflow = new Workflow({
238
+ name: 'ticketReview',
239
+ input: z.object({ department: z.string() }),
240
+ output: z.object({ reviewed: z.number(), escalated: z.number(), summary: z.string() }),
241
+ notifications: {
242
+ progress: z.object({
243
+ phase: z.enum(['scanning', 'reviewing', 'complete']),
244
+ reviewed: z.number(),
245
+ total: z.number(),
246
+ message: z.string(),
247
+ }),
248
+ },
249
+ async handler({ input, step }) {
250
+ await step.notify('progress', {
251
+ phase: 'scanning', reviewed: 0, total: 0,
252
+ message: `Scanning tickets for ${input.department}...`,
253
+ }, 'progress-start')
254
+
255
+ const tickets = await step('fetch-tickets', async () => { /* ... */ })
256
+
257
+ await step.forEach('review-tickets', tickets, async (ticket, { i }) => {
258
+ // Process ticket...
259
+
260
+ // Each notification needs a unique step name
261
+ await step.notify('progress', {
262
+ phase: i + 1 >= tickets.length ? 'complete' : 'reviewing',
263
+ reviewed: i + 1,
264
+ total: tickets.length,
265
+ message: `Reviewed ${i + 1} of ${tickets.length}...`,
266
+ }, `progress-ticket-${i}`)
267
+ }, { concurrency: 1 })
268
+
269
+ return { reviewed: tickets.length, escalated: 0, summary: '...' }
270
+ },
271
+ })
272
+ ```
273
+
274
+ ---
275
+
276
+ ## step.listen() — Pause Until External Resume
277
+
278
+ Puts the workflow into `listening` mode and pauses execution. The workflow will resume when an external event triggers it (e.g., a `WorkflowContinueEvent`).
279
+
280
+ ### Signature
281
+
282
+ ```typescript
283
+ step.listen(name: string): Promise<void>
284
+ ```
285
+
286
+ ### Behavior
287
+
288
+ - Sets workflow status to `listening`.
289
+ - Aborts the current execution.
290
+ - On resume, the step is already marked complete and execution continues past it.
291
+
292
+ Use `step.listen()` when you need a generic pause point that is not tied to a request or sleep timer. For typed data collection, use `step.request()` instead.
293
+
294
+ Note: When a workflow is in `listening` status, nudge events on the linked conversation are suppressed. See [conversation-lifecycle.md](./conversation-lifecycle.md) for details.
295
+
296
+ ---
297
+
298
+ ## step.sleep() / step.sleepUntil()
299
+
300
+ Pause workflow execution for a duration or until a specific date.
301
+
302
+ ### Signatures
303
+
304
+ ```typescript
305
+ step.sleep(name: string, ms: number): Promise<void>
306
+ step.sleepUntil(name: string, date: Date | string): Promise<void>
307
+ ```
308
+
309
+ ### Behavior
310
+
311
+ **step.sleep():**
312
+ - For delays >= 10 seconds (or when remaining execution time is insufficient): schedules a `WorkflowContinueEvent` with the specified delay, sets status to `listening`, and aborts. The workflow resumes automatically when the timer fires.
313
+ - For delays < 10 seconds with sufficient execution time: uses an in-memory `setTimeout`. The workflow stays active.
314
+
315
+ **step.sleepUntil():**
316
+ - Computes `ms = targetDate - now - 10s buffer` and delegates to `step.sleep()`.
317
+ - If the date is in the past (or within the 10s buffer), returns immediately.
318
+
319
+ ### Examples
320
+
321
+ ```typescript
322
+ // Wait 5 minutes between API calls
323
+ await step.sleep('rate-limit-pause', 5 * 60 * 1000)
324
+
325
+ // Wait until a specific deadline
326
+ await step.sleepUntil('wait-for-deadline', new Date('2025-12-31T00:00:00Z'))
327
+
328
+ // Short delay (stays in-memory, no listening mode)
329
+ await step.sleep('brief-pause', 2000)
330
+ ```
331
+
332
+ ---
333
+
334
+ ## step.map()
335
+
336
+ Process an array of items in parallel with controlled concurrency, collecting results.
337
+
338
+ ### Signature
339
+
340
+ ```typescript
341
+ step.map<T, U>(
342
+ name: string,
343
+ items: T[],
344
+ run: (input: T, opts: { i: number }) => Promise<U>,
345
+ opts?: { maxAttempts?: number; concurrency?: number }
346
+ ): Promise<U[]>
347
+ ```
348
+
349
+ ### Parameters
350
+
351
+ | Parameter | Type | Default | Description |
352
+ |-----------|------|---------|-------------|
353
+ | `name` | `string` | required | Name for the map operation. |
354
+ | `items` | `T[]` | required | Array of items to process. |
355
+ | `run` | `(item, { i }) => Promise<U>` | required | Processing function per item. Receives the item and its index. |
356
+ | `opts.maxAttempts` | `number` | `5` | Max retries per item. |
357
+ | `opts.concurrency` | `number` | `1` | Max parallel item executions. |
358
+
359
+ ### Behavior
360
+
361
+ - Creates a parent step named `name`, with child steps named `{name}-i0`, `{name}-i1`, etc.
362
+ - Each item is processed in its own step with independent retry logic.
363
+ - Results are returned in the same order as input items.
364
+ - If execution time runs low mid-iteration, the workflow pauses and resumes on next invocation.
365
+
366
+ ### Examples
367
+
368
+ From the knowledge indexing workflow — syncing data sources in parallel:
369
+
370
+ ```typescript
371
+ const workflows = await step.map(
372
+ 'index-sources',
373
+ kb.sources,
374
+ async (source) => {
375
+ const workflowId = await step('create-sync-workflow', async () =>
376
+ await source.syncWorkflow
377
+ .getOrCreate({ key: `${kbName}:${source.id}`, input: { /* ... */ } })
378
+ .then((x) => x.id)
379
+ )
380
+ return await step.waitForWorkflow(source.id, workflowId)
381
+ .then((x) => x.output)
382
+ },
383
+ { concurrency: 10, maxAttempts: 1 }
384
+ )
385
+ ```
386
+
387
+ From the directory source — deleting files with concurrency:
388
+
389
+ ```typescript
390
+ const deleted = await step.map(
391
+ 'deleting removed files',
392
+ toRemove,
393
+ (f) => client.deleteFile({ id: f.id }).then(() => ({
394
+ file: f.id,
395
+ name: f.key,
396
+ hash: f.metadata?.hash || '',
397
+ size: f.size ?? -1,
398
+ })),
399
+ { concurrency: 5 }
400
+ )
401
+ ```
402
+
403
+ ---
404
+
405
+ ## step.forEach()
406
+
407
+ Process an array of items in parallel without collecting results. Identical to `step.map()` but returns `void`.
408
+
409
+ ### Signature
410
+
411
+ ```typescript
412
+ step.forEach<T>(
413
+ name: string,
414
+ items: T[],
415
+ run: (input: T, opts: { i: number }) => Promise<void>,
416
+ opts?: { maxAttempts?: number; concurrency?: number }
417
+ ): Promise<void>
418
+ ```
419
+
420
+ Use `step.forEach()` for side-effect-only operations (sending notifications, updating records). If you need the results array, use `step.map()`.
421
+
422
+ ### Examples
423
+
424
+ A support workflow — reviewing tickets with side effects:
425
+
426
+ ```typescript
427
+ await step.forEach(
428
+ 'review-tickets',
429
+ tickets,
430
+ async (ticket, { i }) => {
431
+ if (ticket.priority === 'urgent' && ticket.status === 'open') {
432
+ await TicketsTable.updateRows({
433
+ rows: [{ id: ticket.id, status: 'in-progress' }],
434
+ })
435
+ }
436
+ },
437
+ { concurrency: 1 }
438
+ )
439
+ ```
440
+
441
+ ---
442
+
443
+ ## step.batch()
444
+
445
+ Process an array of items in sequential batches. Items are grouped into fixed-size batches and each batch is processed as a single step.
446
+
447
+ ### Signature
448
+
449
+ ```typescript
450
+ step.batch<T>(
451
+ name: string,
452
+ items: T[],
453
+ run: (batch: T[], opts: { i: number }) => Promise<void>,
454
+ opts?: { batchSize?: number; maxAttempts?: number }
455
+ ): Promise<void>
456
+ ```
457
+
458
+ ### Parameters
459
+
460
+ | Parameter | Type | Default | Description |
461
+ |-----------|------|---------|-------------|
462
+ | `name` | `string` | required | Name for the batch operation. |
463
+ | `items` | `T[]` | required | Full array of items. |
464
+ | `run` | `(batch, { i }) => Promise<void>` | required | Function receiving a batch slice and the starting index. |
465
+ | `opts.batchSize` | `number` | `20` | Number of items per batch. |
466
+ | `opts.maxAttempts` | `number` | `5` | Max retries per batch. |
467
+
468
+ ### Behavior
469
+
470
+ - Creates a parent step named `name`, with child steps named `{name}-b1`, `{name}-b2`, etc. (1-indexed).
471
+ - Batches are processed sequentially (no concurrency between batches).
472
+ - Each batch gets independent retry logic.
473
+
474
+ ### Examples
475
+
476
+ ```typescript
477
+ // Bulk insert records in batches of 100
478
+ await step.batch(
479
+ 'bulk-insert',
480
+ records,
481
+ async (batch) => {
482
+ await database.bulkInsert(batch)
483
+ },
484
+ { batchSize: 100 }
485
+ )
486
+ ```
487
+
488
+ ### When to Use batch vs map/forEach
489
+
490
+ | Use | When |
491
+ |-----|------|
492
+ | `step.batch()` | External API accepts arrays (bulk insert, batch API) |
493
+ | `step.map()` | Need individual results per item, want concurrency |
494
+ | `step.forEach()` | Side effects per item, want concurrency |
495
+
496
+ ---
497
+
498
+ ## step.executeWorkflow()
499
+
500
+ Start a child workflow and wait for it to complete, returning its output. Convenience wrapper around `workflow.getOrCreate()` + `step.waitForWorkflow()`.
501
+
502
+ ### Signature
503
+
504
+ ```typescript
505
+ step.executeWorkflow<TName, TInput, TOutput>(
506
+ name: string,
507
+ workflow: BaseWorkflow<TName, TInput, TOutput>,
508
+ input?: z.infer<TInput>
509
+ ): Promise<z.infer<TOutput>>
510
+ ```
511
+
512
+ ### Behavior
513
+
514
+ 1. Generates a unique key (via `ulid()`) to ensure idempotent child workflow creation.
515
+ 2. Calls `workflow.getOrCreate()` with that key.
516
+ 3. Waits for the child workflow to complete via `step.waitForWorkflow()`.
517
+ 4. Returns the child workflow's output.
518
+
519
+ Internally creates three sub-steps: `{name}-key`, `{name}-start`, `{name}-wait`.
520
+
521
+ ### Examples
522
+
523
+ ```typescript
524
+ const result = await step.executeWorkflow(
525
+ 'run-analysis',
526
+ AnalysisWorkflow,
527
+ { data: input.rawData }
528
+ )
529
+ // result is typed as AnalysisWorkflow's output
530
+ ```
531
+
532
+ ---
533
+
534
+ ## step.waitForWorkflow()
535
+
536
+ Wait for another workflow (by ID) to complete before continuing.
537
+
538
+ ### Signature
539
+
540
+ ```typescript
541
+ step.waitForWorkflow(
542
+ name: string,
543
+ workflowId: string
544
+ ): Promise<Workflow>
545
+ ```
546
+
547
+ ### Behavior
548
+
549
+ - Polls the target workflow's status.
550
+ - If the target is finished (`completed`, `failed`, `cancelled`, `timedout`), returns the workflow object immediately.
551
+ - If still running, sets the current workflow to `listening` mode and restarts the step on next invocation.
552
+ - Throws if you try to wait for the same workflow (deadlock prevention).
553
+
554
+ ### Examples
555
+
556
+ ```typescript
557
+ // Start a child workflow manually, then wait
558
+ const child = await ChildWorkflow.getOrCreate({
559
+ key: `child-${input.id}`,
560
+ input: { data: input.data },
561
+ })
562
+
563
+ const finished = await step.waitForWorkflow('wait-for-child', child.id)
564
+
565
+ if (finished.status === 'completed') {
566
+ const output = finished.output as ChildOutput
567
+ // ...
568
+ }
569
+ ```
570
+
571
+ ---
572
+
573
+ ## step.progress()
574
+
575
+ Record a named checkpoint without performing any action. Useful for tracking workflow execution stages.
576
+
577
+ ### Signature
578
+
579
+ ```typescript
580
+ step.progress(name: string): Promise<void>
581
+ ```
582
+
583
+ ### Behavior
584
+
585
+ - Creates a step with no output and `maxAttempts: 1`.
586
+ - The step is visible in the workflow's execution state for audit and debugging.
587
+
588
+ ### Examples
589
+
590
+ ```typescript
591
+ await step.progress('started-processing')
592
+ // ... do work ...
593
+ await step.progress('finished-processing')
594
+ ```
595
+
596
+ ---
597
+
598
+ ## step.fail()
599
+
600
+ Mark the workflow as failed with a reason and stop execution immediately.
601
+
602
+ ### Signature
603
+
604
+ ```typescript
605
+ step.fail(reason: string): Promise<void>
606
+ ```
607
+
608
+ ### Behavior
609
+
610
+ - Sets the workflow's internal failed flag with the given reason.
611
+ - Creates a step named after the reason string (with `maxAttempts: 1`).
612
+ - Execution halts — code after `step.fail()` is never reached.
613
+ - The workflow status becomes `failed`.
614
+
615
+ ### Examples
616
+
617
+ ```typescript
618
+ if (!user.isVerified) {
619
+ await step.fail('User verification required')
620
+ // never reached
621
+ }
622
+ ```
623
+
624
+ > `step.fail()` vs `workflow.fail()`: Both mark the workflow as failed. `step.fail()` is available on the step object. `workflow.fail()` is available on the workflow instance parameter. They achieve the same result. Prefer whichever is already in scope.
625
+
626
+ ---
627
+
628
+ ## step.abort()
629
+
630
+ Immediately stop workflow execution without marking it as failed.
631
+
632
+ ### Signature
633
+
634
+ ```typescript
635
+ step.abort(): void // note: synchronous, throws immediately
636
+ ```
637
+
638
+ ### Behavior
639
+
640
+ - Sets the workflow's internal aborted flag.
641
+ - Throws a step signal to halt execution.
642
+ - The workflow remains in its current status and can be resumed later.
643
+ - This is NOT `step.fail()` — it does not mark the workflow as failed.
644
+
645
+ ### Examples
646
+
647
+ ```typescript
648
+ if (shouldPauseForLater) {
649
+ step.abort()
650
+ // never reached
651
+ }
652
+ ```
653
+
654
+ ---
655
+
656
+ ## Step Execution Model
657
+
658
+ ### Caching and Idempotency
659
+
660
+ Every step is identified by its name. When a workflow resumes:
661
+ 1. Steps that already have a `finishedAt` timestamp return their cached output immediately.
662
+ 2. Steps that failed with `maxAttemptsReached` re-throw their stored error.
663
+ 3. Only steps that haven't completed yet actually execute their `run` function.
664
+
665
+ This means step functions can contain non-idempotent operations (API calls, database writes) and still be safe across workflow restarts — each operation runs exactly once.
666
+
667
+ ### Retry Logic
668
+
669
+ Failed steps retry with exponential backoff: `min(100ms * e^attempt, 5000ms)`. After `maxAttempts` failures, the step is permanently marked as failed with the error stored in state. The error then propagates to the parent step or workflow.
670
+
671
+ ### Execution Time Management
672
+
673
+ The runtime monitors remaining execution time. If less than 10 seconds remain:
674
+ - New steps will not start.
675
+ - The workflow enters a brief sleep and then aborts.
676
+ - On next invocation, execution resumes from the last incomplete step.
677
+
678
+ This prevents cascading timeout failures and ensures clean state persistence.
679
+
680
+ ---
681
+
682
+ ## Patterns
683
+
684
+ ### Multi-Turn Data Collection (Request/Provide Cycle)
685
+
686
+ The most common multi-step pattern: workflow requests data, conversation collects it from the user, provides it back, and the workflow resumes.
687
+
688
+ ```typescript
689
+ // Workflow side
690
+ export const OnboardingWorkflow = new Workflow({
691
+ name: 'onboarding',
692
+ requests: {
693
+ user_input: z.string(),
694
+ selection: z.enum(['optionA', 'optionB']),
695
+ },
696
+ handler: async ({ step }) => {
697
+ const name = await step.request('user_input', 'What is your name?', 'collect-name')
698
+ const choice = await step.request('selection', 'Pick an option:')
699
+ return { name, choice }
700
+ },
701
+ })
702
+
703
+ // Conversation side
704
+ export const Chat = new Conversation({
705
+ channel: 'chat.channel',
706
+ async handler({ type, request, conversation, message }) {
707
+ if (type === 'workflow_request') {
708
+ await conversation.send({ type: 'text', payload: { text: request.prompt } })
709
+ // ... collect user's response ...
710
+ await request.workflow.provide(request.name, userResponse, request.step)
711
+ }
712
+ },
713
+ })
714
+ ```
715
+
716
+ ### Fan-Out with Progress Notifications
717
+
718
+ Process items in parallel while streaming progress updates to the conversation:
719
+
720
+ ```typescript
721
+ export const BulkProcessWorkflow = new Workflow({
722
+ name: 'bulkProcess',
723
+ notifications: {
724
+ status: z.object({ processed: z.number(), total: z.number() }),
725
+ },
726
+ handler: async ({ input, step }) => {
727
+ const items = await step('fetch-items', async () => { /* ... */ })
728
+
729
+ await step.forEach('process', items, async (item, { i }) => {
730
+ await processItem(item)
731
+ await step.notify('status', {
732
+ processed: i + 1,
733
+ total: items.length,
734
+ }, `status-${i}`)
735
+ }, { concurrency: 5 })
736
+
737
+ return { total: items.length }
738
+ },
739
+ })
740
+ ```
741
+
742
+ ### Child Workflow Orchestration
743
+
744
+ Start multiple child workflows and wait for all of them:
745
+
746
+ ```typescript
747
+ handler: async ({ input, step }) => {
748
+ const results = await step.map(
749
+ 'run-children',
750
+ input.tasks,
751
+ async (task) => {
752
+ return await step.executeWorkflow(
753
+ `child-${task.id}`,
754
+ ChildWorkflow,
755
+ { data: task.data }
756
+ )
757
+ },
758
+ { concurrency: 3 }
759
+ )
760
+ return { results }
761
+ }
762
+ ```
763
+
764
+ ---
765
+
766
+ ## Pitfalls
767
+
768
+ ### Step names must be unique and stable
769
+
770
+ Every step in a workflow must have a unique name that does not change between executions. Dynamic step names break resume behavior.
771
+
772
+ ```typescript
773
+ // WRONG — dynamic names break on resume if items change
774
+ for (const item of items) {
775
+ await step(`process-${item.id}`, async () => { /* ... */ })
776
+ }
777
+
778
+ // CORRECT — use step.map() or step.forEach() for iteration
779
+ await step.map('process-items', items, async (item) => { /* ... */ })
780
+ ```
781
+
782
+ ### step.request() needs custom stepName for repeated types
783
+
784
+ If you call `step.request()` with the same request type more than once, you MUST provide a unique `stepName`. Otherwise the provide call cannot disambiguate which step to fill.
785
+
786
+ ```typescript
787
+ // WRONG — both use 'text_input' as implicit step name
788
+ const name = await step.request('text_input', 'Your name?')
789
+ const email = await step.request('text_input', 'Your email?') // conflicts!
790
+
791
+ // CORRECT — explicit step names
792
+ const name = await step.request('text_input', 'Your name?', 'collect-name')
793
+ const email = await step.request('text_input', 'Your email?', 'collect-email')
794
+ ```
795
+
796
+ ### step.notify() inside loops needs unique step names
797
+
798
+ Since `step.notify()` is idempotent per step name, calling it in a loop with the same step name only sends the first notification.
799
+
800
+ ```typescript
801
+ // WRONG — only sends one notification
802
+ for (const item of items) {
803
+ await step.notify('progress', { item: item.name }) // same step name each time
804
+ }
805
+
806
+ // CORRECT — unique step name per iteration
807
+ await step.forEach('process', items, async (item, { i }) => {
808
+ await step.notify('progress', { item: item.name }, `progress-${i}`)
809
+ })
810
+ ```
811
+
812
+ ### step.abort() is synchronous
813
+
814
+ `step.abort()` throws immediately — it does not return a promise. Any code after it is unreachable.
815
+
816
+ ```typescript
817
+ step.abort()
818
+ console.log('never printed') // unreachable
819
+ ```
820
+
821
+ ### step.fail() reason string becomes the step name
822
+
823
+ The `reason` argument to `step.fail()` is used as the step name. Keep it descriptive but avoid characters that would be problematic as identifiers.
824
+
825
+ ### Avoid heavy computation outside steps
826
+
827
+ All work inside a workflow handler should be wrapped in steps. Code between steps runs on every resume but produces no cached state, wasting execution time.
828
+
829
+ ```typescript
830
+ // WRONG — this runs on every resume
831
+ const processed = heavyComputation(input.data)
832
+ await step('use-result', async () => { /* ... */ })
833
+
834
+ // CORRECT — wrap in a step
835
+ const processed = await step('compute', async () => {
836
+ return heavyComputation(input.data)
837
+ })
838
+ ```
839
+
840
+ ### step.sleep() threshold: 10 seconds
841
+
842
+ Sleeps under 10 seconds use in-memory `setTimeout` (workflow stays active). Sleeps of 10 seconds or more enter `listening` mode (workflow pauses and resumes via scheduled event). Plan accordingly — a 9-second sleep ties up the execution context, while a 10-second sleep frees it.
843
+
844
+ ---
845
+
846
+ ## See Also
847
+
848
+ - [Workflows](./workflows.md) — Workflow basics, input/output schemas, request/notification definitions
849
+ - [Conversations](./conversations.md) — Conversation-side handling of workflow requests and notifications
850
+ - [Autonomous Execution](./autonomous-execution.md) — Using execute() inside workflow handlers (worker mode)
851
+ - [Conversation Lifecycle](./conversation-lifecycle.md) — Nudge suppression during active workflow steps