soe-ai 0.1.1__py3-none-any.whl → 0.1.3__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 (134) hide show
  1. soe/builtin_tools/__init__.py +39 -0
  2. soe/builtin_tools/soe_add_signal.py +82 -0
  3. soe/builtin_tools/soe_call_tool.py +111 -0
  4. soe/builtin_tools/soe_copy_context.py +80 -0
  5. soe/builtin_tools/soe_explore_docs.py +290 -0
  6. soe/builtin_tools/soe_get_available_tools.py +42 -0
  7. soe/builtin_tools/soe_get_context.py +50 -0
  8. soe/builtin_tools/soe_get_workflows.py +63 -0
  9. soe/builtin_tools/soe_inject_node.py +86 -0
  10. soe/builtin_tools/soe_inject_workflow.py +105 -0
  11. soe/builtin_tools/soe_list_contexts.py +73 -0
  12. soe/builtin_tools/soe_remove_node.py +72 -0
  13. soe/builtin_tools/soe_remove_workflow.py +62 -0
  14. soe/builtin_tools/soe_update_context.py +54 -0
  15. soe/docs/_config.yml +10 -0
  16. soe/docs/advanced_patterns/guide_fanout_and_aggregations.md +318 -0
  17. soe/docs/advanced_patterns/guide_inheritance.md +435 -0
  18. soe/docs/advanced_patterns/hybrid_intelligence.md +237 -0
  19. soe/docs/advanced_patterns/index.md +49 -0
  20. soe/docs/advanced_patterns/operational.md +781 -0
  21. soe/docs/advanced_patterns/self_evolving_workflows.md +385 -0
  22. soe/docs/advanced_patterns/swarm_intelligence.md +211 -0
  23. soe/docs/builtins/context.md +164 -0
  24. soe/docs/builtins/explore_docs.md +135 -0
  25. soe/docs/builtins/tools.md +164 -0
  26. soe/docs/builtins/workflows.md +199 -0
  27. soe/docs/guide_00_getting_started.md +341 -0
  28. soe/docs/guide_01_tool.md +206 -0
  29. soe/docs/guide_02_llm.md +143 -0
  30. soe/docs/guide_03_router.md +146 -0
  31. soe/docs/guide_04_patterns.md +475 -0
  32. soe/docs/guide_05_agent.md +159 -0
  33. soe/docs/guide_06_schema.md +397 -0
  34. soe/docs/guide_07_identity.md +540 -0
  35. soe/docs/guide_08_child.md +612 -0
  36. soe/docs/guide_09_ecosystem.md +690 -0
  37. soe/docs/guide_10_infrastructure.md +427 -0
  38. soe/docs/guide_11_builtins.md +118 -0
  39. soe/docs/index.md +104 -0
  40. soe/docs/primitives/backends.md +281 -0
  41. soe/docs/primitives/context.md +256 -0
  42. soe/docs/primitives/node_reference.md +259 -0
  43. soe/docs/primitives/primitives.md +331 -0
  44. soe/docs/primitives/signals.md +865 -0
  45. soe/docs_index.py +1 -1
  46. soe/lib/__init__.py +0 -0
  47. soe/lib/child_context.py +46 -0
  48. soe/lib/context_fields.py +51 -0
  49. soe/lib/inheritance.py +172 -0
  50. soe/lib/jinja_render.py +113 -0
  51. soe/lib/operational.py +51 -0
  52. soe/lib/parent_sync.py +71 -0
  53. soe/lib/register_event.py +75 -0
  54. soe/lib/schema_validation.py +134 -0
  55. soe/lib/yaml_parser.py +14 -0
  56. soe/local_backends/__init__.py +18 -0
  57. soe/local_backends/factory.py +124 -0
  58. soe/local_backends/in_memory/context.py +38 -0
  59. soe/local_backends/in_memory/conversation_history.py +60 -0
  60. soe/local_backends/in_memory/identity.py +52 -0
  61. soe/local_backends/in_memory/schema.py +40 -0
  62. soe/local_backends/in_memory/telemetry.py +38 -0
  63. soe/local_backends/in_memory/workflow.py +33 -0
  64. soe/local_backends/storage/context.py +57 -0
  65. soe/local_backends/storage/conversation_history.py +82 -0
  66. soe/local_backends/storage/identity.py +118 -0
  67. soe/local_backends/storage/schema.py +96 -0
  68. soe/local_backends/storage/telemetry.py +72 -0
  69. soe/local_backends/storage/workflow.py +56 -0
  70. soe/nodes/__init__.py +13 -0
  71. soe/nodes/agent/__init__.py +10 -0
  72. soe/nodes/agent/factory.py +134 -0
  73. soe/nodes/agent/lib/loop_handlers.py +150 -0
  74. soe/nodes/agent/lib/loop_state.py +157 -0
  75. soe/nodes/agent/lib/prompts.py +65 -0
  76. soe/nodes/agent/lib/tools.py +35 -0
  77. soe/nodes/agent/stages/__init__.py +12 -0
  78. soe/nodes/agent/stages/parameter.py +37 -0
  79. soe/nodes/agent/stages/response.py +54 -0
  80. soe/nodes/agent/stages/router.py +37 -0
  81. soe/nodes/agent/state.py +111 -0
  82. soe/nodes/agent/types.py +66 -0
  83. soe/nodes/agent/validation/__init__.py +11 -0
  84. soe/nodes/agent/validation/config.py +95 -0
  85. soe/nodes/agent/validation/operational.py +24 -0
  86. soe/nodes/child/__init__.py +3 -0
  87. soe/nodes/child/factory.py +61 -0
  88. soe/nodes/child/state.py +59 -0
  89. soe/nodes/child/validation/__init__.py +11 -0
  90. soe/nodes/child/validation/config.py +126 -0
  91. soe/nodes/child/validation/operational.py +28 -0
  92. soe/nodes/lib/conditions.py +71 -0
  93. soe/nodes/lib/context.py +24 -0
  94. soe/nodes/lib/conversation_history.py +77 -0
  95. soe/nodes/lib/identity.py +64 -0
  96. soe/nodes/lib/llm_resolver.py +142 -0
  97. soe/nodes/lib/output.py +68 -0
  98. soe/nodes/lib/response_builder.py +91 -0
  99. soe/nodes/lib/signal_emission.py +79 -0
  100. soe/nodes/lib/signals.py +54 -0
  101. soe/nodes/lib/tools.py +100 -0
  102. soe/nodes/llm/__init__.py +7 -0
  103. soe/nodes/llm/factory.py +103 -0
  104. soe/nodes/llm/state.py +76 -0
  105. soe/nodes/llm/types.py +12 -0
  106. soe/nodes/llm/validation/__init__.py +11 -0
  107. soe/nodes/llm/validation/config.py +89 -0
  108. soe/nodes/llm/validation/operational.py +23 -0
  109. soe/nodes/router/__init__.py +3 -0
  110. soe/nodes/router/factory.py +37 -0
  111. soe/nodes/router/state.py +32 -0
  112. soe/nodes/router/validation/__init__.py +11 -0
  113. soe/nodes/router/validation/config.py +58 -0
  114. soe/nodes/router/validation/operational.py +16 -0
  115. soe/nodes/tool/factory.py +66 -0
  116. soe/nodes/tool/lib/__init__.py +11 -0
  117. soe/nodes/tool/lib/conditions.py +35 -0
  118. soe/nodes/tool/lib/failure.py +28 -0
  119. soe/nodes/tool/lib/parameters.py +67 -0
  120. soe/nodes/tool/state.py +66 -0
  121. soe/nodes/tool/types.py +27 -0
  122. soe/nodes/tool/validation/__init__.py +15 -0
  123. soe/nodes/tool/validation/config.py +132 -0
  124. soe/nodes/tool/validation/operational.py +16 -0
  125. soe/validation/__init__.py +18 -0
  126. soe/validation/config.py +195 -0
  127. soe/validation/jinja.py +54 -0
  128. soe/validation/operational.py +110 -0
  129. {soe_ai-0.1.1.dist-info → soe_ai-0.1.3.dist-info}/METADATA +5 -5
  130. soe_ai-0.1.3.dist-info/RECORD +137 -0
  131. {soe_ai-0.1.1.dist-info → soe_ai-0.1.3.dist-info}/WHEEL +1 -1
  132. soe_ai-0.1.1.dist-info/RECORD +0 -10
  133. {soe_ai-0.1.1.dist-info → soe_ai-0.1.3.dist-info}/licenses/LICENSE +0 -0
  134. {soe_ai-0.1.1.dist-info → soe_ai-0.1.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,435 @@
1
+
2
+ # Advanced Pattern: Configuration and Context Inheritance
3
+
4
+ This guide covers inheritance patterns for workflow chaining and multi-phase execution using SOE's `inherit_config_from_id` and `inherit_context_from_id` parameters.
5
+
6
+ ## The Hidden Power: Execution Chaining
7
+
8
+ Until now, each `orchestrate()` call required a full config. However, SOE supports **inheritance** between executions, enabling:
9
+
10
+ - **Workflow Chaining**: Run Phase 2 using Phase 1's config (workflows, identities, schema)
11
+ - **Context Sharing**: Continue with existing context data from a previous execution
12
+ - **Operational Reset**: Always start fresh operational counters even when inheriting context
13
+
14
+ This architecture enables powerful multi-phase patterns without re-sending configuration.
15
+
16
+ ---
17
+
18
+ ## The Two Inheritance Parameters
19
+
20
+ | Parameter | Inherits | Behavior |
21
+ |-----------|----------|----------|
22
+ | `inherit_config_from_id` | Workflows, identities, context_schema | Config becomes optional |
23
+ | `inherit_context_from_id` | Context fields (excludes `__operational__`) | **Always** resets operational state |
24
+
25
+ **Key Rule**: At least one of `config` or `inherit_config_from_id` must be provided.
26
+
27
+ ---
28
+
29
+ ## Pattern 1: Config Inheritance
30
+
31
+ Inherit workflows, identities, and context_schema from an existing execution.
32
+
33
+ ### The Workflow
34
+
35
+ ```yaml
36
+ workflows:
37
+ main_workflow:
38
+ ProcessData:
39
+ node_type: tool
40
+ event_triggers: [START]
41
+ tool_name: process
42
+ context_parameter_field: input_data
43
+ output_field: result
44
+ event_emissions:
45
+ - signal_name: PROCESSED
46
+
47
+ Complete:
48
+ node_type: router
49
+ event_triggers: [PROCESSED]
50
+ event_emissions:
51
+ - signal_name: COMPLETE
52
+
53
+ identities:
54
+ assistant: "You are a helpful assistant."
55
+ analyst: "You are a data analyst."
56
+
57
+ context_schema:
58
+ input_data:
59
+ type: object
60
+ description: "Input data to process"
61
+ result:
62
+ type: object
63
+ description: "Processing result"
64
+ ```
65
+
66
+ ### Usage: First Execution
67
+
68
+ ```python
69
+ # First execution - establishes config
70
+ first_id = orchestrate(
71
+ config=workflow_config,
72
+ initial_workflow_name="main_workflow",
73
+ initial_signals=["START"],
74
+ initial_context={"input_data": {"value": "test1"}},
75
+ backends=backends,
76
+ broadcast_signals_caller=broadcast,
77
+ )
78
+ ```
79
+
80
+ ### Usage: Inherited Execution
81
+
82
+ ```python
83
+ # Second execution - inherits config, no need to resend
84
+ second_id = orchestrate(
85
+ config=None, # Not needed!
86
+ initial_workflow_name="main_workflow",
87
+ initial_signals=["START"],
88
+ initial_context={"input_data": {"value": "test2"}},
89
+ backends=backends,
90
+ broadcast_signals_caller=broadcast,
91
+ inherit_config_from_id=first_id, # <-- Inherit from first
92
+ )
93
+ ```
94
+
95
+ **What gets inherited:**
96
+ - Workflows registry (all workflow definitions)
97
+ - Identities (LLM system prompts)
98
+ - Context schema (field definitions)
99
+
100
+ ---
101
+
102
+ ## Pattern 2: Context Inheritance
103
+
104
+ Inherit context from an existing execution, but **always** reset operational state.
105
+
106
+ ### The Workflow
107
+
108
+ ```yaml
109
+ workflows:
110
+ main_workflow:
111
+ Step1:
112
+ node_type: tool
113
+ event_triggers: [START]
114
+ tool_name: step1_tool
115
+ output_field: step1_result
116
+ event_emissions:
117
+ - signal_name: STEP1_DONE
118
+
119
+ Step2:
120
+ node_type: tool
121
+ event_triggers: [STEP1_DONE]
122
+ tool_name: step2_tool
123
+ context_parameter_field: step1_result
124
+ output_field: step2_result
125
+ event_emissions:
126
+ - signal_name: COMPLETE
127
+
128
+ retry_workflow:
129
+ RetryStep2:
130
+ node_type: tool
131
+ event_triggers: [START]
132
+ tool_name: step2_tool_v2
133
+ context_parameter_field: step1_result
134
+ output_field: step2_result
135
+ event_emissions:
136
+ - signal_name: RETRY_COMPLETE
137
+ ```
138
+
139
+ ### Usage: Retry with Existing Context
140
+
141
+ ```python
142
+ # First execution - builds up context
143
+ first_id = orchestrate(
144
+ config=workflow_config,
145
+ initial_workflow_name="main_workflow",
146
+ initial_signals=["START"],
147
+ initial_context={},
148
+ backends=backends,
149
+ broadcast_signals_caller=broadcast,
150
+ )
151
+
152
+ # Second execution - inherits context, runs retry workflow
153
+ second_id = orchestrate(
154
+ config=None,
155
+ initial_workflow_name="retry_workflow",
156
+ initial_signals=["START"],
157
+ initial_context={},
158
+ backends=backends,
159
+ broadcast_signals_caller=broadcast,
160
+ inherit_config_from_id=first_id,
161
+ inherit_context_from_id=first_id, # <-- Inherit context too
162
+ )
163
+ ```
164
+
165
+ **Critical**: `inherit_context_from_id` **always** resets `__operational__`:
166
+ - Fresh signal list
167
+ - Fresh counters (llm_calls, tool_calls, errors)
168
+ - New main_execution_id
169
+
170
+ This ensures each execution has its own operational tracking.
171
+
172
+ ---
173
+
174
+ ## Pattern 3: Multi-Phase Execution
175
+
176
+ Combine both inheritance types for complex, multi-phase workflows.
177
+
178
+ ### The Workflow
179
+
180
+ ```yaml
181
+ workflows:
182
+ phase1_workflow:
183
+ Analyze:
184
+ node_type: llm
185
+ event_triggers: [START]
186
+ identity: analyst
187
+ prompt: "Analyze this data: &#123;&#123; context.raw_data &#125;&#125;"
188
+ output_field: analysis
189
+ event_emissions:
190
+ - signal_name: ANALYSIS_DONE
191
+
192
+ Validate:
193
+ node_type: tool
194
+ event_triggers: [ANALYSIS_DONE]
195
+ tool_name: validate_analysis
196
+ context_parameter_field: analysis
197
+ output_field: validated_analysis
198
+ event_emissions:
199
+ - signal_name: PHASE1_COMPLETE
200
+
201
+ phase2_workflow:
202
+ Generate:
203
+ node_type: llm
204
+ event_triggers: [START]
205
+ identity: writer
206
+ prompt: "Based on analysis: &#123;&#123; context.validated_analysis &#125;&#125;, generate report"
207
+ output_field: report
208
+ event_emissions:
209
+ - signal_name: REPORT_DONE
210
+
211
+ Finalize:
212
+ node_type: router
213
+ event_triggers: [REPORT_DONE]
214
+ event_emissions:
215
+ - signal_name: PHASE2_COMPLETE
216
+
217
+ identities:
218
+ analyst: "You are a data analyst. Focus on insights and patterns."
219
+ writer: "You are a technical writer. Be clear and professional."
220
+
221
+ context_schema:
222
+ raw_data:
223
+ type: string
224
+ description: "Raw data to analyze"
225
+ analysis:
226
+ type: object
227
+ description: "Analysis results"
228
+ validated_analysis:
229
+ type: object
230
+ description: "Validated analysis"
231
+ report:
232
+ type: string
233
+ description: "Generated report"
234
+ ```
235
+
236
+ ### Usage: Phase 1 → Phase 2
237
+
238
+ ```python
239
+ # Phase 1 - Analysis
240
+ phase1_id = orchestrate(
241
+ config=multi_phase_config,
242
+ initial_workflow_name="phase1_workflow",
243
+ initial_signals=["START"],
244
+ initial_context={"raw_data": "sample data"},
245
+ backends=backends,
246
+ broadcast_signals_caller=broadcast,
247
+ )
248
+
249
+ # Phase 2 - Generation (inherits everything)
250
+ phase2_id = orchestrate(
251
+ config=None,
252
+ initial_workflow_name="phase2_workflow",
253
+ initial_signals=["START"],
254
+ initial_context={}, # Uses Phase 1's context
255
+ backends=backends,
256
+ broadcast_signals_caller=broadcast,
257
+ inherit_config_from_id=phase1_id,
258
+ inherit_context_from_id=phase1_id,
259
+ )
260
+ ```
261
+
262
+ **Benefits:**
263
+ - Phase 2 has access to Phase 1's `validated_analysis`
264
+ - Phase 2 uses same identities (`analyst`, `writer`)
265
+ - Phase 2 has fresh operational state for clean tracking
266
+
267
+ ---
268
+
269
+ ## Pattern 4: Identity Sharing Across Conversations
270
+
271
+ Inherit config to share identities across multiple conversation executions.
272
+
273
+ ### The Workflow
274
+
275
+ ```yaml
276
+ workflows:
277
+ conversation_workflow:
278
+ FirstMessage:
279
+ node_type: llm
280
+ event_triggers: [START]
281
+ identity: assistant
282
+ prompt: "User says: &#123;&#123; context.user_message &#125;&#125;"
283
+ output_field: assistant_response
284
+ event_emissions:
285
+ - signal_name: RESPONDED
286
+
287
+ Complete:
288
+ node_type: router
289
+ event_triggers: [RESPONDED]
290
+ event_emissions:
291
+ - signal_name: CONVERSATION_DONE
292
+
293
+ followup_workflow:
294
+ FollowUp:
295
+ node_type: llm
296
+ event_triggers: [START]
297
+ identity: assistant
298
+ prompt: "User follows up: &#123;&#123; context.followup_message &#125;&#125;"
299
+ output_field: followup_response
300
+ event_emissions:
301
+ - signal_name: FOLLOWUP_DONE
302
+
303
+ identities:
304
+ assistant: "You are a helpful assistant. Be concise and friendly."
305
+ ```
306
+
307
+ ### Usage: Continued Conversation
308
+
309
+ ```python
310
+ # First conversation
311
+ first_id = orchestrate(
312
+ config=conversation_config,
313
+ initial_workflow_name="conversation_workflow",
314
+ initial_signals=["START"],
315
+ initial_context={"user_message": "Hello"},
316
+ backends=backends,
317
+ broadcast_signals_caller=broadcast,
318
+ )
319
+
320
+ # Follow-up conversation (same identity definitions)
321
+ second_id = orchestrate(
322
+ config=None,
323
+ initial_workflow_name="followup_workflow",
324
+ initial_signals=["START"],
325
+ initial_context={"followup_message": "Tell me more"},
326
+ backends=backends,
327
+ broadcast_signals_caller=broadcast,
328
+ inherit_config_from_id=first_id,
329
+ )
330
+ ```
331
+
332
+ ---
333
+
334
+ ## Override Behavior
335
+
336
+ ### Config Override
337
+
338
+ If both `config` and `inherit_config_from_id` are provided, `config` takes precedence:
339
+
340
+ ```python
341
+ # Inherits, then overrides with new config
342
+ orchestrate(
343
+ config=new_config, # <-- This wins
344
+ initial_workflow_name="workflow",
345
+ initial_signals=["START"],
346
+ initial_context={},
347
+ backends=backends,
348
+ broadcast_signals_caller=broadcast,
349
+ inherit_config_from_id=old_id, # <-- Loaded first, then overridden
350
+ )
351
+ ```
352
+
353
+ ### Context Append (History Preservation)
354
+
355
+ `initial_context` fields are **appended** to inherited context history using `set_field()`. This preserves the full history chain:
356
+
357
+ ```python
358
+ # Inherits context, appends new values to history
359
+ orchestrate(
360
+ config=None,
361
+ initial_workflow_name="retry_workflow",
362
+ initial_signals=["START"],
363
+ initial_context={"user_input": "new message"}, # <-- Appends to history
364
+ backends=backends,
365
+ broadcast_signals_caller=broadcast,
366
+ inherit_config_from_id=first_id,
367
+ inherit_context_from_id=first_id,
368
+ )
369
+
370
+ # Result: context["user_input"] = ["old message", "new message"]
371
+ # Reading with get_field() returns "new message" (latest)
372
+ # Reading with get_accumulated() returns ["old message", "new message"] (full history)
373
+ ```
374
+
375
+ ---
376
+
377
+ ## Validation Rules
378
+
379
+ | Scenario | Result |
380
+ |----------|--------|
381
+ | `config=None`, `inherit_config_from_id=None` | **Error**: Must provide one |
382
+ | `inherit_config_from_id="nonexistent"` | **Error**: Execution not found |
383
+ | `inherit_context_from_id="nonexistent"` | **Error**: Context not found |
384
+ | `inherit_context_from_id` without `inherit_config_from_id` | Valid if `config` provided |
385
+
386
+ ---
387
+
388
+ ## When to Use Each Pattern
389
+
390
+ | Use Case | Pattern |
391
+ |----------|---------|
392
+ | Run same workflow with different inputs | Config inheritance only |
393
+ | Retry failed step with existing context | Config + context inheritance |
394
+ | Multi-phase pipelines | Config + context inheritance |
395
+ | Share identities across API calls | Config inheritance only |
396
+ | Fresh start with same config | Config inheritance only (no context) |
397
+ | Continue with old version of workflow | Config inheritance from old execution |
398
+ | Continue with evolved workflow | Config inheritance from evolved execution |
399
+
400
+ ### Version Management with Inheritance
401
+
402
+ Inheritance enables interesting version management patterns:
403
+
404
+ 1. **Continue with Old Version**: If you have a running process and update your workflow definition, existing executions continue with their captured config. New executions use the new definition.
405
+
406
+ 2. **Continue with Evolved Version**: If a workflow self-evolves (using `soe_inject_workflow` or `inject_node`), you can start new executions that inherit the evolved config:
407
+
408
+ ```python
409
+ # Original execution - workflow may have self-evolved
410
+ evolved_id = orchestrate(
411
+ config=original_config,
412
+ initial_workflow_name="self_evolving_workflow",
413
+ initial_signals=["START"],
414
+ ...
415
+ )
416
+
417
+ # New execution inherits the evolved config (including injected nodes)
418
+ new_execution = orchestrate(
419
+ config=None,
420
+ initial_workflow_name="self_evolving_workflow",
421
+ initial_signals=["START"],
422
+ inherit_config_from_id=evolved_id, # Gets evolved workflow
423
+ ...
424
+ )
425
+ ```
426
+
427
+ ---
428
+
429
+ ## Best Practices
430
+
431
+ 1. **Always reset operational for retries**: Use `inherit_context_from_id` to get fresh counters
432
+ 2. **Store execution IDs**: Keep track of execution IDs for chaining
433
+ 3. **Explicit is better**: When in doubt, provide full config
434
+ 4. **Test inheritance chains**: Multi-phase tests catch subtle bugs
435
+ 5. **Don't assume context shape**: Validate inherited context before use
@@ -0,0 +1,237 @@
1
+ # SOE Advanced Patterns: Hybrid Intelligence
2
+
3
+ ## Introduction
4
+
5
+ The power of SOE comes from mixing **deterministic code** with **probabilistic AI**. Routers are 100% reliable. LLMs are creative. Combine them for systems that are both flexible and safe.
6
+
7
+ This is the "Centaur" model — human-like intelligence bounded by machine precision.
8
+
9
+ ### Why Hybrid Intelligence Matters
10
+
11
+ Hybrid patterns are one of SOE's biggest selling points:
12
+
13
+ 1. **Low-Latency User Endpoints**: Fast, deterministic routers handle user requests immediately
14
+ 2. **Async AI Processing**: LLM-driven workflows run in the background for complex analysis
15
+ 3. **Self-Evolving Context**: Deterministic workflows can be enhanced by AI that modifies context
16
+
17
+ This enables architectures where:
18
+ - Users get instant responses from deterministic logic
19
+ - AI enriches/improves the system asynchronously
20
+ - Complex decisions combine both in a single workflow
21
+
22
+ ---
23
+
24
+ ## Pattern 1: Safety Rails
25
+
26
+ Validate inputs and outputs around AI processing.
27
+
28
+ ### The Workflow
29
+
30
+ ```yaml
31
+ hybrid_workflow:
32
+ ValidateInput:
33
+ node_type: router
34
+ event_triggers: [START]
35
+ event_emissions:
36
+ - signal_name: INPUT_VALID
37
+ condition: "&#123;&#123; context.user_input is defined and context.user_input | length > 0 &#125;&#125;"
38
+ - signal_name: INPUT_INVALID
39
+ condition: "&#123;&#123; context.user_input is not defined or context.user_input | length == 0 &#125;&#125;"
40
+
41
+ ProcessInput:
42
+ node_type: tool
43
+ event_triggers: [INPUT_VALID]
44
+ tool_name: process_input
45
+ context_parameter_field: process_params
46
+ output_field: processed_result
47
+ event_emissions:
48
+ - signal_name: PROCESSED
49
+
50
+ ValidateOutput:
51
+ node_type: router
52
+ event_triggers: [PROCESSED]
53
+ event_emissions:
54
+ - signal_name: OUTPUT_VALID
55
+ condition: "&#123;&#123; context.processed_result is defined and context.processed_result.valid == true &#125;&#125;"
56
+ - signal_name: OUTPUT_INVALID
57
+ condition: "&#123;&#123; context.processed_result is not defined or context.processed_result.valid != true &#125;&#125;"
58
+
59
+ Complete:
60
+ node_type: router
61
+ event_triggers: [OUTPUT_VALID]
62
+ event_emissions:
63
+ - signal_name: DONE
64
+
65
+ HandleError:
66
+ node_type: router
67
+ event_triggers: [INPUT_INVALID, OUTPUT_INVALID, PROCESS_FAILED]
68
+ event_emissions:
69
+ - signal_name: ERROR
70
+ ```
71
+
72
+ ### How It Works
73
+
74
+ 1. **ValidateInput** (Router) — Checks input meets requirements
75
+ 2. **ProcessInput** (Tool/LLM) — Does the actual work
76
+ 3. **ValidateOutput** (Router) — Ensures output meets requirements
77
+ 4. **HandleError** (Router) — Catches any failures
78
+
79
+ **The AI never runs with bad input. The AI output never escapes validation.**
80
+
81
+ ### The Flow
82
+
83
+ ```
84
+ START
85
+ ├─ INPUT_VALID ──→ ProcessInput ──→ PROCESSED
86
+ │ ├─ OUTPUT_VALID ──→ DONE
87
+ │ └─ OUTPUT_INVALID ──→ ERROR
88
+ └─ INPUT_INVALID ──→ ERROR
89
+ ```
90
+
91
+ ---
92
+
93
+ ## Pattern 2: LLM with Safety Rails
94
+
95
+ Same pattern but with an LLM doing the processing.
96
+
97
+ ### The Workflow
98
+
99
+ ```yaml
100
+ safety_workflow:
101
+ ValidateInput:
102
+ node_type: router
103
+ event_triggers: [START]
104
+ event_emissions:
105
+ - signal_name: INPUT_VALID
106
+ condition: "&#123;&#123; context.amount is defined and context.amount > 0 &#125;&#125;"
107
+ - signal_name: INPUT_INVALID
108
+ condition: "&#123;&#123; context.amount is not defined or context.amount <= 0 &#125;&#125;"
109
+
110
+ GenerateResponse:
111
+ node_type: llm
112
+ event_triggers: [INPUT_VALID]
113
+ prompt: |
114
+ Generate a professional message about a transaction of $&#123;&#123; context.amount &#125;&#125;.
115
+ Keep it brief and professional.
116
+ output_field: generated_message
117
+ event_emissions:
118
+ - signal_name: MESSAGE_GENERATED
119
+
120
+ ValidateOutput:
121
+ node_type: router
122
+ event_triggers: [MESSAGE_GENERATED]
123
+ event_emissions:
124
+ - signal_name: OUTPUT_SAFE
125
+ condition: "&#123;&#123; context.generated_message is defined and context.generated_message | length < 500 &#125;&#125;"
126
+ - signal_name: OUTPUT_UNSAFE
127
+ condition: "&#123;&#123; context.generated_message is not defined or context.generated_message | length >= 500 &#125;&#125;"
128
+
129
+ HandleInputError:
130
+ node_type: router
131
+ event_triggers: [INPUT_INVALID]
132
+ event_emissions:
133
+ - signal_name: ERROR
134
+
135
+ Complete:
136
+ node_type: router
137
+ event_triggers: [OUTPUT_SAFE]
138
+ event_emissions:
139
+ - signal_name: DONE
140
+
141
+ HandleOutputError:
142
+ node_type: router
143
+ event_triggers: [OUTPUT_UNSAFE]
144
+ event_emissions:
145
+ - signal_name: ERROR
146
+ ```
147
+
148
+ ### How It Works
149
+
150
+ 1. Router validates `amount > 0` (deterministic)
151
+ 2. LLM generates professional message (creative)
152
+ 3. Router validates output length < 500 (deterministic)
153
+
154
+ **Enterprise use case**: Financial messages that must be professional but also compliant.
155
+
156
+ ---
157
+
158
+ ## Pattern 3: Retry with Validation
159
+
160
+ LLM generates output, router validates, retry if invalid.
161
+
162
+ ### The Workflow
163
+
164
+ ```yaml
165
+ retry_workflow:
166
+ Start:
167
+ node_type: router
168
+ event_triggers: [START]
169
+ event_emissions:
170
+ - signal_name: ATTEMPT
171
+
172
+ Generate:
173
+ node_type: llm
174
+ event_triggers: [ATTEMPT]
175
+ prompt: "Generate a valid JSON object with fields: name, age"
176
+ output_field: llm_output
177
+ event_emissions:
178
+ - signal_name: GENERATED
179
+
180
+ Validate:
181
+ node_type: router
182
+ event_triggers: [GENERATED]
183
+ event_emissions:
184
+ - signal_name: VALID_JSON
185
+ condition: "&#123;&#123; context.llm_output is mapping &#125;&#125;"
186
+ - signal_name: INVALID_JSON
187
+ condition: "&#123;&#123; context.llm_output is not mapping &#125;&#125;"
188
+
189
+ IncrementRetry:
190
+ node_type: router
191
+ event_triggers: [INVALID_JSON]
192
+ event_emissions:
193
+ - signal_name: ATTEMPT
194
+ condition: "&#123;&#123; context.retry_count | default(0) < 3 &#125;&#125;"
195
+ - signal_name: MAX_RETRIES
196
+ condition: "&#123;&#123; context.retry_count | default(0) >= 3 &#125;&#125;"
197
+
198
+ Complete:
199
+ node_type: router
200
+ event_triggers: [VALID_JSON]
201
+ event_emissions:
202
+ - signal_name: DONE
203
+ ```
204
+
205
+ ### How It Works
206
+
207
+ 1. LLM generates JSON
208
+ 2. Router checks if output is valid mapping
209
+ 3. If invalid, increment retry counter and loop back
210
+ 4. If max retries exceeded, emit `MAX_RETRIES`
211
+
212
+ **Key Insight**: The retry logic is deterministic. Only the generation is probabilistic. You control the boundaries.
213
+
214
+ ---
215
+
216
+ ## Why Hybrid Matters
217
+
218
+ | Pure LLM | Pure Code | Hybrid (SOE) |
219
+ |----------|-----------|--------------|
220
+ | Creative but unreliable | Reliable but rigid | Creative AND reliable |
221
+ | "Hope it follows rules" | "Can't adapt" | "Enforce rules, allow creativity" |
222
+
223
+ ---
224
+
225
+ ## Best Practices
226
+
227
+ 1. **Validate early** — Check inputs before any AI processing
228
+ 2. **Validate late** — Check outputs before any downstream effects
229
+ 3. **Bound retries** — Never let loops run forever
230
+ 4. **Log everything** — Use telemetry backend to track decisions
231
+
232
+ ---
233
+
234
+ ## Related Patterns
235
+
236
+ - [Swarm Intelligence](swarm_intelligence.md) — Multiple agents coordinating
237
+ - [Self-Evolving Workflows](self_evolving_workflows.md) — Agents that modify their own workflows