@hexabot-ai/agentic 3.0.1-alpha.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 (176) hide show
  1. package/LICENSE.md +120 -0
  2. package/README.md +231 -0
  3. package/dist/cjs/action/abstract-action.js +141 -0
  4. package/dist/cjs/action/action.js +47 -0
  5. package/dist/cjs/action/action.types.js +7 -0
  6. package/dist/cjs/bindings/base-binding.js +278 -0
  7. package/dist/cjs/context.js +79 -0
  8. package/dist/cjs/dsl.types.js +318 -0
  9. package/dist/cjs/index.js +52 -0
  10. package/dist/cjs/runner-runtime-control.js +343 -0
  11. package/dist/cjs/step-executors/conditional-executor.js +63 -0
  12. package/dist/cjs/step-executors/loop-executor.js +187 -0
  13. package/dist/cjs/step-executors/parallel-executor.js +51 -0
  14. package/dist/cjs/step-executors/skip-helpers.js +29 -0
  15. package/dist/cjs/step-executors/task-executor.js +155 -0
  16. package/dist/cjs/step-executors/types.js +7 -0
  17. package/dist/cjs/suspension-rebuilder.js +301 -0
  18. package/dist/cjs/utils/deferred.js +17 -0
  19. package/dist/cjs/utils/naming.js +48 -0
  20. package/dist/cjs/utils/timeout.js +42 -0
  21. package/dist/cjs/utils/workflow-definition.js +102 -0
  22. package/dist/cjs/workflow-compiler.js +212 -0
  23. package/dist/cjs/workflow-event-emitter.js +40 -0
  24. package/dist/cjs/workflow-runner.js +448 -0
  25. package/dist/cjs/workflow-types.js +7 -0
  26. package/dist/cjs/workflow-values.js +99 -0
  27. package/dist/cjs/workflow.js +243 -0
  28. package/dist/esm/action/abstract-action.js +137 -0
  29. package/dist/esm/action/action.js +44 -0
  30. package/dist/esm/action/action.types.js +6 -0
  31. package/dist/esm/bindings/base-binding.js +273 -0
  32. package/dist/esm/context.js +75 -0
  33. package/dist/esm/dsl.types.js +309 -0
  34. package/dist/esm/index.js +16 -0
  35. package/dist/esm/runner-runtime-control.js +338 -0
  36. package/dist/esm/step-executors/conditional-executor.js +60 -0
  37. package/dist/esm/step-executors/loop-executor.js +182 -0
  38. package/dist/esm/step-executors/parallel-executor.js +48 -0
  39. package/dist/esm/step-executors/skip-helpers.js +25 -0
  40. package/dist/esm/step-executors/task-executor.js +152 -0
  41. package/dist/esm/step-executors/types.js +6 -0
  42. package/dist/esm/suspension-rebuilder.js +296 -0
  43. package/dist/esm/utils/deferred.js +14 -0
  44. package/dist/esm/utils/naming.js +42 -0
  45. package/dist/esm/utils/timeout.js +37 -0
  46. package/dist/esm/utils/workflow-definition.js +98 -0
  47. package/dist/esm/workflow-compiler.js +208 -0
  48. package/dist/esm/workflow-event-emitter.js +36 -0
  49. package/dist/esm/workflow-runner.js +444 -0
  50. package/dist/esm/workflow-types.js +6 -0
  51. package/dist/esm/workflow-values.js +89 -0
  52. package/dist/esm/workflow.js +236 -0
  53. package/dist/types/action/abstract-action.d.ts +54 -0
  54. package/dist/types/action/abstract-action.d.ts.map +1 -0
  55. package/dist/types/action/action.d.ts +23 -0
  56. package/dist/types/action/action.d.ts.map +1 -0
  57. package/dist/types/action/action.types.d.ts +55 -0
  58. package/dist/types/action/action.types.d.ts.map +1 -0
  59. package/dist/types/bindings/base-binding.d.ts +52 -0
  60. package/dist/types/bindings/base-binding.d.ts.map +1 -0
  61. package/dist/types/context.d.ts +105 -0
  62. package/dist/types/context.d.ts.map +1 -0
  63. package/dist/types/dsl.types.d.ts +220 -0
  64. package/dist/types/dsl.types.d.ts.map +1 -0
  65. package/dist/types/index.d.ts +18 -0
  66. package/dist/types/index.d.ts.map +1 -0
  67. package/dist/types/runner-runtime-control.d.ts +64 -0
  68. package/dist/types/runner-runtime-control.d.ts.map +1 -0
  69. package/dist/types/step-executors/conditional-executor.d.ts +13 -0
  70. package/dist/types/step-executors/conditional-executor.d.ts.map +1 -0
  71. package/dist/types/step-executors/loop-executor.d.ts +33 -0
  72. package/dist/types/step-executors/loop-executor.d.ts.map +1 -0
  73. package/dist/types/step-executors/parallel-executor.d.ts +14 -0
  74. package/dist/types/step-executors/parallel-executor.d.ts.map +1 -0
  75. package/dist/types/step-executors/skip-helpers.d.ts +12 -0
  76. package/dist/types/step-executors/skip-helpers.d.ts.map +1 -0
  77. package/dist/types/step-executors/task-executor.d.ts +13 -0
  78. package/dist/types/step-executors/task-executor.d.ts.map +1 -0
  79. package/dist/types/step-executors/types.d.ts +24 -0
  80. package/dist/types/step-executors/types.d.ts.map +1 -0
  81. package/dist/types/suspension-rebuilder.d.ts +67 -0
  82. package/dist/types/suspension-rebuilder.d.ts.map +1 -0
  83. package/dist/types/utils/deferred.d.ts +7 -0
  84. package/dist/types/utils/deferred.d.ts.map +1 -0
  85. package/dist/types/utils/naming.d.ts +24 -0
  86. package/dist/types/utils/naming.d.ts.map +1 -0
  87. package/dist/types/utils/timeout.d.ts +17 -0
  88. package/dist/types/utils/timeout.d.ts.map +1 -0
  89. package/dist/types/utils/workflow-definition.d.ts +3 -0
  90. package/dist/types/utils/workflow-definition.d.ts.map +1 -0
  91. package/dist/types/workflow-compiler.d.ts +12 -0
  92. package/dist/types/workflow-compiler.d.ts.map +1 -0
  93. package/dist/types/workflow-event-emitter.d.ts +70 -0
  94. package/dist/types/workflow-event-emitter.d.ts.map +1 -0
  95. package/dist/types/workflow-runner.d.ts +179 -0
  96. package/dist/types/workflow-runner.d.ts.map +1 -0
  97. package/dist/types/workflow-types.d.ts +195 -0
  98. package/dist/types/workflow-types.d.ts.map +1 -0
  99. package/dist/types/workflow-values.d.ts +34 -0
  100. package/dist/types/workflow-values.d.ts.map +1 -0
  101. package/dist/types/workflow.d.ts +75 -0
  102. package/dist/types/workflow.d.ts.map +1 -0
  103. package/examples/defs-bindings-agent/actions/ai-agent.ts +110 -0
  104. package/examples/defs-bindings-agent/actions/caculate-score.ts +49 -0
  105. package/examples/defs-bindings-agent/actions/index.ts +15 -0
  106. package/examples/defs-bindings-agent/bindings.ts +20 -0
  107. package/examples/defs-bindings-agent/context.ts +32 -0
  108. package/examples/defs-bindings-agent/workflow.ts +67 -0
  109. package/examples/defs-bindings-agent/workflow.yml +28 -0
  110. package/examples/full/actions/await-user-input.ts +46 -0
  111. package/examples/full/actions/call-llm.ts +133 -0
  112. package/examples/full/actions/create-ticket.ts +46 -0
  113. package/examples/full/actions/decision-router.ts +83 -0
  114. package/examples/full/actions/get-calendar-events.ts +55 -0
  115. package/examples/full/actions/get-user-profile.ts +72 -0
  116. package/examples/full/actions/index.ts +33 -0
  117. package/examples/full/actions/query-memory.ts +45 -0
  118. package/examples/full/actions/search-web.ts +53 -0
  119. package/examples/full/actions/send-email.ts +50 -0
  120. package/examples/full/context.ts +41 -0
  121. package/examples/full/workflow.ts +75 -0
  122. package/examples/full/workflow.yml +359 -0
  123. package/examples/loop/actions/await-reply.ts +47 -0
  124. package/examples/loop/actions/index.ts +19 -0
  125. package/examples/loop/actions/send-text-message.ts +40 -0
  126. package/examples/loop/context.ts +32 -0
  127. package/examples/loop/workflow.ts +66 -0
  128. package/examples/loop/workflow.yml +66 -0
  129. package/examples/suspend-resume/actions/format-reply.ts +43 -0
  130. package/examples/suspend-resume/actions/index.ts +13 -0
  131. package/examples/suspend-resume/actions/wait-for-user.ts +51 -0
  132. package/examples/suspend-resume/context.ts +32 -0
  133. package/examples/suspend-resume/workflow.ts +79 -0
  134. package/examples/suspend-resume/workflow.yml +29 -0
  135. package/package.json +60 -0
  136. package/src/__tests__/parser.test.ts +95 -0
  137. package/src/__tests__/suspension-rebuilder.test.ts +361 -0
  138. package/src/__tests__/test-helpers.ts +36 -0
  139. package/src/__tests__/validation.test.ts +526 -0
  140. package/src/__tests__/workflow-compiler.test.ts +715 -0
  141. package/src/__tests__/workflow-definition-path.test.ts +381 -0
  142. package/src/__tests__/workflow-event-emitter.test.ts +50 -0
  143. package/src/__tests__/workflow-runner.test.ts +1397 -0
  144. package/src/__tests__/workflow-values.test.ts +132 -0
  145. package/src/__tests__/workflow.test.ts +320 -0
  146. package/src/action/__tests__/abstract-action-timing.test.ts +183 -0
  147. package/src/action/__tests__/action.test.ts +473 -0
  148. package/src/action/abstract-action.ts +215 -0
  149. package/src/action/action.ts +83 -0
  150. package/src/action/action.types.ts +93 -0
  151. package/src/bindings/base-binding.ts +508 -0
  152. package/src/context.ts +179 -0
  153. package/src/dsl.types.ts +473 -0
  154. package/src/index.ts +114 -0
  155. package/src/runner-runtime-control.ts +544 -0
  156. package/src/step-executors/conditional-executor.test.ts +192 -0
  157. package/src/step-executors/conditional-executor.ts +83 -0
  158. package/src/step-executors/loop-executor.test.ts +303 -0
  159. package/src/step-executors/loop-executor.ts +299 -0
  160. package/src/step-executors/parallel-executor.test.ts +182 -0
  161. package/src/step-executors/parallel-executor.ts +77 -0
  162. package/src/step-executors/skip-helpers.ts +68 -0
  163. package/src/step-executors/task-executor.test.ts +257 -0
  164. package/src/step-executors/task-executor.ts +248 -0
  165. package/src/step-executors/types.ts +70 -0
  166. package/src/suspension-rebuilder.ts +504 -0
  167. package/src/utils/deferred.ts +23 -0
  168. package/src/utils/naming.ts +55 -0
  169. package/src/utils/timeout.ts +48 -0
  170. package/src/utils/workflow-definition.ts +173 -0
  171. package/src/workflow-compiler.ts +317 -0
  172. package/src/workflow-event-emitter.ts +86 -0
  173. package/src/workflow-runner.ts +603 -0
  174. package/src/workflow-types.ts +207 -0
  175. package/src/workflow-values.ts +159 -0
  176. package/src/workflow.ts +387 -0
@@ -0,0 +1,359 @@
1
+ ### Example DSL: AI/automation workflow (annotated)
2
+ ### This single file is reference documentation for the YAML language used to orchestrate multi-step agents.
3
+ ### Convention: strings beginning with "=" are JSONata expressions with scope {context, input, output, iteration, accumulator, result}; all other strings are treated as literals.
4
+ ---
5
+ # End-to-end assistant that classifies a user request, collects signals, performs a human-in-the-loop
6
+ # clarification (pausing the run until the user replies), branches by intent/priority, and delivers an email.
7
+ # Declares external inputs the caller must provide when starting a run.
8
+ inputs:
9
+ schema:
10
+ query:
11
+ type: string
12
+ description: "Natural language request or question from the user."
13
+ user_email:
14
+ type: string
15
+ description: "Where to send the response."
16
+ priority:
17
+ type: string
18
+ enum: ["low", "normal", "high"]
19
+ description: "Used for routing/escalation decisions."
20
+ attachments:
21
+ type: array
22
+ items:
23
+ type: string
24
+ description: "Optional file ids that the user attached with the request."
25
+
26
+ # Context is provided by the runtime (read-only) and accessed with $context.* inside tasks.
27
+ # Include any long-term state here (thread ids, transcripts, briefs) instead of a separate memory block.
28
+ context:
29
+ user_id: "=$context.user_id" # e.g., authenticated account id
30
+ account_tier: "=$context.account_tier" # e.g., free/pro/enterprise for policy decisions
31
+ locale: "=$context.locale" # e.g., en-US, influences prompts
32
+ timezone: "=$context.timezone" # useful for time-sensitive operations
33
+ channel: "=$context.channel" # e.g., web, email, slack; used when messaging the user
34
+ thread_id: "=$context.thread_id" # thread/conversation id for continuity
35
+ full_transcript: "=$context.full_transcript"
36
+ last_summary: "=$context.last_summary"
37
+ support_playbook: "=$context.support_playbook"
38
+ product_brief: "=$context.product_brief"
39
+
40
+ # Global defaults inherited by every task (overridable per task).
41
+ defaults:
42
+ settings:
43
+ timeout_ms: 10000
44
+ retries:
45
+ enabled: true
46
+ max_attempts: 2
47
+ backoff_ms: 400
48
+ max_delay_ms: 10000
49
+ jitter: 0
50
+ multiplier: 1.5
51
+
52
+ # Task catalog: each task has a unique name, description, action, inputs, settings.
53
+ defs:
54
+ understand_request:
55
+ kind: task
56
+ description: "LLM categorizes the incoming query, extracts a short summary, and lists missing must-have fields."
57
+ action: call_llm
58
+ inputs:
59
+ system_prompt: |
60
+ You are an intent classifier and requirement extractor.
61
+ Return JSON with keys: intent, confidence, summary, missing_fields (array), needs_human (boolean).
62
+ Allowed intents: support, sales, research.
63
+ user_query: "=$input.query"
64
+ attachments: "=$input.attachments"
65
+ last_summary: "=$context.last_summary"
66
+ user_tier: "=$context.account_tier"
67
+ settings:
68
+ model: gpt-4o
69
+ temperature: 0.2
70
+ timeout_ms: 6000
71
+ retries:
72
+ enabled: true
73
+ max_attempts: 1
74
+ backoff_ms: 400
75
+ max_delay_ms: 3000
76
+ jitter: 0.05
77
+ multiplier: 1.2
78
+
79
+ fetch_user_profile:
80
+ kind: task
81
+ description: "Look up CRM profile and recent cases for the authenticated user."
82
+ action: get_user_profile
83
+ inputs:
84
+ user_id: "=$context.user_id"
85
+ include_history: true
86
+
87
+ # Three distinct actions will run in parallel; their outputs will later be merged.
88
+ search_recent_news:
89
+ kind: task
90
+ description: "Web search for fresh information about the user's company or topic."
91
+ action: search_web
92
+ inputs:
93
+ query: "='updates for ' & $output.fetch_user_profile.profile.company"
94
+ limit: 3
95
+ settings:
96
+ timeout_ms: 7000
97
+
98
+ retrieve_memory_thread:
99
+ kind: task
100
+ description: "Recall summarized prior conversation for continuity."
101
+ action: query_memory
102
+ inputs:
103
+ thread_id: "=$context.thread_id" # read thread id from long-term context state
104
+ user_id: "=$context.user_id"
105
+ settings:
106
+ timeout_ms: 3000
107
+
108
+ fetch_calendar_events:
109
+ kind: task
110
+ description: "Pull the user's upcoming meetings for scheduling-aware replies."
111
+ action: get_calendar_events
112
+ inputs:
113
+ user_email: "=$input.user_email"
114
+ range: "next 7 days"
115
+ settings:
116
+ timeout_ms: 5000
117
+
118
+ # Human-in-the-loop primitive: ask the user a clarifying question and pause the run until they respond.
119
+ ask_for_missing_detail:
120
+ kind: task
121
+ description: >
122
+ Send a clarifying question to the end user when the request lacks required fields.
123
+ The engine MUST pause the workflow here and resume once a reply is received or a timeout occurs.
124
+ action: await_user_input # generic action name that does not imply a specific channel
125
+ inputs:
126
+ prompt: | # question shown to the user in the same channel that triggered the run
127
+ = 'To help with your ' & $output.understand_request.intent & ' request, please provide: ' &
128
+ $join($output.understand_request.missing_fields, ', ')
129
+ allow_attachments: true
130
+ settings:
131
+ await_user: true # signals the runtime to checkpoint and wait
132
+ timeout_ms: 86400000 # 24h; engines may choose a default if omitted
133
+
134
+ synthesize_research:
135
+ kind: task
136
+ description: "LLM that combines all signals (including any user clarification) into a concise brief."
137
+ action: call_llm
138
+ inputs:
139
+ intent: "=$output.understand_request.intent"
140
+ profile: "=$output.fetch_user_profile.profile"
141
+ news_snippets: "=$output.search_recent_news.snippets" # from a parallel task
142
+ memory_summary: "=$output.retrieve_memory_thread.summary"
143
+ meetings: "=$output.fetch_calendar_events.events"
144
+ user_clarification: "=$exists($output.ask_for_missing_detail.text) ? $output.ask_for_missing_detail.text : ''"
145
+ attachments: "=$exists($output.ask_for_missing_detail.attachments) ? $output.ask_for_missing_detail.attachments : $input.attachments"
146
+ settings:
147
+ model: gpt-4o
148
+ temperature: 0.35
149
+
150
+ route_by_intent:
151
+ kind: task
152
+ description: "Decide which downstream path to take using a branching table."
153
+ action: decision_router
154
+ inputs:
155
+ intent: "=$output.understand_request.intent"
156
+ priority: "=$input.priority"
157
+ confidence: "=$output.understand_request.confidence"
158
+ needs_human: "=$output.understand_request.needs_human"
159
+ settings:
160
+ # Multi-branch logic evaluated top-to-bottom; first match wins.
161
+ branches:
162
+ - when: "=$output.understand_request.needs_human = true"
163
+ next: escalate_to_human
164
+ - when: "=$output.understand_request.intent = 'support' and $output.understand_request.confidence > 0.6"
165
+ next: draft_support_reply
166
+ - when: "=$output.understand_request.intent = 'sales'"
167
+ next: craft_sales_pitch
168
+ - when: "=$input.priority = 'high'" # demonstrates condition using workflow input
169
+ next: escalate_to_human
170
+ default_next: send_generic_summary
171
+
172
+ draft_support_reply:
173
+ kind: task
174
+ description: "Generate troubleshooting steps tailored to the user."
175
+ action: call_llm
176
+ inputs:
177
+ profile: "=$output.fetch_user_profile.profile"
178
+ question: "=$input.query"
179
+ clarification: "=$exists($output.ask_for_missing_detail.text) ? $output.ask_for_missing_detail.text : ''"
180
+ troubleshooting_guide: "=$context.support_playbook"
181
+ signals: "=$output.synthesize_research.synthesis"
182
+ settings:
183
+ model: gpt-4o-mini
184
+ temperature: 0.3
185
+
186
+ craft_sales_pitch:
187
+ kind: task
188
+ description: "Write a sales email using retrieved company insights."
189
+ action: call_llm
190
+ inputs:
191
+ product_brief: "=$context.product_brief"
192
+ company_news: "=$output.search_recent_news.snippets"
193
+ meetings: "=$output.fetch_calendar_events.events"
194
+ persona: "=$output.fetch_user_profile.profile.persona"
195
+ query: "=$input.query"
196
+ settings:
197
+ model: gpt-4o
198
+ temperature: 0.55
199
+
200
+ escalate_to_human:
201
+ kind: task
202
+ description: "Open or update a human-agent ticket with full context."
203
+ action: create_ticket
204
+ inputs:
205
+ user_id: "=$context.user_id"
206
+ transcript: "=$context.full_transcript"
207
+ summary: "=$output.synthesize_research.synthesis"
208
+ clarification: "=$exists($output.ask_for_missing_detail.text) ? $output.ask_for_missing_detail.text : ''"
209
+
210
+ send_generic_summary:
211
+ kind: task
212
+ description: "Fallback response when no specialized branch applies."
213
+ action: call_llm
214
+ inputs:
215
+ summary: "=$output.synthesize_research.synthesis"
216
+ intent: "=$output.understand_request.intent"
217
+ question: "=$input.query"
218
+ settings:
219
+ model: gpt-4o-mini
220
+
221
+ send_email_response:
222
+ kind: task
223
+ description: "Deliver the chosen response via email."
224
+ action: send_email
225
+ inputs:
226
+ to: "=$input.user_email"
227
+ subject: "='Response for ' & $output.understand_request.intent & ' request'"
228
+ body: >
229
+ = (
230
+ $route := $output.route_by_intent.route;
231
+ $route = 'draft_support_reply' ? $output.draft_support_reply.message :
232
+ $route = 'craft_sales_pitch' ? $output.craft_sales_pitch.email :
233
+ $route = 'escalate_to_human' ? ('Ticket created: ' & $output.escalate_to_human.ticket_id) :
234
+ $output.send_generic_summary.message
235
+ )
236
+ # The JSONata expression above chooses the body from prior outputs without separate templating.
237
+ settings:
238
+ track_opens: true
239
+
240
+ # Loop-specific tasks used by the for-each demo in the flow below.
241
+ draft_stakeholder_update:
242
+ kind: task
243
+ description: "LLM crafts a short FYI note for each stakeholder iterated in the loop."
244
+ action: call_llm
245
+ inputs:
246
+ stakeholder: "=$iteration.item" # $iteration.* is provided inside loop bodies
247
+ intent: "=$output.understand_request.intent"
248
+ research_brief: "=$output.synthesize_research.synthesis"
249
+ question: "=$input.query"
250
+ settings:
251
+ model: gpt-4o-mini
252
+ temperature: 0.35
253
+
254
+ send_stakeholder_update:
255
+ kind: task
256
+ description: "Deliver FYI notes to stakeholders and capture acknowledgement."
257
+ action: send_email
258
+ inputs:
259
+ to: "=$iteration.item.email"
260
+ subject: "='FYI: ' & $iteration.item.name & ' about ' & $output.understand_request.intent"
261
+ body: "=$output.draft_stakeholder_update.message"
262
+
263
+ # Flow defines execution order, mixing sequential chains, parallel blocks, conditionals, waits, and loops.
264
+ flow:
265
+ - do: understand_request # Step 1: always start by understanding the request ($input.query).
266
+
267
+ # Step 2: If required context is missing, ask the user and pause until they answer.
268
+ - conditional:
269
+ description: "Human-in-the-loop gate: pause the run until user replies with missing details."
270
+ when:
271
+ - condition: "=$count($output.understand_request.missing_fields) > 0"
272
+ steps:
273
+ - do: ask_for_missing_detail # settings.await_user=true causes a checkpoint + wait-for-reply resume
274
+ - else:
275
+ steps: [] # nothing to do when request is already complete
276
+
277
+ - do: fetch_user_profile # Step 3: enrich with CRM data before branching.
278
+
279
+ # Step 4: run three independent enrichment tasks concurrently.
280
+ # Note: each entry under `parallel.steps` can be a `do`, `conditional`, `loop`, or nested `parallel` block.
281
+ - parallel:
282
+ strategy: wait_all # wait_all ensures downstream steps see every output
283
+ steps:
284
+ - do: search_recent_news
285
+ - do: retrieve_memory_thread
286
+ - do: fetch_calendar_events
287
+
288
+ - do: synthesize_research # Step 5: summarize signals (including any user clarification).
289
+ - do: route_by_intent # Step 6: produce a route key used by the conditional below.
290
+
291
+ # Step 7: conditional routing with a nested condition.
292
+ - conditional:
293
+ description: "Branch based on router output; demonstrates multi-branch + nested logic."
294
+ when:
295
+ - condition: "=$output.route_by_intent.route = 'draft_support_reply'"
296
+ steps:
297
+ - do: draft_support_reply
298
+ # Nested conditional: if the request is high priority, escalate after drafting.
299
+ - conditional:
300
+ when:
301
+ - condition: "=$input.priority = 'high'"
302
+ steps:
303
+ - do: escalate_to_human
304
+ - condition: "=$output.route_by_intent.route = 'craft_sales_pitch'"
305
+ steps:
306
+ - do: craft_sales_pitch
307
+ - condition: "=$output.route_by_intent.route = 'escalate_to_human'"
308
+ steps:
309
+ - do: escalate_to_human
310
+ - else:
311
+ steps:
312
+ - do: send_generic_summary
313
+
314
+ # Step 8: loop example showcasing for-each fan-out with an accumulator and early exit.
315
+ - loop:
316
+ type: for_each
317
+ name: notify_stakeholders # Optional: name surfaces $output.notify_stakeholders.*
318
+ description: "Fan out FYI updates to profile stakeholders until one acknowledges."
319
+ for_each:
320
+ item: stakeholder
321
+ in: "=$output.fetch_user_profile.profile.stakeholders" # expects [{name, email, role}]
322
+ max_concurrency: 2 # throttle deliveries to avoid rate limits
323
+ until: "=$output.send_stakeholder_update.acknowledged = true" # break once any ack arrives
324
+ accumulate:
325
+ as: notifications # accumulator is visible as $accumulator.notifications
326
+ initial: [] # seed value for the accumulator
327
+ merge: > # JSONata to append per-iteration output
328
+ = $append(
329
+ $accumulator.notifications,
330
+ [{'email': $iteration.item.email,
331
+ 'status': $output.send_stakeholder_update.status,
332
+ 'ack': $output.send_stakeholder_update.acknowledged}]
333
+ )
334
+ steps:
335
+ - do: draft_stakeholder_update # has access to $iteration.item and $iteration.index
336
+ - do: send_stakeholder_update # delivers the note and sets acknowledged flag
337
+ # Accumulated data is accessible as $output.notify_stakeholders.notifications downstream.
338
+
339
+ - do: send_email_response # Step 9: final action uses outputs of whichever branch ran.
340
+
341
+ # Final workflow outputs expose the important artifacts to callers.
342
+ outputs:
343
+ delivered: "=$output.send_email_response.status"
344
+ intent: "=$output.understand_request.intent"
345
+ missing_fields_resolved: "=$count($output.understand_request.missing_fields) = 0"
346
+ user_reply: "=$exists($output.ask_for_missing_detail.text) ? $output.ask_for_missing_detail.text : null"
347
+ research_brief: "=$output.synthesize_research.synthesis"
348
+ ticket: "=$output.escalate_to_human.ticket_id" # may be null if no escalation occurred
349
+ stakeholder_notifications: "=$output.notify_stakeholders.notifications"
350
+
351
+ # Notes:
352
+ # - Expressions: strings starting with "=" are JSONata with scope {context, input, output};
353
+ # other strings are treated as literals.
354
+ # - Accessors recap: $context.* (runtime info and long-term state you injected),
355
+ # $input.* (original workflow inputs), $output.* (raw results from prior tasks), $iteration.* (loop locals).
356
+ # - Human-in-the-loop: tasks can declare settings.await_user=true to checkpoint execution, send a message,
357
+ # and resume when the user replies. Engines may optionally support on_timeout/fallback hooks, not shown here.
358
+ # - Error handling & retries: globally configured in defaults, overridable per task (see above).
359
+ # - Control flow: sequential steps (do), conditionals (conditional), fan-out (parallel, loop), and waits (await_user via task settings).
@@ -0,0 +1,47 @@
1
+ /*
2
+ * Hexabot — Fair Core License (FCL-1.0-ALv2)
3
+ * Copyright (c) 2025 Hexastack.
4
+ * Full terms: see LICENSE.md.
5
+ */
6
+
7
+ import { z } from 'zod';
8
+
9
+ import { defineAction } from '../../../src';
10
+ import type { Settings } from '../../../src/dsl.types';
11
+ import type { LoopExampleContext } from '../context';
12
+
13
+ const inputSchema = z.object({});
14
+ const resumeSchema = z.object({
15
+ text: z.string(),
16
+ });
17
+ const outputSchema = z.object({
18
+ text: z.string(),
19
+ });
20
+
21
+ type AwaitReplyInput = z.infer<typeof inputSchema>;
22
+ type AwaitReplyOutput = z.infer<typeof outputSchema>;
23
+
24
+ export const awaitReply = defineAction<
25
+ AwaitReplyInput,
26
+ AwaitReplyOutput,
27
+ LoopExampleContext,
28
+ Settings
29
+ >({
30
+ name: 'await_reply',
31
+ description:
32
+ 'Pause workflow execution until the simulated user sends a reply payload.',
33
+ inputSchema,
34
+ outputSchema,
35
+ execute: async ({ context }) => {
36
+ const resumeData = await context.workflow.suspend({
37
+ reason: 'awaiting_user_response',
38
+ data: { hint: 'Provide phone number' },
39
+ });
40
+ const parsed = resumeSchema.safeParse(resumeData);
41
+ if (!parsed.success) {
42
+ throw new Error('resumeData must include a string `text` field.');
43
+ }
44
+
45
+ return parsed.data;
46
+ },
47
+ });
@@ -0,0 +1,19 @@
1
+ /*
2
+ * Hexabot — Fair Core License (FCL-1.0-ALv2)
3
+ * Copyright (c) 2025 Hexastack.
4
+ * Full terms: see LICENSE.md.
5
+ */
6
+
7
+ import type { Action, Settings } from '../../../src';
8
+ import type { LoopExampleContext } from '../context';
9
+
10
+ import { awaitReply } from './await-reply';
11
+ import { sendTextMessage } from './send-text-message';
12
+
13
+ export const loopExampleActions: Record<
14
+ string,
15
+ Action<unknown, unknown, LoopExampleContext, Settings>
16
+ > = {
17
+ send_text_message: sendTextMessage,
18
+ await_reply: awaitReply,
19
+ };
@@ -0,0 +1,40 @@
1
+ /*
2
+ * Hexabot — Fair Core License (FCL-1.0-ALv2)
3
+ * Copyright (c) 2025 Hexastack.
4
+ * Full terms: see LICENSE.md.
5
+ */
6
+
7
+ import { z } from 'zod';
8
+
9
+ import { defineAction } from '../../../src';
10
+ import type { Settings } from '../../../src/dsl.types';
11
+ import type { LoopExampleContext } from '../context';
12
+
13
+ const inputSchema = z.object({
14
+ text: z.string(),
15
+ });
16
+ const outputSchema = z.object({
17
+ text: z.string(),
18
+ });
19
+
20
+ type SendTextMessageInput = z.infer<typeof inputSchema>;
21
+ type SendTextMessageOutput = z.infer<typeof outputSchema>;
22
+
23
+ export const sendTextMessage = defineAction<
24
+ SendTextMessageInput,
25
+ SendTextMessageOutput,
26
+ LoopExampleContext,
27
+ Settings
28
+ >({
29
+ name: 'send_text_message',
30
+ description: 'Mock action that emits a text message to the subscriber.',
31
+ inputSchema,
32
+ outputSchema,
33
+ execute: async ({ input, context }) => {
34
+ context.log('Send text message', { text: input.text });
35
+
36
+ return {
37
+ text: input.text,
38
+ };
39
+ },
40
+ });
@@ -0,0 +1,32 @@
1
+ /*
2
+ * Hexabot — Fair Core License (FCL-1.0-ALv2)
3
+ * Copyright (c) 2025 Hexastack.
4
+ * Full terms: see LICENSE.md.
5
+ */
6
+
7
+ import {
8
+ BaseWorkflowContext,
9
+ WorkflowEventEmitter,
10
+ type WorkflowEventEmitterLike,
11
+ } from '../../src';
12
+
13
+ export type LoopExampleContextState = {
14
+ channel: string;
15
+ };
16
+
17
+ export class LoopExampleContext extends BaseWorkflowContext<LoopExampleContextState> {
18
+ public eventEmitter: WorkflowEventEmitterLike<WorkflowEventEmitter>;
19
+
20
+ constructor(
21
+ state: LoopExampleContextState,
22
+ eventEmitter: WorkflowEventEmitterLike<WorkflowEventEmitter> = new WorkflowEventEmitter(),
23
+ ) {
24
+ super(state);
25
+ this.eventEmitter = eventEmitter;
26
+ }
27
+
28
+ log(message: string, payload?: unknown): void {
29
+ const suffix = payload === undefined ? '' : ` ${JSON.stringify(payload)}`;
30
+ console.log(`[loop] ${message}${suffix}`);
31
+ }
32
+ }
@@ -0,0 +1,66 @@
1
+ /*
2
+ * Hexabot — Fair Core License (FCL-1.0-ALv2)
3
+ * Copyright (c) 2025 Hexastack.
4
+ * Full terms: see LICENSE.md.
5
+ */
6
+
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+
10
+ import { Workflow, WorkflowEventEmitter } from '../../src';
11
+
12
+ import { loopExampleActions } from './actions';
13
+ import { LoopExampleContext } from './context';
14
+
15
+ const workflowPath = path.join(__dirname, 'workflow.yml');
16
+
17
+ async function main() {
18
+ const yamlSource = fs.readFileSync(workflowPath, 'utf8');
19
+ const workflow = Workflow.fromYaml(yamlSource, {
20
+ actions: loopExampleActions,
21
+ });
22
+ const emitter = new WorkflowEventEmitter();
23
+ const context = new LoopExampleContext({ channel: 'chat' }, emitter);
24
+
25
+ emitter.on('hook:workflow:start', () => context.log('workflow started'));
26
+ emitter.on('hook:workflow:suspended', ({ step, reason }) =>
27
+ context.log(`workflow suspended at "${step.name}"`, { reason }),
28
+ );
29
+ emitter.on('hook:workflow:finish', ({ output }) =>
30
+ context.log('workflow finished', output),
31
+ );
32
+
33
+ const runner = await workflow.buildAsyncRunner();
34
+ let result = await runner.start({
35
+ inputData: {},
36
+ context,
37
+ });
38
+ const simulatedReplies = ['12', '12345678'];
39
+ let attempt = 0;
40
+
41
+ while (result.status === 'suspended') {
42
+ const nextReply =
43
+ simulatedReplies[Math.min(attempt, simulatedReplies.length - 1)];
44
+ attempt += 1;
45
+
46
+ if (!nextReply) {
47
+ throw new Error('No simulated reply available for resuming the workflow');
48
+ }
49
+
50
+ context.log('resuming with simulated reply', { text: nextReply });
51
+ result = await runner.resume({
52
+ resumeData: { text: nextReply },
53
+ });
54
+ }
55
+
56
+ if (result.status === 'finished') {
57
+ console.log('Final workflow outputs:', result.output);
58
+
59
+ return;
60
+ }
61
+
62
+ console.error('Workflow failed:', result.error);
63
+ }
64
+
65
+ // pnpm dlx ts-node packages/agentic/examples/loop/workflow.ts
66
+ void main();
@@ -0,0 +1,66 @@
1
+ defaults:
2
+ settings:
3
+ timeout_ms: 0
4
+ retries:
5
+ enabled: false
6
+ max_attempts: 3
7
+ backoff_ms: 25
8
+ max_delay_ms: 10000
9
+ jitter: 0
10
+ multiplier: 2
11
+
12
+ defs:
13
+ ask_phone_number:
14
+ kind: task
15
+ description: Ask the subscriber for their phone number.
16
+ action: send_text_message
17
+ inputs:
18
+ text: Please enter your phone number in 8 digits.
19
+ settings:
20
+ typing: 0
21
+
22
+ ask_phone_number_again:
23
+ kind: task
24
+ description: Ask again when the provided value is invalid.
25
+ action: send_text_message
26
+ inputs:
27
+ text: Invalid phone number. Please enter exactly 8 digits.
28
+ settings:
29
+ typing: 0
30
+
31
+ confirm_phone_number:
32
+ kind: task
33
+ description: Confirm that the phone number was captured.
34
+ action: send_text_message
35
+ inputs:
36
+ text: Well received.
37
+ settings:
38
+ typing: 0
39
+
40
+ await_phone_reply:
41
+ kind: task
42
+ description: Suspend the workflow until a subscriber reply is received.
43
+ action: await_reply
44
+
45
+ flow:
46
+ - do: ask_phone_number
47
+
48
+ - loop:
49
+ type: while
50
+ name: capture_phone_number
51
+ description: Keep waiting for a valid 8-digit phone number.
52
+ while: =$not($exists($output.await_phone_reply.text) and $exists($match($trim($output.await_phone_reply.text), /^\d{8}$/)))
53
+ steps:
54
+ - do: await_phone_reply
55
+ - conditional:
56
+ when:
57
+ - condition: =$exists($match($trim($output.await_phone_reply.text), /^\d{8}$/))
58
+ steps:
59
+ - do: confirm_phone_number
60
+ - else: true
61
+ steps:
62
+ - do: ask_phone_number_again
63
+
64
+ outputs:
65
+ phone_number: =$trim($output.await_phone_reply.text)
66
+ result: =$trim($output.await_phone_reply.text)
@@ -0,0 +1,43 @@
1
+ /*
2
+ * Hexabot — Fair Core License (FCL-1.0-ALv2)
3
+ * Copyright (c) 2025 Hexastack.
4
+ * Full terms: see LICENSE.md.
5
+ */
6
+
7
+ import { z } from 'zod';
8
+
9
+ import { defineAction, type Settings } from '../../../src';
10
+ import type { SuspendResumeContext } from '../context';
11
+
12
+ const inputSchema = z.object({
13
+ reply: z.string(),
14
+ });
15
+ const outputSchema = z.object({
16
+ message: z.string(),
17
+ });
18
+
19
+ type FormatReplyInput = z.infer<typeof inputSchema>;
20
+ type FormatReplyOutput = z.infer<typeof outputSchema>;
21
+
22
+ export const formatReply = defineAction<
23
+ FormatReplyInput,
24
+ FormatReplyOutput,
25
+ SuspendResumeContext,
26
+ Settings
27
+ >({
28
+ name: 'format_reply',
29
+ description: 'Formats the user reply captured after resuming execution.',
30
+ inputSchema,
31
+ outputSchema,
32
+ execute: async ({ input, context }) => {
33
+ const trimmed = input.reply.trim();
34
+
35
+ context.log('Formatting reply after resume', { reply: trimmed });
36
+
37
+ return {
38
+ message: trimmed
39
+ ? `User replied: ${trimmed}`
40
+ : 'No reply was provided when resuming the workflow.',
41
+ };
42
+ },
43
+ });
@@ -0,0 +1,13 @@
1
+ /*
2
+ * Hexabot — Fair Core License (FCL-1.0-ALv2)
3
+ * Copyright (c) 2025 Hexastack.
4
+ * Full terms: see LICENSE.md.
5
+ */
6
+
7
+ import { formatReply } from './format-reply';
8
+ import { waitForUser } from './wait-for-user';
9
+
10
+ export const suspendResumeActions = {
11
+ wait_for_user: waitForUser,
12
+ format_reply: formatReply,
13
+ };