soe-ai 0.1.1__py3-none-any.whl → 0.1.2__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.
- soe/builtin_tools/__init__.py +39 -0
- soe/builtin_tools/soe_add_signal.py +82 -0
- soe/builtin_tools/soe_call_tool.py +111 -0
- soe/builtin_tools/soe_copy_context.py +80 -0
- soe/builtin_tools/soe_explore_docs.py +290 -0
- soe/builtin_tools/soe_get_available_tools.py +42 -0
- soe/builtin_tools/soe_get_context.py +50 -0
- soe/builtin_tools/soe_get_workflows.py +63 -0
- soe/builtin_tools/soe_inject_node.py +86 -0
- soe/builtin_tools/soe_inject_workflow.py +105 -0
- soe/builtin_tools/soe_list_contexts.py +73 -0
- soe/builtin_tools/soe_remove_node.py +72 -0
- soe/builtin_tools/soe_remove_workflow.py +62 -0
- soe/builtin_tools/soe_update_context.py +54 -0
- soe/docs/_config.yml +10 -0
- soe/docs/advanced_patterns/guide_fanout_and_aggregations.md +318 -0
- soe/docs/advanced_patterns/guide_inheritance.md +435 -0
- soe/docs/advanced_patterns/hybrid_intelligence.md +237 -0
- soe/docs/advanced_patterns/index.md +49 -0
- soe/docs/advanced_patterns/operational.md +781 -0
- soe/docs/advanced_patterns/self_evolving_workflows.md +385 -0
- soe/docs/advanced_patterns/swarm_intelligence.md +211 -0
- soe/docs/builtins/context.md +164 -0
- soe/docs/builtins/explore_docs.md +135 -0
- soe/docs/builtins/tools.md +164 -0
- soe/docs/builtins/workflows.md +199 -0
- soe/docs/guide_00_getting_started.md +341 -0
- soe/docs/guide_01_tool.md +206 -0
- soe/docs/guide_02_llm.md +143 -0
- soe/docs/guide_03_router.md +146 -0
- soe/docs/guide_04_patterns.md +475 -0
- soe/docs/guide_05_agent.md +159 -0
- soe/docs/guide_06_schema.md +397 -0
- soe/docs/guide_07_identity.md +540 -0
- soe/docs/guide_08_child.md +612 -0
- soe/docs/guide_09_ecosystem.md +690 -0
- soe/docs/guide_10_infrastructure.md +427 -0
- soe/docs/guide_11_builtins.md +118 -0
- soe/docs/index.md +104 -0
- soe/docs/primitives/backends.md +281 -0
- soe/docs/primitives/context.md +256 -0
- soe/docs/primitives/node_reference.md +259 -0
- soe/docs/primitives/primitives.md +331 -0
- soe/docs/primitives/signals.md +865 -0
- soe/docs_index.py +1 -1
- soe/lib/__init__.py +0 -0
- soe/lib/child_context.py +46 -0
- soe/lib/context_fields.py +51 -0
- soe/lib/inheritance.py +172 -0
- soe/lib/jinja_render.py +113 -0
- soe/lib/operational.py +51 -0
- soe/lib/parent_sync.py +71 -0
- soe/lib/register_event.py +75 -0
- soe/lib/schema_validation.py +134 -0
- soe/lib/yaml_parser.py +14 -0
- soe/local_backends/__init__.py +18 -0
- soe/local_backends/factory.py +124 -0
- soe/local_backends/in_memory/context.py +38 -0
- soe/local_backends/in_memory/conversation_history.py +60 -0
- soe/local_backends/in_memory/identity.py +52 -0
- soe/local_backends/in_memory/schema.py +40 -0
- soe/local_backends/in_memory/telemetry.py +38 -0
- soe/local_backends/in_memory/workflow.py +33 -0
- soe/local_backends/storage/context.py +57 -0
- soe/local_backends/storage/conversation_history.py +82 -0
- soe/local_backends/storage/identity.py +118 -0
- soe/local_backends/storage/schema.py +96 -0
- soe/local_backends/storage/telemetry.py +72 -0
- soe/local_backends/storage/workflow.py +56 -0
- soe/nodes/__init__.py +13 -0
- soe/nodes/agent/__init__.py +10 -0
- soe/nodes/agent/factory.py +134 -0
- soe/nodes/agent/lib/loop_handlers.py +150 -0
- soe/nodes/agent/lib/loop_state.py +157 -0
- soe/nodes/agent/lib/prompts.py +65 -0
- soe/nodes/agent/lib/tools.py +35 -0
- soe/nodes/agent/stages/__init__.py +12 -0
- soe/nodes/agent/stages/parameter.py +37 -0
- soe/nodes/agent/stages/response.py +54 -0
- soe/nodes/agent/stages/router.py +37 -0
- soe/nodes/agent/state.py +111 -0
- soe/nodes/agent/types.py +66 -0
- soe/nodes/agent/validation/__init__.py +11 -0
- soe/nodes/agent/validation/config.py +95 -0
- soe/nodes/agent/validation/operational.py +24 -0
- soe/nodes/child/__init__.py +3 -0
- soe/nodes/child/factory.py +61 -0
- soe/nodes/child/state.py +59 -0
- soe/nodes/child/validation/__init__.py +11 -0
- soe/nodes/child/validation/config.py +126 -0
- soe/nodes/child/validation/operational.py +28 -0
- soe/nodes/lib/conditions.py +71 -0
- soe/nodes/lib/context.py +24 -0
- soe/nodes/lib/conversation_history.py +77 -0
- soe/nodes/lib/identity.py +64 -0
- soe/nodes/lib/llm_resolver.py +142 -0
- soe/nodes/lib/output.py +68 -0
- soe/nodes/lib/response_builder.py +91 -0
- soe/nodes/lib/signal_emission.py +79 -0
- soe/nodes/lib/signals.py +54 -0
- soe/nodes/lib/tools.py +100 -0
- soe/nodes/llm/__init__.py +7 -0
- soe/nodes/llm/factory.py +103 -0
- soe/nodes/llm/state.py +76 -0
- soe/nodes/llm/types.py +12 -0
- soe/nodes/llm/validation/__init__.py +11 -0
- soe/nodes/llm/validation/config.py +89 -0
- soe/nodes/llm/validation/operational.py +23 -0
- soe/nodes/router/__init__.py +3 -0
- soe/nodes/router/factory.py +37 -0
- soe/nodes/router/state.py +32 -0
- soe/nodes/router/validation/__init__.py +11 -0
- soe/nodes/router/validation/config.py +58 -0
- soe/nodes/router/validation/operational.py +16 -0
- soe/nodes/tool/factory.py +66 -0
- soe/nodes/tool/lib/__init__.py +11 -0
- soe/nodes/tool/lib/conditions.py +35 -0
- soe/nodes/tool/lib/failure.py +28 -0
- soe/nodes/tool/lib/parameters.py +67 -0
- soe/nodes/tool/state.py +66 -0
- soe/nodes/tool/types.py +27 -0
- soe/nodes/tool/validation/__init__.py +15 -0
- soe/nodes/tool/validation/config.py +132 -0
- soe/nodes/tool/validation/operational.py +16 -0
- soe/validation/__init__.py +18 -0
- soe/validation/config.py +195 -0
- soe/validation/jinja.py +54 -0
- soe/validation/operational.py +110 -0
- {soe_ai-0.1.1.dist-info → soe_ai-0.1.2.dist-info}/METADATA +4 -4
- soe_ai-0.1.2.dist-info/RECORD +137 -0
- {soe_ai-0.1.1.dist-info → soe_ai-0.1.2.dist-info}/WHEEL +1 -1
- soe_ai-0.1.1.dist-info/RECORD +0 -10
- {soe_ai-0.1.1.dist-info → soe_ai-0.1.2.dist-info}/licenses/LICENSE +0 -0
- {soe_ai-0.1.1.dist-info → soe_ai-0.1.2.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: {{ context.raw_data }}"
|
|
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: {{ context.validated_analysis }}, 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: {{ context.user_message }}"
|
|
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: {{ context.followup_message }}"
|
|
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: "{{ context.user_input is defined and context.user_input | length > 0 }}"
|
|
38
|
+
- signal_name: INPUT_INVALID
|
|
39
|
+
condition: "{{ context.user_input is not defined or context.user_input | length == 0 }}"
|
|
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: "{{ context.processed_result is defined and context.processed_result.valid == true }}"
|
|
56
|
+
- signal_name: OUTPUT_INVALID
|
|
57
|
+
condition: "{{ context.processed_result is not defined or context.processed_result.valid != true }}"
|
|
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: "{{ context.amount is defined and context.amount > 0 }}"
|
|
107
|
+
- signal_name: INPUT_INVALID
|
|
108
|
+
condition: "{{ context.amount is not defined or context.amount <= 0 }}"
|
|
109
|
+
|
|
110
|
+
GenerateResponse:
|
|
111
|
+
node_type: llm
|
|
112
|
+
event_triggers: [INPUT_VALID]
|
|
113
|
+
prompt: |
|
|
114
|
+
Generate a professional message about a transaction of ${{ context.amount }}.
|
|
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: "{{ context.generated_message is defined and context.generated_message | length < 500 }}"
|
|
126
|
+
- signal_name: OUTPUT_UNSAFE
|
|
127
|
+
condition: "{{ context.generated_message is not defined or context.generated_message | length >= 500 }}"
|
|
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: "{{ context.llm_output is mapping }}"
|
|
186
|
+
- signal_name: INVALID_JSON
|
|
187
|
+
condition: "{{ context.llm_output is not mapping }}"
|
|
188
|
+
|
|
189
|
+
IncrementRetry:
|
|
190
|
+
node_type: router
|
|
191
|
+
event_triggers: [INVALID_JSON]
|
|
192
|
+
event_emissions:
|
|
193
|
+
- signal_name: ATTEMPT
|
|
194
|
+
condition: "{{ context.retry_count | default(0) < 3 }}"
|
|
195
|
+
- signal_name: MAX_RETRIES
|
|
196
|
+
condition: "{{ context.retry_count | default(0) >= 3 }}"
|
|
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
|