soe-ai 0.2.0b1__py3-none-any.whl

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 (145) hide show
  1. soe/__init__.py +50 -0
  2. soe/broker.py +168 -0
  3. soe/builtin_tools/__init__.py +51 -0
  4. soe/builtin_tools/soe_add_signal.py +82 -0
  5. soe/builtin_tools/soe_call_tool.py +111 -0
  6. soe/builtin_tools/soe_copy_context.py +80 -0
  7. soe/builtin_tools/soe_explore_docs.py +290 -0
  8. soe/builtin_tools/soe_get_available_tools.py +42 -0
  9. soe/builtin_tools/soe_get_context.py +50 -0
  10. soe/builtin_tools/soe_get_context_schema.py +56 -0
  11. soe/builtin_tools/soe_get_identities.py +63 -0
  12. soe/builtin_tools/soe_get_workflows.py +63 -0
  13. soe/builtin_tools/soe_inject_context_schema_field.py +80 -0
  14. soe/builtin_tools/soe_inject_identity.py +64 -0
  15. soe/builtin_tools/soe_inject_node.py +86 -0
  16. soe/builtin_tools/soe_inject_workflow.py +105 -0
  17. soe/builtin_tools/soe_list_contexts.py +73 -0
  18. soe/builtin_tools/soe_remove_context_schema_field.py +61 -0
  19. soe/builtin_tools/soe_remove_identity.py +61 -0
  20. soe/builtin_tools/soe_remove_node.py +72 -0
  21. soe/builtin_tools/soe_remove_workflow.py +62 -0
  22. soe/builtin_tools/soe_update_context.py +54 -0
  23. soe/docs/_config.yml +10 -0
  24. soe/docs/advanced_patterns/guide_fanout_and_aggregations.md +318 -0
  25. soe/docs/advanced_patterns/guide_inheritance.md +435 -0
  26. soe/docs/advanced_patterns/hybrid_intelligence.md +237 -0
  27. soe/docs/advanced_patterns/index.md +49 -0
  28. soe/docs/advanced_patterns/operational.md +781 -0
  29. soe/docs/advanced_patterns/self_evolving_workflows.md +385 -0
  30. soe/docs/advanced_patterns/swarm_intelligence.md +211 -0
  31. soe/docs/builtins/context.md +164 -0
  32. soe/docs/builtins/context_schema.md +158 -0
  33. soe/docs/builtins/identity.md +139 -0
  34. soe/docs/builtins/soe_explore_docs.md +135 -0
  35. soe/docs/builtins/tools.md +164 -0
  36. soe/docs/builtins/workflows.md +199 -0
  37. soe/docs/guide_00_getting_started.md +341 -0
  38. soe/docs/guide_01_tool.md +206 -0
  39. soe/docs/guide_02_llm.md +143 -0
  40. soe/docs/guide_03_router.md +146 -0
  41. soe/docs/guide_04_patterns.md +475 -0
  42. soe/docs/guide_05_agent.md +159 -0
  43. soe/docs/guide_06_schema.md +397 -0
  44. soe/docs/guide_07_identity.md +540 -0
  45. soe/docs/guide_08_child.md +612 -0
  46. soe/docs/guide_09_ecosystem.md +690 -0
  47. soe/docs/guide_10_infrastructure.md +427 -0
  48. soe/docs/guide_11_builtins.md +126 -0
  49. soe/docs/index.md +104 -0
  50. soe/docs/primitives/backends.md +281 -0
  51. soe/docs/primitives/context.md +256 -0
  52. soe/docs/primitives/node_reference.md +259 -0
  53. soe/docs/primitives/primitives.md +331 -0
  54. soe/docs/primitives/signals.md +865 -0
  55. soe/docs_index.py +2 -0
  56. soe/init.py +165 -0
  57. soe/lib/__init__.py +0 -0
  58. soe/lib/child_context.py +46 -0
  59. soe/lib/context_fields.py +51 -0
  60. soe/lib/inheritance.py +172 -0
  61. soe/lib/jinja_render.py +113 -0
  62. soe/lib/operational.py +51 -0
  63. soe/lib/parent_sync.py +71 -0
  64. soe/lib/register_event.py +75 -0
  65. soe/lib/schema_validation.py +134 -0
  66. soe/lib/yaml_parser.py +14 -0
  67. soe/local_backends/__init__.py +18 -0
  68. soe/local_backends/factory.py +124 -0
  69. soe/local_backends/in_memory/context.py +38 -0
  70. soe/local_backends/in_memory/conversation_history.py +60 -0
  71. soe/local_backends/in_memory/identity.py +52 -0
  72. soe/local_backends/in_memory/schema.py +40 -0
  73. soe/local_backends/in_memory/telemetry.py +38 -0
  74. soe/local_backends/in_memory/workflow.py +33 -0
  75. soe/local_backends/storage/context.py +57 -0
  76. soe/local_backends/storage/conversation_history.py +82 -0
  77. soe/local_backends/storage/identity.py +118 -0
  78. soe/local_backends/storage/schema.py +96 -0
  79. soe/local_backends/storage/telemetry.py +72 -0
  80. soe/local_backends/storage/workflow.py +56 -0
  81. soe/nodes/__init__.py +13 -0
  82. soe/nodes/agent/__init__.py +10 -0
  83. soe/nodes/agent/factory.py +134 -0
  84. soe/nodes/agent/lib/loop_handlers.py +150 -0
  85. soe/nodes/agent/lib/loop_state.py +157 -0
  86. soe/nodes/agent/lib/prompts.py +65 -0
  87. soe/nodes/agent/lib/tools.py +35 -0
  88. soe/nodes/agent/stages/__init__.py +12 -0
  89. soe/nodes/agent/stages/parameter.py +37 -0
  90. soe/nodes/agent/stages/response.py +54 -0
  91. soe/nodes/agent/stages/router.py +37 -0
  92. soe/nodes/agent/state.py +111 -0
  93. soe/nodes/agent/types.py +66 -0
  94. soe/nodes/agent/validation/__init__.py +11 -0
  95. soe/nodes/agent/validation/config.py +95 -0
  96. soe/nodes/agent/validation/operational.py +24 -0
  97. soe/nodes/child/__init__.py +3 -0
  98. soe/nodes/child/factory.py +61 -0
  99. soe/nodes/child/state.py +59 -0
  100. soe/nodes/child/validation/__init__.py +11 -0
  101. soe/nodes/child/validation/config.py +126 -0
  102. soe/nodes/child/validation/operational.py +28 -0
  103. soe/nodes/lib/conditions.py +71 -0
  104. soe/nodes/lib/context.py +24 -0
  105. soe/nodes/lib/conversation_history.py +77 -0
  106. soe/nodes/lib/identity.py +64 -0
  107. soe/nodes/lib/llm_resolver.py +142 -0
  108. soe/nodes/lib/output.py +68 -0
  109. soe/nodes/lib/response_builder.py +91 -0
  110. soe/nodes/lib/signal_emission.py +79 -0
  111. soe/nodes/lib/signals.py +54 -0
  112. soe/nodes/lib/tools.py +100 -0
  113. soe/nodes/llm/__init__.py +7 -0
  114. soe/nodes/llm/factory.py +103 -0
  115. soe/nodes/llm/state.py +76 -0
  116. soe/nodes/llm/types.py +12 -0
  117. soe/nodes/llm/validation/__init__.py +11 -0
  118. soe/nodes/llm/validation/config.py +89 -0
  119. soe/nodes/llm/validation/operational.py +23 -0
  120. soe/nodes/router/__init__.py +3 -0
  121. soe/nodes/router/factory.py +37 -0
  122. soe/nodes/router/state.py +32 -0
  123. soe/nodes/router/validation/__init__.py +11 -0
  124. soe/nodes/router/validation/config.py +58 -0
  125. soe/nodes/router/validation/operational.py +16 -0
  126. soe/nodes/tool/factory.py +66 -0
  127. soe/nodes/tool/lib/__init__.py +11 -0
  128. soe/nodes/tool/lib/conditions.py +35 -0
  129. soe/nodes/tool/lib/failure.py +28 -0
  130. soe/nodes/tool/lib/parameters.py +67 -0
  131. soe/nodes/tool/state.py +66 -0
  132. soe/nodes/tool/types.py +27 -0
  133. soe/nodes/tool/validation/__init__.py +15 -0
  134. soe/nodes/tool/validation/config.py +132 -0
  135. soe/nodes/tool/validation/operational.py +16 -0
  136. soe/types.py +209 -0
  137. soe/validation/__init__.py +18 -0
  138. soe/validation/config.py +195 -0
  139. soe/validation/jinja.py +54 -0
  140. soe/validation/operational.py +110 -0
  141. soe_ai-0.2.0b1.dist-info/METADATA +262 -0
  142. soe_ai-0.2.0b1.dist-info/RECORD +145 -0
  143. soe_ai-0.2.0b1.dist-info/WHEEL +5 -0
  144. soe_ai-0.2.0b1.dist-info/licenses/LICENSE +21 -0
  145. soe_ai-0.2.0b1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,690 @@
1
+
2
+ # SOE Guide: Chapter 9 - The Workflows Ecosystem
3
+
4
+ ## Understanding the Big Picture
5
+
6
+ Before diving deeper into SOE, it's important to understand **why** workflows are structured the way they are. This chapter explains the ecosystem—how workflows relate to each other, how data persists, and how you can build sophisticated multi-workflow systems.
7
+
8
+ ---
9
+
10
+ ## Combined Config: The Recommended Pattern
11
+
12
+ The most powerful way to configure SOE is with **combined config**—a single structure containing workflows, context schemas, and identities:
13
+
14
+ ```yaml
15
+ workflows:
16
+ example_workflow:
17
+ Analyze:
18
+ node_type: llm
19
+ event_triggers: [START]
20
+ identity: analyst
21
+ prompt: "Analyze: {{ context.input }}"
22
+ output_field: analysis
23
+ event_emissions:
24
+ - signal_name: ANALYZED
25
+
26
+ Summarize:
27
+ node_type: llm
28
+ event_triggers: [ANALYZED]
29
+ identity: analyst
30
+ prompt: "Summarize the analysis: {{ context.analysis }}"
31
+ output_field: summary
32
+ event_emissions:
33
+ - signal_name: DONE
34
+
35
+ context_schema:
36
+ input:
37
+ type: string
38
+ description: The input to analyze
39
+ analysis:
40
+ type: object
41
+ description: Detailed analysis result
42
+ summary:
43
+ type: string
44
+ description: A concise summary
45
+
46
+ identities:
47
+ analyst: "You are a thorough analyst. Be precise and structured in your analysis."
48
+ ```
49
+
50
+ ### Why Combined Config?
51
+
52
+ 1. **Single source of truth**: All configuration in one place
53
+ 2. **Automatic setup**: Schemas and identities are saved to backends automatically
54
+ 3. **Keyed by execution**: Child workflows can access parent's schemas and identities
55
+ 4. **Clear structure**: Easy to understand what your workflow ecosystem contains
56
+
57
+ ### Combined Config Sections
58
+
59
+ | Section | Purpose | Required |
60
+ |---------|---------|----------|
61
+ | `workflows` | Workflow definitions (nodes, signals) | Yes |
62
+ | `context_schema` | Field type validation for LLM outputs | No |
63
+ | `identities` | System prompts for LLM/Agent nodes | No |
64
+
65
+ ---
66
+
67
+ ## Multi-Workflow Ecosystems
68
+
69
+ When you call `orchestrate()`, you pass a **config**. This config can contain multiple workflow definitions:
70
+
71
+ ```python
72
+ execution_id = orchestrate(
73
+ config=my_config, # Contains MULTIPLE workflows
74
+ initial_workflow_name="main_workflow", # Which one to start
75
+ initial_signals=["START"],
76
+ initial_context={...},
77
+ backends=backends,
78
+ broadcast_signals_caller=broadcast,
79
+ )
80
+ ```
81
+
82
+ ### Why Multiple Workflows?
83
+
84
+ The config is your **ecosystem**. Workflows can:
85
+
86
+ 1. **Spawn children** — One workflow triggers another via child nodes
87
+ 2. **Share definitions** — Common sub-workflows reused across your system
88
+ 3. **Run in parallel** — Multiple child workflows executing simultaneously
89
+ 4. **Share schemas and identities** — Via `main_execution_id` keying
90
+
91
+ ### Example: Full Ecosystem Config
92
+
93
+ ```yaml
94
+ workflows:
95
+ main_workflow:
96
+ Classifier:
97
+ node_type: router
98
+ event_triggers: [START]
99
+ event_emissions:
100
+ - signal_name: HANDLE_TEXT
101
+ condition: "{{ context.input_type == 'text' }}"
102
+ - signal_name: HANDLE_IMAGE
103
+ condition: "{{ context.input_type == 'image' }}"
104
+
105
+ DelegateToTextProcessor:
106
+ node_type: child
107
+ event_triggers: [HANDLE_TEXT]
108
+ child_workflow_name: text_workflow
109
+ child_initial_signals: [START]
110
+ input_fields: [content]
111
+ signals_to_parent: [DONE]
112
+ context_updates_to_parent: [result]
113
+ event_emissions:
114
+ - signal_name: PROCESSING_COMPLETE
115
+
116
+ DelegateToImageProcessor:
117
+ node_type: child
118
+ event_triggers: [HANDLE_IMAGE]
119
+ child_workflow_name: image_workflow
120
+ child_initial_signals: [START]
121
+ input_fields: [content]
122
+ signals_to_parent: [DONE]
123
+ context_updates_to_parent: [result]
124
+ event_emissions:
125
+ - signal_name: PROCESSING_COMPLETE
126
+
127
+ Finalize:
128
+ node_type: router
129
+ event_triggers: [PROCESSING_COMPLETE]
130
+ event_emissions:
131
+ - signal_name: COMPLETE
132
+
133
+ text_workflow:
134
+ AnalyzeText:
135
+ node_type: llm
136
+ event_triggers: [START]
137
+ identity: text_analyzer
138
+ prompt: "Analyze this text: {{ context.content }}"
139
+ output_field: result
140
+ event_emissions:
141
+ - signal_name: DONE
142
+
143
+ image_workflow:
144
+ AnalyzeImage:
145
+ node_type: llm
146
+ event_triggers: [START]
147
+ identity: image_analyzer
148
+ prompt: "Describe this image: {{ context.content }}"
149
+ output_field: result
150
+ event_emissions:
151
+ - signal_name: DONE
152
+
153
+ context_schema:
154
+ content:
155
+ type: string
156
+ description: The input content to process
157
+ result:
158
+ type: object
159
+ description: The processing result
160
+
161
+ identities:
162
+ text_analyzer: "You are an expert text analyst. Provide detailed, structured analysis."
163
+ image_analyzer: "You are an expert image analyst. Describe visual elements precisely."
164
+ ```
165
+
166
+ In this example:
167
+ - `main_workflow` is the entry point that routes to specialized workflows
168
+ - `text_workflow` and `image_workflow` are child workflows
169
+ - Both child workflows share identities defined in the config
170
+ - Schema validation ensures consistent data structures
171
+
172
+ ---
173
+
174
+ ## Data vs Execution: A Critical Distinction
175
+
176
+ SOE separates two concerns:
177
+
178
+ | Concept | What It Is | Where It Lives |
179
+ |---------|------------|----------------|
180
+ | **Workflow Definition** | The YAML describing nodes and signals | `WorkflowBackend` |
181
+ | **Workflow Execution** | The running state of a specific run | `ContextBackend` |
182
+
183
+ ### Workflow Definitions Are Data
184
+
185
+ Your workflow YAML is **just data**. It can be:
186
+
187
+ - Stored in files, databases, or version control
188
+ - Loaded dynamically at runtime
189
+ - Modified without restarting your application
190
+ - Versioned using any strategy you prefer
191
+
192
+ ```python
193
+ # Load from file
194
+ with open("workflows/my_workflow.yaml") as f:
195
+ workflow_yaml = f.read()
196
+
197
+ # Load from database
198
+ workflow_yaml = db.get_workflow("my_workflow", version="2.1.0")
199
+
200
+ # Pass to orchestrate
201
+ orchestrate(config=workflow_yaml, ...)
202
+ ```
203
+
204
+ ### Executions Are Immutable
205
+
206
+ When you start an orchestration, the workflow definition is **captured** and stored in the `WorkflowBackend` for that execution. This means:
207
+
208
+ 1. **No version conflicts**: If you update your workflow, existing executions continue with their original definition
209
+ 2. **Natural versioning**: Each execution remembers which workflow version it started with
210
+ 3. **Audit trail**: You can inspect what any historical execution ran
211
+
212
+ ```
213
+ Execution 001 → Uses workflow v1.0 (stored in workflow backend)
214
+ Execution 002 → Uses workflow v1.1 (stored in workflow backend)
215
+ Execution 003 → Uses workflow v2.0 (stored in workflow backend)
216
+
217
+ ↓ You update the workflow file ↓
218
+
219
+ Execution 001 → Still uses v1.0 (continues unchanged)
220
+ Execution 004 → Uses new version
221
+ ```
222
+
223
+ ### Why This Matters
224
+
225
+ This architecture means:
226
+
227
+ - **No downtime migrations**: Update workflows without stopping running processes
228
+ - **Rollback safety**: Bad workflow version? New executions use the old; existing continue
229
+ - **Debugging**: Inspect exactly what workflow an execution used, even months later
230
+
231
+ ---
232
+
233
+ ## Parallel Workflow Execution
234
+
235
+ Child workflows can run **in parallel**. When a router emits multiple signals and each triggers a child node, all children start simultaneously (in infrastructure that supports it):
236
+
237
+ ### Fan-Out Pattern with Combined Config
238
+
239
+ ```yaml
240
+ workflows:
241
+ orchestrator_workflow:
242
+ FanOut:
243
+ node_type: router
244
+ event_triggers: [START]
245
+ event_emissions:
246
+ - signal_name: START_WORKER_A
247
+ - signal_name: START_WORKER_B
248
+ - signal_name: START_WORKER_C
249
+
250
+ WorkerA:
251
+ node_type: child
252
+ event_triggers: [START_WORKER_A]
253
+ child_workflow_name: worker_workflow
254
+ child_initial_signals: [START]
255
+ input_fields: [chunk_a]
256
+ signals_to_parent: [WORKER_DONE]
257
+ context_updates_to_parent: [result_a]
258
+ event_emissions:
259
+ - signal_name: A_COMPLETE
260
+
261
+ WorkerB:
262
+ node_type: child
263
+ event_triggers: [START_WORKER_B]
264
+ child_workflow_name: worker_workflow
265
+ child_initial_signals: [START]
266
+ input_fields: [chunk_b]
267
+ signals_to_parent: [WORKER_DONE]
268
+ context_updates_to_parent: [result_b]
269
+ event_emissions:
270
+ - signal_name: B_COMPLETE
271
+
272
+ WorkerC:
273
+ node_type: child
274
+ event_triggers: [START_WORKER_C]
275
+ child_workflow_name: worker_workflow
276
+ child_initial_signals: [START]
277
+ input_fields: [chunk_c]
278
+ signals_to_parent: [WORKER_DONE]
279
+ context_updates_to_parent: [result_c]
280
+ event_emissions:
281
+ - signal_name: C_COMPLETE
282
+
283
+ Aggregate:
284
+ node_type: llm
285
+ event_triggers: [A_COMPLETE, B_COMPLETE, C_COMPLETE]
286
+ identity: aggregator
287
+ prompt: |
288
+ Aggregate the results:
289
+ - Result A: {{ context.result_a }}
290
+ - Result B: {{ context.result_b }}
291
+ - Result C: {{ context.result_c }}
292
+ output_field: final_result
293
+ event_emissions:
294
+ - signal_name: ALL_DONE
295
+
296
+ worker_workflow:
297
+ ProcessData:
298
+ node_type: llm
299
+ event_triggers: [START]
300
+ identity: data_processor
301
+ prompt: "Process this data chunk: {{ context.data }}"
302
+ output_field: result
303
+ event_emissions:
304
+ - signal_name: WORKER_DONE
305
+
306
+ context_schema:
307
+ chunk_a:
308
+ type: object
309
+ description: Data chunk for worker A
310
+ chunk_b:
311
+ type: object
312
+ description: Data chunk for worker B
313
+ chunk_c:
314
+ type: object
315
+ description: Data chunk for worker C
316
+ result_a:
317
+ type: object
318
+ description: Processing result from worker A
319
+ result_b:
320
+ type: object
321
+ description: Processing result from worker B
322
+ result_c:
323
+ type: object
324
+ description: Processing result from worker C
325
+ final_result:
326
+ type: object
327
+ description: Aggregated final result
328
+
329
+ identities:
330
+ data_processor: "You are a data processing specialist. Extract and transform data accurately."
331
+ aggregator: "You are an expert at synthesizing multiple data sources into coherent summaries."
332
+ ```
333
+
334
+ This combined config includes:
335
+ - **workflows**: Orchestrator that fans out to workers
336
+ - **context_schema**: Validates data chunks and results
337
+ - **identities**: Specialized prompts for processor and aggregator
338
+
339
+ ### How Parallel Execution Works
340
+
341
+ ```
342
+ START
343
+
344
+
345
+ ┌─────────┐
346
+ │ FanOut │
347
+ │(Router) │
348
+ └────┬────┘
349
+
350
+ ├──────────────┬──────────────┐
351
+ ▼ ▼ ▼
352
+ ┌─────────┐ ┌─────────┐ ┌─────────┐
353
+ │Worker A │ │Worker B │ │Worker C │
354
+ │ (Child) │ │ (Child) │ │ (Child) │
355
+ └────┬────┘ └────┬────┘ └────┬────┘
356
+ │ │ │
357
+ └──────────────┴──────────────┘
358
+
359
+
360
+ ┌───────────┐
361
+ │ Aggregate │
362
+ │ (Router) │
363
+ └───────────┘
364
+ ```
365
+
366
+ Each worker:
367
+ - Runs independently (potentially on different infrastructure)
368
+ - Updates its own context (isolated)
369
+ - Propagates results back via `context_updates_to_parent`
370
+
371
+ ---
372
+
373
+ ## Fire-and-Forget vs Callbacks
374
+
375
+ Child workflows offer two patterns:
376
+
377
+ ### Fire-and-Forget
378
+
379
+ Start a child and continue immediately—don't wait for completion:
380
+
381
+ ```yaml
382
+ main_workflow:
383
+ LaunchBackground:
384
+ node_type: router
385
+ event_triggers: [START]
386
+ event_emissions:
387
+ - signal_name: TASK_LAUNCHED
388
+ - signal_name: START_BACKGROUND
389
+
390
+ StartBackgroundTask:
391
+ node_type: child
392
+ event_triggers: [START_BACKGROUND]
393
+ child_workflow_name: background_workflow
394
+ child_initial_signals: [START]
395
+ input_fields: [task_data]
396
+ # No signals_to_parent - we don't wait for completion
397
+
398
+ ContinueImmediately:
399
+ node_type: router
400
+ event_triggers: [TASK_LAUNCHED]
401
+ event_emissions:
402
+ - signal_name: PARENT_COMPLETE
403
+
404
+ background_workflow:
405
+ LongRunningTask:
406
+ node_type: tool
407
+ event_triggers: [START]
408
+ tool_name: long_task
409
+ context_parameter_field: task_data
410
+ output_field: result
411
+ event_emissions:
412
+ - signal_name: BACKGROUND_DONE
413
+ ```
414
+
415
+ **Use cases:**
416
+ - Background processing
417
+ - Logging/analytics
418
+ - Notifications that don't affect the main flow
419
+
420
+ ### Callback (Wait for Child)
421
+
422
+ Wait for specific signals from the child before continuing:
423
+
424
+ ```yaml
425
+ # From child node configuration
426
+ signals_to_parent: [CHILD_DONE] # Wait for this signal
427
+ context_updates_to_parent: [result] # Get this data back
428
+ ```
429
+
430
+ **Use cases:**
431
+ - Sequential processing
432
+ - When parent needs child's result
433
+ - Validation before proceeding
434
+
435
+ ---
436
+
437
+ ## External Triggers and Continuation
438
+
439
+ Workflows don't have to complete in one `orchestrate()` call. When there are no more signals to process, the execution simply **stops**. It can be resumed at any time by sending new signals:
440
+
441
+ ### The Pattern
442
+
443
+ ```yaml
444
+ waiting_workflow:
445
+ Initialize:
446
+ node_type: router
447
+ event_triggers: [START]
448
+ event_emissions:
449
+ - signal_name: WAITING_FOR_APPROVAL
450
+
451
+ # This workflow pauses here. An external system must send APPROVED or REJECTED
452
+ # using broadcast_signals with the execution_id
453
+
454
+ HandleApproval:
455
+ node_type: router
456
+ event_triggers: [APPROVED]
457
+ event_emissions:
458
+ - signal_name: PROCESS_APPROVED
459
+
460
+ HandleRejection:
461
+ node_type: router
462
+ event_triggers: [REJECTED]
463
+ event_emissions:
464
+ - signal_name: PROCESS_REJECTED
465
+
466
+ ProcessApproved:
467
+ node_type: tool
468
+ event_triggers: [PROCESS_APPROVED]
469
+ tool_name: finalize_approved
470
+ output_field: final_result
471
+ event_emissions:
472
+ - signal_name: COMPLETE
473
+
474
+ NotifyRejection:
475
+ node_type: tool
476
+ event_triggers: [PROCESS_REJECTED]
477
+ tool_name: notify_rejection
478
+ output_field: rejection_notice
479
+ event_emissions:
480
+ - signal_name: COMPLETE
481
+ ```
482
+
483
+ ### How It Works
484
+
485
+ 1. Workflow starts, processes available signals, then stops (no more matching triggers)
486
+ 2. Returns `execution_id` to the caller
487
+ 3. Later: external system (human, API, event) sends a signal using that ID
488
+ 4. Workflow resumes and continues processing
489
+
490
+ ```python
491
+ # Start the workflow
492
+ execution_id = orchestrate(
493
+ config=waiting_workflow,
494
+ initial_workflow_name="waiting_workflow",
495
+ initial_signals=["START"],
496
+ ...
497
+ )
498
+ # Execution stops after emitting WAITING_FOR_APPROVAL (no nodes listen for it yet)
499
+
500
+ # Later... external system sends approval
501
+ broadcast_signals(
502
+ execution_id=execution_id,
503
+ signals=["APPROVED"],
504
+ backends=backends,
505
+ )
506
+ # Workflow resumes and runs to completion
507
+ ```
508
+
509
+ **Key insight**: There's no "waiting state" or "paused" status. The execution is simply stopped. Anyone with the `execution_id` can send a signal to trigger it again—even the original workflow you created months ago.
510
+
511
+ ### Use Cases
512
+
513
+ - Human-in-the-loop approval
514
+ - External API callbacks (webhooks)
515
+ - Long-running processes that span days/weeks
516
+ - Event-driven architectures
517
+ - Triggering old executions with new data
518
+
519
+ ---
520
+
521
+ ## Versioning Strategies
522
+
523
+ SOE doesn't mandate a versioning strategy—it gives you the **primitives** to implement any strategy:
524
+
525
+ ### Strategy 1: Context-Based Routing
526
+
527
+ Route to different workflow versions based on context:
528
+
529
+ ```yaml
530
+ entry_workflow:
531
+ RouteByVersion:
532
+ node_type: router
533
+ event_triggers: [START]
534
+ event_emissions:
535
+ - signal_name: USE_V1
536
+ condition: "{{ context.api_version == 'v1' }}"
537
+ - signal_name: USE_V2
538
+ condition: "{{ context.api_version == 'v2' }}"
539
+ - signal_name: USE_LATEST
540
+ condition: "{{ context.api_version is not defined }}"
541
+
542
+ ExecuteV1:
543
+ node_type: child
544
+ event_triggers: [USE_V1]
545
+ child_workflow_name: processor_v1
546
+ child_initial_signals: [START]
547
+ input_fields: [request]
548
+ signals_to_parent: [V1_DONE]
549
+ context_updates_to_parent: [response]
550
+
551
+ ExecuteV2:
552
+ node_type: child
553
+ event_triggers: [USE_V2, USE_LATEST]
554
+ child_workflow_name: processor_v2
555
+ child_initial_signals: [START]
556
+ input_fields: [request]
557
+ signals_to_parent: [V2_DONE]
558
+ context_updates_to_parent: [response]
559
+
560
+ HandleV1Done:
561
+ node_type: router
562
+ event_triggers: [V1_DONE]
563
+ event_emissions:
564
+ - signal_name: COMPLETE
565
+
566
+ HandleV2Done:
567
+ node_type: router
568
+ event_triggers: [V2_DONE]
569
+ event_emissions:
570
+ - signal_name: COMPLETE
571
+
572
+ processor_v1:
573
+ ProcessOldWay:
574
+ node_type: llm
575
+ event_triggers: [START]
576
+ prompt: "Process (v1 legacy format): {{ context.request }}"
577
+ output_field: response
578
+ event_emissions:
579
+ - signal_name: V1_DONE
580
+
581
+ processor_v2:
582
+ ProcessNewWay:
583
+ node_type: llm
584
+ event_triggers: [START]
585
+ prompt: "Process with enhanced capabilities: {{ context.request }}"
586
+ output_field: response
587
+ event_emissions:
588
+ - signal_name: V2_DONE
589
+ ```
590
+
591
+ ### Strategy 2: Natural Versioning (No Migration)
592
+
593
+ Since execution state includes the workflow definition:
594
+
595
+ 1. **New version**: Just deploy new workflow YAML
596
+ 2. **Existing executions**: Continue with their captured version
597
+ 3. **New executions**: Use the new version
598
+
599
+ No migration needed—versions coexist naturally.
600
+
601
+ ### Strategy 3: Context Migration
602
+
603
+ For executions that need to switch to a new workflow version:
604
+
605
+ 1. **Read old context**: Get the execution's current state
606
+ 2. **Transform context**: Map old fields to new schema
607
+ 3. **Start new execution**: With the new workflow and migrated context
608
+ 4. **Mark old execution**: Complete or archive it
609
+
610
+ ```python
611
+ # Migrate an execution to a new workflow version
612
+ old_context = backends.context.get_context(old_execution_id)
613
+
614
+ migrated_context = migrate_context_v1_to_v2(old_context)
615
+
616
+ new_execution_id = orchestrate(
617
+ config=new_workflow_v2,
618
+ initial_workflow_name="main_workflow",
619
+ initial_signals=["RESUME"], # Custom signal for migrations
620
+ initial_context=migrated_context,
621
+ backends=backends,
622
+ broadcast_signals_caller=broadcast,
623
+ )
624
+ ```
625
+
626
+ ---
627
+
628
+ ## Execution IDs: The Key to Everything
629
+
630
+ Every orchestration returns an `execution_id`. This ID is:
631
+
632
+ - **Unique**: Identifies this specific execution
633
+ - **Persistent**: Stored in the context backend
634
+ - **The key**: Used to send signals, read context, continue execution
635
+
636
+ ### Sending Signals to Existing Executions
637
+
638
+ ```python
639
+ # External system sends a signal
640
+ broadcast_signals(
641
+ execution_id="abc-123-def",
642
+ signals=["USER_APPROVED"],
643
+ backends=backends,
644
+ )
645
+ ```
646
+
647
+ ### Reading Execution State
648
+
649
+ ```python
650
+ # Inspect an execution
651
+ context = backends.context.get_context("abc-123-def")
652
+ print(context["current_step"])
653
+ print(context["__operational__"]["active_signals"])
654
+ ```
655
+
656
+ ### Cross-Execution Communication
657
+
658
+ One execution can trigger signals in another:
659
+
660
+ ```python
661
+ # In a tool function
662
+ def notify_other_workflow(other_execution_id: str) -> dict:
663
+ broadcast_signals(
664
+ execution_id=other_execution_id,
665
+ signals=["EXTERNAL_EVENT"],
666
+ backends=global_backends,
667
+ )
668
+ return {"notified": True}
669
+ ```
670
+
671
+ ---
672
+
673
+ ## Key Takeaways
674
+
675
+ 1. **Workflows are data** — Store, version, and compose them freely
676
+ 2. **Executions are immutable** — Each run captures its workflow definition in the workflow backend
677
+ 3. **Multiple workflows compose** — Build ecosystems, not monoliths
678
+ 4. **Parallel is natural** — Fan-out to children for concurrent processing
679
+ 5. **Executions stop, not pause** — Send signals anytime to continue any execution
680
+ 6. **Execution IDs connect everything** — The key to cross-workflow communication
681
+
682
+ This ecosystem approach means you can:
683
+ - Deploy updates without breaking running processes
684
+ - Build sophisticated multi-workflow systems
685
+ - Handle long-running, event-driven processes
686
+ - Scale from simple scripts to enterprise orchestration
687
+
688
+ ## Next Steps
689
+
690
+ Now that you understand the workflows ecosystem, let's explore [Infrastructure](guide_10_infrastructure.md) for custom backends and production deployments →