@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.
- package/LICENSE.md +120 -0
- package/README.md +231 -0
- package/dist/cjs/action/abstract-action.js +141 -0
- package/dist/cjs/action/action.js +47 -0
- package/dist/cjs/action/action.types.js +7 -0
- package/dist/cjs/bindings/base-binding.js +278 -0
- package/dist/cjs/context.js +79 -0
- package/dist/cjs/dsl.types.js +318 -0
- package/dist/cjs/index.js +52 -0
- package/dist/cjs/runner-runtime-control.js +343 -0
- package/dist/cjs/step-executors/conditional-executor.js +63 -0
- package/dist/cjs/step-executors/loop-executor.js +187 -0
- package/dist/cjs/step-executors/parallel-executor.js +51 -0
- package/dist/cjs/step-executors/skip-helpers.js +29 -0
- package/dist/cjs/step-executors/task-executor.js +155 -0
- package/dist/cjs/step-executors/types.js +7 -0
- package/dist/cjs/suspension-rebuilder.js +301 -0
- package/dist/cjs/utils/deferred.js +17 -0
- package/dist/cjs/utils/naming.js +48 -0
- package/dist/cjs/utils/timeout.js +42 -0
- package/dist/cjs/utils/workflow-definition.js +102 -0
- package/dist/cjs/workflow-compiler.js +212 -0
- package/dist/cjs/workflow-event-emitter.js +40 -0
- package/dist/cjs/workflow-runner.js +448 -0
- package/dist/cjs/workflow-types.js +7 -0
- package/dist/cjs/workflow-values.js +99 -0
- package/dist/cjs/workflow.js +243 -0
- package/dist/esm/action/abstract-action.js +137 -0
- package/dist/esm/action/action.js +44 -0
- package/dist/esm/action/action.types.js +6 -0
- package/dist/esm/bindings/base-binding.js +273 -0
- package/dist/esm/context.js +75 -0
- package/dist/esm/dsl.types.js +309 -0
- package/dist/esm/index.js +16 -0
- package/dist/esm/runner-runtime-control.js +338 -0
- package/dist/esm/step-executors/conditional-executor.js +60 -0
- package/dist/esm/step-executors/loop-executor.js +182 -0
- package/dist/esm/step-executors/parallel-executor.js +48 -0
- package/dist/esm/step-executors/skip-helpers.js +25 -0
- package/dist/esm/step-executors/task-executor.js +152 -0
- package/dist/esm/step-executors/types.js +6 -0
- package/dist/esm/suspension-rebuilder.js +296 -0
- package/dist/esm/utils/deferred.js +14 -0
- package/dist/esm/utils/naming.js +42 -0
- package/dist/esm/utils/timeout.js +37 -0
- package/dist/esm/utils/workflow-definition.js +98 -0
- package/dist/esm/workflow-compiler.js +208 -0
- package/dist/esm/workflow-event-emitter.js +36 -0
- package/dist/esm/workflow-runner.js +444 -0
- package/dist/esm/workflow-types.js +6 -0
- package/dist/esm/workflow-values.js +89 -0
- package/dist/esm/workflow.js +236 -0
- package/dist/types/action/abstract-action.d.ts +54 -0
- package/dist/types/action/abstract-action.d.ts.map +1 -0
- package/dist/types/action/action.d.ts +23 -0
- package/dist/types/action/action.d.ts.map +1 -0
- package/dist/types/action/action.types.d.ts +55 -0
- package/dist/types/action/action.types.d.ts.map +1 -0
- package/dist/types/bindings/base-binding.d.ts +52 -0
- package/dist/types/bindings/base-binding.d.ts.map +1 -0
- package/dist/types/context.d.ts +105 -0
- package/dist/types/context.d.ts.map +1 -0
- package/dist/types/dsl.types.d.ts +220 -0
- package/dist/types/dsl.types.d.ts.map +1 -0
- package/dist/types/index.d.ts +18 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/runner-runtime-control.d.ts +64 -0
- package/dist/types/runner-runtime-control.d.ts.map +1 -0
- package/dist/types/step-executors/conditional-executor.d.ts +13 -0
- package/dist/types/step-executors/conditional-executor.d.ts.map +1 -0
- package/dist/types/step-executors/loop-executor.d.ts +33 -0
- package/dist/types/step-executors/loop-executor.d.ts.map +1 -0
- package/dist/types/step-executors/parallel-executor.d.ts +14 -0
- package/dist/types/step-executors/parallel-executor.d.ts.map +1 -0
- package/dist/types/step-executors/skip-helpers.d.ts +12 -0
- package/dist/types/step-executors/skip-helpers.d.ts.map +1 -0
- package/dist/types/step-executors/task-executor.d.ts +13 -0
- package/dist/types/step-executors/task-executor.d.ts.map +1 -0
- package/dist/types/step-executors/types.d.ts +24 -0
- package/dist/types/step-executors/types.d.ts.map +1 -0
- package/dist/types/suspension-rebuilder.d.ts +67 -0
- package/dist/types/suspension-rebuilder.d.ts.map +1 -0
- package/dist/types/utils/deferred.d.ts +7 -0
- package/dist/types/utils/deferred.d.ts.map +1 -0
- package/dist/types/utils/naming.d.ts +24 -0
- package/dist/types/utils/naming.d.ts.map +1 -0
- package/dist/types/utils/timeout.d.ts +17 -0
- package/dist/types/utils/timeout.d.ts.map +1 -0
- package/dist/types/utils/workflow-definition.d.ts +3 -0
- package/dist/types/utils/workflow-definition.d.ts.map +1 -0
- package/dist/types/workflow-compiler.d.ts +12 -0
- package/dist/types/workflow-compiler.d.ts.map +1 -0
- package/dist/types/workflow-event-emitter.d.ts +70 -0
- package/dist/types/workflow-event-emitter.d.ts.map +1 -0
- package/dist/types/workflow-runner.d.ts +179 -0
- package/dist/types/workflow-runner.d.ts.map +1 -0
- package/dist/types/workflow-types.d.ts +195 -0
- package/dist/types/workflow-types.d.ts.map +1 -0
- package/dist/types/workflow-values.d.ts +34 -0
- package/dist/types/workflow-values.d.ts.map +1 -0
- package/dist/types/workflow.d.ts +75 -0
- package/dist/types/workflow.d.ts.map +1 -0
- package/examples/defs-bindings-agent/actions/ai-agent.ts +110 -0
- package/examples/defs-bindings-agent/actions/caculate-score.ts +49 -0
- package/examples/defs-bindings-agent/actions/index.ts +15 -0
- package/examples/defs-bindings-agent/bindings.ts +20 -0
- package/examples/defs-bindings-agent/context.ts +32 -0
- package/examples/defs-bindings-agent/workflow.ts +67 -0
- package/examples/defs-bindings-agent/workflow.yml +28 -0
- package/examples/full/actions/await-user-input.ts +46 -0
- package/examples/full/actions/call-llm.ts +133 -0
- package/examples/full/actions/create-ticket.ts +46 -0
- package/examples/full/actions/decision-router.ts +83 -0
- package/examples/full/actions/get-calendar-events.ts +55 -0
- package/examples/full/actions/get-user-profile.ts +72 -0
- package/examples/full/actions/index.ts +33 -0
- package/examples/full/actions/query-memory.ts +45 -0
- package/examples/full/actions/search-web.ts +53 -0
- package/examples/full/actions/send-email.ts +50 -0
- package/examples/full/context.ts +41 -0
- package/examples/full/workflow.ts +75 -0
- package/examples/full/workflow.yml +359 -0
- package/examples/loop/actions/await-reply.ts +47 -0
- package/examples/loop/actions/index.ts +19 -0
- package/examples/loop/actions/send-text-message.ts +40 -0
- package/examples/loop/context.ts +32 -0
- package/examples/loop/workflow.ts +66 -0
- package/examples/loop/workflow.yml +66 -0
- package/examples/suspend-resume/actions/format-reply.ts +43 -0
- package/examples/suspend-resume/actions/index.ts +13 -0
- package/examples/suspend-resume/actions/wait-for-user.ts +51 -0
- package/examples/suspend-resume/context.ts +32 -0
- package/examples/suspend-resume/workflow.ts +79 -0
- package/examples/suspend-resume/workflow.yml +29 -0
- package/package.json +60 -0
- package/src/__tests__/parser.test.ts +95 -0
- package/src/__tests__/suspension-rebuilder.test.ts +361 -0
- package/src/__tests__/test-helpers.ts +36 -0
- package/src/__tests__/validation.test.ts +526 -0
- package/src/__tests__/workflow-compiler.test.ts +715 -0
- package/src/__tests__/workflow-definition-path.test.ts +381 -0
- package/src/__tests__/workflow-event-emitter.test.ts +50 -0
- package/src/__tests__/workflow-runner.test.ts +1397 -0
- package/src/__tests__/workflow-values.test.ts +132 -0
- package/src/__tests__/workflow.test.ts +320 -0
- package/src/action/__tests__/abstract-action-timing.test.ts +183 -0
- package/src/action/__tests__/action.test.ts +473 -0
- package/src/action/abstract-action.ts +215 -0
- package/src/action/action.ts +83 -0
- package/src/action/action.types.ts +93 -0
- package/src/bindings/base-binding.ts +508 -0
- package/src/context.ts +179 -0
- package/src/dsl.types.ts +473 -0
- package/src/index.ts +114 -0
- package/src/runner-runtime-control.ts +544 -0
- package/src/step-executors/conditional-executor.test.ts +192 -0
- package/src/step-executors/conditional-executor.ts +83 -0
- package/src/step-executors/loop-executor.test.ts +303 -0
- package/src/step-executors/loop-executor.ts +299 -0
- package/src/step-executors/parallel-executor.test.ts +182 -0
- package/src/step-executors/parallel-executor.ts +77 -0
- package/src/step-executors/skip-helpers.ts +68 -0
- package/src/step-executors/task-executor.test.ts +257 -0
- package/src/step-executors/task-executor.ts +248 -0
- package/src/step-executors/types.ts +70 -0
- package/src/suspension-rebuilder.ts +504 -0
- package/src/utils/deferred.ts +23 -0
- package/src/utils/naming.ts +55 -0
- package/src/utils/timeout.ts +48 -0
- package/src/utils/workflow-definition.ts +173 -0
- package/src/workflow-compiler.ts +317 -0
- package/src/workflow-event-emitter.ts +86 -0
- package/src/workflow-runner.ts +603 -0
- package/src/workflow-types.ts +207 -0
- package/src/workflow-values.ts +159 -0
- 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
|
+
};
|