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.
- soe/__init__.py +50 -0
- soe/broker.py +168 -0
- soe/builtin_tools/__init__.py +51 -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_context_schema.py +56 -0
- soe/builtin_tools/soe_get_identities.py +63 -0
- soe/builtin_tools/soe_get_workflows.py +63 -0
- soe/builtin_tools/soe_inject_context_schema_field.py +80 -0
- soe/builtin_tools/soe_inject_identity.py +64 -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_context_schema_field.py +61 -0
- soe/builtin_tools/soe_remove_identity.py +61 -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/context_schema.md +158 -0
- soe/docs/builtins/identity.md +139 -0
- soe/docs/builtins/soe_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 +126 -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 +2 -0
- soe/init.py +165 -0
- 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/types.py +209 -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.2.0b1.dist-info/METADATA +262 -0
- soe_ai-0.2.0b1.dist-info/RECORD +145 -0
- soe_ai-0.2.0b1.dist-info/WHEEL +5 -0
- soe_ai-0.2.0b1.dist-info/licenses/LICENSE +21 -0
- soe_ai-0.2.0b1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
# SOE Advanced Patterns: Self-Evolving Workflows
|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
|
|
5
|
+
One of SOE's most powerful capabilities is **runtime workflow modification**. Because workflows are YAML text interpreted at runtime, they can be modified *during execution*.
|
|
6
|
+
|
|
7
|
+
This enables:
|
|
8
|
+
- **Deterministic injection** — Add predefined workflows/nodes based on conditions
|
|
9
|
+
- **LLM-driven generation** — Let an AI architect new workflow components
|
|
10
|
+
- **Self-improving systems** — Workflows that adapt based on performance
|
|
11
|
+
- **Self-awareness** — Workflows that understand their own capabilities via `soe_explore_docs`
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Self-Awareness with soe_explore_docs
|
|
16
|
+
|
|
17
|
+
Before a workflow can evolve intelligently, it needs to understand what's possible. The `soe_explore_docs` built-in enables **self-awareness**:
|
|
18
|
+
|
|
19
|
+
```yaml
|
|
20
|
+
SelfAwareAgent:
|
|
21
|
+
node_type: agent
|
|
22
|
+
event_triggers: [NEED_CAPABILITY]
|
|
23
|
+
prompt: |
|
|
24
|
+
The user needs: {{ context.user_request }}
|
|
25
|
+
|
|
26
|
+
First, explore the documentation to understand what SOE can do.
|
|
27
|
+
Then decide if we need to create a new workflow or modify existing ones.
|
|
28
|
+
tools: [soe_explore_docs, soe_inject_workflow, soe_inject_node]
|
|
29
|
+
output_field: evolution_result
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Why this matters**:
|
|
33
|
+
- Reduces the need for extensive prompting about SOE capabilities
|
|
34
|
+
- Agent can discover what node types, builtins, and patterns are available
|
|
35
|
+
- Works especially well with agent nodes that can iteratively explore
|
|
36
|
+
|
|
37
|
+
See [Built-in Tools: soe_explore_docs](../builtins/soe_explore_docs.md) for details.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Built-in Injection Tools
|
|
42
|
+
|
|
43
|
+
SOE provides built-in tools for runtime modification:
|
|
44
|
+
|
|
45
|
+
| Tool | Purpose |
|
|
46
|
+
|------|---------|
|
|
47
|
+
| `soe_inject_workflow` | Add a complete new workflow to the registry |
|
|
48
|
+
| `soe_inject_node` | Add a node to an existing workflow |
|
|
49
|
+
| `soe_remove_workflow` | Remove a workflow from the registry |
|
|
50
|
+
| `soe_remove_node` | Remove a node from a workflow |
|
|
51
|
+
| `soe_get_workflows` | Query current workflow definitions |
|
|
52
|
+
| `call_tool` | Dynamically call any registered tool by name |
|
|
53
|
+
|
|
54
|
+
The `call_tool` builtin is particularly powerful for self-evolution: you can create a new tool and immediately have a node call it without modifying the workflow structure.
|
|
55
|
+
|
|
56
|
+
Both injection tools are created with factory functions that bind them to the current execution:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from soe.builtin_tools.soe_inject_workflow import create_soe_inject_workflow_tool
|
|
60
|
+
from soe.builtin_tools.soe_inject_node import create_soe_inject_node_tool
|
|
61
|
+
|
|
62
|
+
# Create tools bound to the current execution
|
|
63
|
+
soe_inject_workflow = create_soe_inject_workflow_tool(execution_id, backends)
|
|
64
|
+
inject_node = create_soe_inject_node_tool(execution_id, backends)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Pattern 1: Deterministic Workflow Injection
|
|
70
|
+
|
|
71
|
+
Inject a predefined workflow based on runtime conditions.
|
|
72
|
+
|
|
73
|
+
### The Workflow
|
|
74
|
+
|
|
75
|
+
```yaml
|
|
76
|
+
example_workflow:
|
|
77
|
+
Start:
|
|
78
|
+
node_type: router
|
|
79
|
+
event_triggers: [START]
|
|
80
|
+
event_emissions:
|
|
81
|
+
- signal_name: soe_inject_workflow
|
|
82
|
+
|
|
83
|
+
InjectNewWorkflow:
|
|
84
|
+
node_type: tool
|
|
85
|
+
event_triggers: [soe_inject_workflow]
|
|
86
|
+
tool_name: soe_inject_workflow
|
|
87
|
+
context_parameter_field: inject_params
|
|
88
|
+
output_field: injection_result
|
|
89
|
+
event_emissions:
|
|
90
|
+
- signal_name: WORKFLOW_INJECTED
|
|
91
|
+
|
|
92
|
+
Complete:
|
|
93
|
+
node_type: router
|
|
94
|
+
event_triggers: [WORKFLOW_INJECTED]
|
|
95
|
+
event_emissions:
|
|
96
|
+
- signal_name: DONE
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### How It Works
|
|
100
|
+
|
|
101
|
+
1. **Start** — Router triggers `soe_inject_workflow`
|
|
102
|
+
2. **InjectNewWorkflow** — Tool node calls `soe_inject_workflow` with parameters from `inject_params` in context
|
|
103
|
+
3. **Complete** — Confirms injection and emits `DONE`
|
|
104
|
+
|
|
105
|
+
The `inject_params` context field must contain:
|
|
106
|
+
- `workflow_name`: Name for the new workflow
|
|
107
|
+
- `workflow_data`: YAML or JSON string with the workflow definition
|
|
108
|
+
|
|
109
|
+
### The Injected Workflow
|
|
110
|
+
|
|
111
|
+
The `workflow_data` in context contains the new workflow:
|
|
112
|
+
|
|
113
|
+
```yaml
|
|
114
|
+
InjectedWorkflow:
|
|
115
|
+
ProcessData:
|
|
116
|
+
node_type: router
|
|
117
|
+
event_triggers: [START]
|
|
118
|
+
event_emissions:
|
|
119
|
+
- signal_name: INJECTED_COMPLETE
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
After injection, this workflow can be triggered by a child node or by switching the current workflow.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Pattern 2: Deterministic Node Injection
|
|
127
|
+
|
|
128
|
+
Add a node to an existing workflow at runtime.
|
|
129
|
+
|
|
130
|
+
### The Workflow
|
|
131
|
+
|
|
132
|
+
```yaml
|
|
133
|
+
example_workflow:
|
|
134
|
+
Start:
|
|
135
|
+
node_type: router
|
|
136
|
+
event_triggers: [START]
|
|
137
|
+
event_emissions:
|
|
138
|
+
- signal_name: INJECT_NODE
|
|
139
|
+
|
|
140
|
+
InjectNewNode:
|
|
141
|
+
node_type: tool
|
|
142
|
+
event_triggers: [INJECT_NODE]
|
|
143
|
+
tool_name: soe_inject_node
|
|
144
|
+
context_parameter_field: inject_params
|
|
145
|
+
output_field: injection_result
|
|
146
|
+
event_emissions:
|
|
147
|
+
- signal_name: NODE_INJECTED
|
|
148
|
+
|
|
149
|
+
Complete:
|
|
150
|
+
node_type: router
|
|
151
|
+
event_triggers: [NODE_INJECTED]
|
|
152
|
+
event_emissions:
|
|
153
|
+
- signal_name: DONE
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### How It Works
|
|
157
|
+
|
|
158
|
+
1. **Start** — Router triggers `INJECT_NODE`
|
|
159
|
+
2. **InjectNewNode** — Tool node calls `soe_inject_node` with parameters from `inject_params`:
|
|
160
|
+
- `workflow_name`: Which workflow to modify
|
|
161
|
+
- `node_name`: Name for the new node
|
|
162
|
+
- `node_config_data`: The node configuration (YAML or JSON)
|
|
163
|
+
3. **Complete** — Confirms injection
|
|
164
|
+
|
|
165
|
+
### The Injected Node
|
|
166
|
+
|
|
167
|
+
```yaml
|
|
168
|
+
node_type: router
|
|
169
|
+
event_triggers: [TRIGGER_NEW_NODE]
|
|
170
|
+
event_emissions:
|
|
171
|
+
- signal_name: NEW_NODE_COMPLETE
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Pattern 3: LLM-Driven Workflow Generation
|
|
177
|
+
|
|
178
|
+
The most powerful pattern: let an LLM generate workflow components.
|
|
179
|
+
|
|
180
|
+
### The Workflow
|
|
181
|
+
|
|
182
|
+
```yaml
|
|
183
|
+
example_workflow:
|
|
184
|
+
Start:
|
|
185
|
+
node_type: router
|
|
186
|
+
event_triggers: [START]
|
|
187
|
+
event_emissions:
|
|
188
|
+
- signal_name: GENERATE_WORKFLOW
|
|
189
|
+
|
|
190
|
+
GenerateWorkflow:
|
|
191
|
+
node_type: llm
|
|
192
|
+
event_triggers: [GENERATE_WORKFLOW]
|
|
193
|
+
prompt: |
|
|
194
|
+
You are a workflow architect. Generate a simple SOE workflow.
|
|
195
|
+
|
|
196
|
+
The workflow should be named "{{ context.workflow_name }}" and have:
|
|
197
|
+
- A router node that triggers on START
|
|
198
|
+
- Emit signal: GENERATED_COMPLETE
|
|
199
|
+
|
|
200
|
+
Provide the workflow_name and the workflow_data (as YAML string).
|
|
201
|
+
schema_name: Generated_WorkflowResponse
|
|
202
|
+
output_field: inject_params
|
|
203
|
+
event_emissions:
|
|
204
|
+
- signal_name: WORKFLOW_GENERATED
|
|
205
|
+
|
|
206
|
+
InjectGeneratedWorkflow:
|
|
207
|
+
node_type: tool
|
|
208
|
+
event_triggers: [WORKFLOW_GENERATED]
|
|
209
|
+
tool_name: soe_inject_workflow
|
|
210
|
+
context_parameter_field: inject_params
|
|
211
|
+
output_field: injection_result
|
|
212
|
+
event_emissions:
|
|
213
|
+
- signal_name: INJECTION_COMPLETE
|
|
214
|
+
|
|
215
|
+
Complete:
|
|
216
|
+
node_type: router
|
|
217
|
+
event_triggers: [INJECTION_COMPLETE]
|
|
218
|
+
event_emissions:
|
|
219
|
+
- signal_name: DONE
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### How It Works
|
|
223
|
+
|
|
224
|
+
1. **GenerateWorkflow** — LLM node receives a prompt describing what to generate
|
|
225
|
+
2. **LLM Response** — Returns valid YAML for a new workflow, stored in `generated_workflow`
|
|
226
|
+
3. **PrepareInjection** — Router node assembles `inject_params` from the LLM output
|
|
227
|
+
4. **InjectGeneratedWorkflow** — Tool node injects the workflow using `inject_params`
|
|
228
|
+
5. **Complete** — The new workflow is now available
|
|
229
|
+
|
|
230
|
+
### The Prompt
|
|
231
|
+
|
|
232
|
+
The prompt instructs the LLM to generate valid SOE YAML:
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
You are a workflow architect. Generate a simple SOE workflow in YAML format.
|
|
236
|
+
|
|
237
|
+
The workflow should:
|
|
238
|
+
- Be named "{{ context.workflow_name }}"
|
|
239
|
+
- Have a router node that triggers on START
|
|
240
|
+
- Update context with: generated=true
|
|
241
|
+
- Emit signal: GENERATED_COMPLETE
|
|
242
|
+
|
|
243
|
+
Return ONLY valid YAML, no explanation.
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Safety Considerations
|
|
247
|
+
|
|
248
|
+
When using LLM-generated workflows:
|
|
249
|
+
|
|
250
|
+
1. **Validation** — SOE validates all workflows before execution
|
|
251
|
+
2. **Sandboxing** — Consider what node types the generated workflow can use
|
|
252
|
+
3. **Review** — Log generated workflows for human review
|
|
253
|
+
4. **Constraints** — Use specific prompts to limit what the LLM can generate
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Pattern 4: LLM-Driven Node Generation
|
|
258
|
+
|
|
259
|
+
Generate individual nodes rather than complete workflows:
|
|
260
|
+
|
|
261
|
+
```yaml
|
|
262
|
+
example_workflow:
|
|
263
|
+
Start:
|
|
264
|
+
node_type: router
|
|
265
|
+
event_triggers: [START]
|
|
266
|
+
event_emissions:
|
|
267
|
+
- signal_name: GENERATE_NODE
|
|
268
|
+
|
|
269
|
+
GenerateNode:
|
|
270
|
+
node_type: llm
|
|
271
|
+
event_triggers: [GENERATE_NODE]
|
|
272
|
+
prompt: |
|
|
273
|
+
You are a workflow architect. Generate a SOE node configuration.
|
|
274
|
+
|
|
275
|
+
The node should:
|
|
276
|
+
- Be a router node
|
|
277
|
+
- Trigger on signal: {{ context.trigger_signal }}
|
|
278
|
+
- Emit signal: DYNAMIC_NODE_DONE
|
|
279
|
+
|
|
280
|
+
Target workflow: {{ context.target_workflow }}
|
|
281
|
+
Node name: {{ context.node_name }}
|
|
282
|
+
|
|
283
|
+
Provide the workflow_name, node_name, and node_config_data (as YAML string).
|
|
284
|
+
schema_name: Generated_NodeResponse
|
|
285
|
+
output_field: inject_params
|
|
286
|
+
event_emissions:
|
|
287
|
+
- signal_name: NODE_GENERATED
|
|
288
|
+
|
|
289
|
+
InjectGeneratedNode:
|
|
290
|
+
node_type: tool
|
|
291
|
+
event_triggers: [NODE_GENERATED]
|
|
292
|
+
tool_name: soe_inject_node
|
|
293
|
+
context_parameter_field: inject_params
|
|
294
|
+
output_field: injection_result
|
|
295
|
+
event_emissions:
|
|
296
|
+
- signal_name: NODE_INJECTION_COMPLETE
|
|
297
|
+
|
|
298
|
+
Complete:
|
|
299
|
+
node_type: router
|
|
300
|
+
event_triggers: [NODE_INJECTION_COMPLETE]
|
|
301
|
+
event_emissions:
|
|
302
|
+
- signal_name: DONE
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
This is more surgical—add specific capabilities to existing workflows based on context.
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## Use Cases
|
|
310
|
+
|
|
311
|
+
### Self-Healing Workflows
|
|
312
|
+
|
|
313
|
+
```yaml
|
|
314
|
+
MonitorPerformance:
|
|
315
|
+
node_type: router
|
|
316
|
+
event_triggers: [CHECK_HEALTH]
|
|
317
|
+
event_emissions:
|
|
318
|
+
- signal_name: PERFORMANCE_DEGRADED
|
|
319
|
+
condition: "{{ context.error_rate > 0.1 }}"
|
|
320
|
+
|
|
321
|
+
GenerateFix:
|
|
322
|
+
node_type: llm
|
|
323
|
+
event_triggers: [PERFORMANCE_DEGRADED]
|
|
324
|
+
prompt: |
|
|
325
|
+
The workflow has error rate {{ context.error_rate }}.
|
|
326
|
+
Generate a retry node with exponential backoff...
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Dynamic Skill Acquisition
|
|
330
|
+
|
|
331
|
+
```yaml
|
|
332
|
+
CheckCapability:
|
|
333
|
+
node_type: router
|
|
334
|
+
event_triggers: [NEW_TASK]
|
|
335
|
+
event_emissions:
|
|
336
|
+
- signal_name: NEED_NEW_SKILL
|
|
337
|
+
condition: "{{ context.task_type not in context.available_skills }}"
|
|
338
|
+
|
|
339
|
+
AcquireSkill:
|
|
340
|
+
node_type: llm
|
|
341
|
+
event_triggers: [NEED_NEW_SKILL]
|
|
342
|
+
prompt: |
|
|
343
|
+
Generate a workflow for handling {{ context.task_type }} tasks...
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### A/B Testing Workflows
|
|
347
|
+
|
|
348
|
+
Inject variant workflows and track which performs better:
|
|
349
|
+
|
|
350
|
+
```yaml
|
|
351
|
+
SelectVariant:
|
|
352
|
+
node_type: tool
|
|
353
|
+
event_triggers: [START]
|
|
354
|
+
tool_name: select_ab_variant
|
|
355
|
+
output_field: variant_workflow
|
|
356
|
+
|
|
357
|
+
InjectVariant:
|
|
358
|
+
node_type: tool
|
|
359
|
+
event_triggers: [VARIANT_SELECTED]
|
|
360
|
+
tool_name: soe_inject_workflow
|
|
361
|
+
input_fields: [variant_name, variant_workflow]
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## Best Practices
|
|
367
|
+
|
|
368
|
+
1. **Start Deterministic** — Begin with predefined injections before using LLM generation
|
|
369
|
+
2. **Validate Outputs** — Always check LLM-generated YAML is valid before injection
|
|
370
|
+
3. **Log Everything** — Record all injections for debugging and audit
|
|
371
|
+
4. **Limit Scope** — Constrain what the LLM can generate (node types, signals)
|
|
372
|
+
5. **Test Thoroughly** — Create test cases for injection scenarios
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## Summary
|
|
377
|
+
|
|
378
|
+
| Pattern | Complexity | Use Case |
|
|
379
|
+
|---------|------------|----------|
|
|
380
|
+
| Deterministic Workflow Injection | Low | Plugin systems, feature flags |
|
|
381
|
+
| Deterministic Node Injection | Low | Dynamic capability extension |
|
|
382
|
+
| LLM Workflow Generation | High | Self-evolving systems, adaptive behavior |
|
|
383
|
+
| LLM Node Generation | Medium | Targeted self-improvement |
|
|
384
|
+
|
|
385
|
+
Self-evolving workflows unlock SOE's full potential as a **protocol for intelligence**—systems that can modify their own behavior based on experience.
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# SOE Advanced Patterns: Swarm Intelligence
|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
|
|
5
|
+
Most agent frameworks rely on **conversation** (text back-and-forth). SOE relies on **signals**. This is much more efficient for coordinating multiple agents.
|
|
6
|
+
|
|
7
|
+
Swarm patterns enable:
|
|
8
|
+
- **Voting/Consensus** — Multiple agents vote, deterministic logic tallies
|
|
9
|
+
- **Jury Systems** — Parallel analysis with majority decision
|
|
10
|
+
- **Bidding/Auctions** — Agents compete via signal-based mechanisms
|
|
11
|
+
|
|
12
|
+
**Note**: Many swarm patterns leverage the **accumulated context** feature. For simpler fan-out and aggregation patterns, see [Fan-Out, Fan-In & Aggregations](guide_fanout_and_aggregations.md).
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Pattern 1: Simple Consensus
|
|
17
|
+
|
|
18
|
+
Check if a threshold is met using deterministic routing.
|
|
19
|
+
|
|
20
|
+
### The Workflow
|
|
21
|
+
|
|
22
|
+
```yaml
|
|
23
|
+
consensus_workflow:
|
|
24
|
+
Start:
|
|
25
|
+
node_type: router
|
|
26
|
+
event_triggers: [START]
|
|
27
|
+
event_emissions:
|
|
28
|
+
- signal_name: CHECK_CONSENSUS
|
|
29
|
+
|
|
30
|
+
CheckConsensus:
|
|
31
|
+
node_type: router
|
|
32
|
+
event_triggers: [CHECK_CONSENSUS]
|
|
33
|
+
event_emissions:
|
|
34
|
+
- signal_name: CONSENSUS_REACHED
|
|
35
|
+
condition: "{{ context.approve_count >= context.threshold }}"
|
|
36
|
+
- signal_name: CONSENSUS_FAILED
|
|
37
|
+
condition: "{{ context.approve_count < context.threshold }}"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### How It Works
|
|
41
|
+
|
|
42
|
+
1. Context contains `approve_count` and `threshold`
|
|
43
|
+
2. Router emits `CONSENSUS_REACHED` if threshold met
|
|
44
|
+
3. Router emits `CONSENSUS_FAILED` otherwise
|
|
45
|
+
|
|
46
|
+
**No LLM needed** — pure conditional logic handles the decision.
|
|
47
|
+
|
|
48
|
+
### Usage
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
execution_id = orchestrate(
|
|
52
|
+
config=simple_consensus,
|
|
53
|
+
initial_workflow_name="consensus_workflow",
|
|
54
|
+
initial_signals=["START"],
|
|
55
|
+
initial_context={
|
|
56
|
+
"approve_count": 5,
|
|
57
|
+
"threshold": 3,
|
|
58
|
+
},
|
|
59
|
+
...
|
|
60
|
+
)
|
|
61
|
+
# Result: CONSENSUS_REACHED signal emitted
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Pattern 2: Multi-Voter Tallying
|
|
67
|
+
|
|
68
|
+
Multiple voters provide input, router tallies the results.
|
|
69
|
+
|
|
70
|
+
### The Workflow
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
voting_workflow:
|
|
74
|
+
Start:
|
|
75
|
+
node_type: router
|
|
76
|
+
event_triggers: [START]
|
|
77
|
+
event_emissions:
|
|
78
|
+
- signal_name: VOTES_READY
|
|
79
|
+
|
|
80
|
+
TallyVotes:
|
|
81
|
+
node_type: router
|
|
82
|
+
event_triggers: [VOTES_READY]
|
|
83
|
+
event_emissions:
|
|
84
|
+
- signal_name: APPROVED
|
|
85
|
+
condition: |
|
|
86
|
+
{{ (context.votes | selectattr('vote', 'equalto', 'approve') | list | length) >= 2 }}
|
|
87
|
+
- signal_name: REJECTED
|
|
88
|
+
condition: |
|
|
89
|
+
{{ (context.votes | selectattr('vote', 'equalto', 'approve') | list | length) < 2 }}
|
|
90
|
+
|
|
91
|
+
HandleApproved:
|
|
92
|
+
node_type: router
|
|
93
|
+
event_triggers: [APPROVED]
|
|
94
|
+
event_emissions:
|
|
95
|
+
- signal_name: DONE
|
|
96
|
+
|
|
97
|
+
HandleRejected:
|
|
98
|
+
node_type: router
|
|
99
|
+
event_triggers: [REJECTED]
|
|
100
|
+
event_emissions:
|
|
101
|
+
- signal_name: DONE
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### How It Works
|
|
105
|
+
|
|
106
|
+
1. Context contains a list of votes: `[{voter: "A", vote: "approve"}, ...]`
|
|
107
|
+
2. Router uses Jinja2 to count approvals
|
|
108
|
+
3. Emits `APPROVED` if majority approve, `REJECTED` otherwise
|
|
109
|
+
|
|
110
|
+
### The Jinja2 Magic
|
|
111
|
+
|
|
112
|
+
The condition uses Jinja2 filters to count:
|
|
113
|
+
|
|
114
|
+
```jinja
|
|
115
|
+
{{ (context.votes | selectattr('vote', 'equalto', 'approve') | list | length) >= 2 }}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
This:
|
|
119
|
+
1. Filters votes where `vote == 'approve'`
|
|
120
|
+
2. Counts them
|
|
121
|
+
3. Compares to threshold
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Pattern 3: LLM-Based Voting (Full Swarm)
|
|
126
|
+
|
|
127
|
+
For real swarm intelligence, multiple LLM nodes can vote in parallel.
|
|
128
|
+
|
|
129
|
+
### The Workflow
|
|
130
|
+
|
|
131
|
+
```yaml
|
|
132
|
+
voting_workflow:
|
|
133
|
+
Start:
|
|
134
|
+
node_type: router
|
|
135
|
+
event_triggers: [START]
|
|
136
|
+
event_emissions:
|
|
137
|
+
- signal_name: ANALYZE
|
|
138
|
+
|
|
139
|
+
# Three "voters" - each is an LLM that outputs a verdict
|
|
140
|
+
Voter1:
|
|
141
|
+
node_type: llm
|
|
142
|
+
event_triggers: [ANALYZE]
|
|
143
|
+
prompt: |
|
|
144
|
+
Analyze if this content is appropriate: {{ context.content }}
|
|
145
|
+
You must output either "approve" or "reject".
|
|
146
|
+
output_field: vote_1
|
|
147
|
+
event_emissions:
|
|
148
|
+
- signal_name: VOTE_CAST
|
|
149
|
+
|
|
150
|
+
Voter2:
|
|
151
|
+
node_type: llm
|
|
152
|
+
event_triggers: [ANALYZE]
|
|
153
|
+
prompt: |
|
|
154
|
+
Analyze if this content is appropriate: {{ context.content }}
|
|
155
|
+
You must output either "approve" or "reject".
|
|
156
|
+
output_field: vote_2
|
|
157
|
+
event_emissions:
|
|
158
|
+
- signal_name: VOTE_CAST
|
|
159
|
+
|
|
160
|
+
Voter3:
|
|
161
|
+
node_type: llm
|
|
162
|
+
event_triggers: [ANALYZE]
|
|
163
|
+
prompt: |
|
|
164
|
+
Analyze if this content is appropriate: {{ context.content }}
|
|
165
|
+
You must output either "approve" or "reject".
|
|
166
|
+
output_field: vote_3
|
|
167
|
+
event_emissions:
|
|
168
|
+
- signal_name: VOTE_CAST
|
|
169
|
+
|
|
170
|
+
# Tally waits for all votes (triggered 3 times, runs when all present)
|
|
171
|
+
TallyVotes:
|
|
172
|
+
node_type: router
|
|
173
|
+
event_triggers: [VOTE_CAST]
|
|
174
|
+
event_emissions:
|
|
175
|
+
- signal_name: APPROVED
|
|
176
|
+
condition: |
|
|
177
|
+
{{ context.vote_1 is defined and context.vote_2 is defined and context.vote_3 is defined and
|
|
178
|
+
((context.vote_1 == 'approve') | int + (context.vote_2 == 'approve') | int + (context.vote_3 == 'approve') | int) >= 2 }}
|
|
179
|
+
- signal_name: REJECTED
|
|
180
|
+
condition: |
|
|
181
|
+
{{ context.vote_1 is defined and context.vote_2 is defined and context.vote_3 is defined and
|
|
182
|
+
((context.vote_1 == 'approve') | int + (context.vote_2 == 'approve') | int + (context.vote_3 == 'approve') | int) < 2 }}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### How It Works
|
|
186
|
+
|
|
187
|
+
1. `ANALYZE` signal triggers all three voters **in parallel**
|
|
188
|
+
2. Each voter outputs to their own field (`vote_1`, `vote_2`, `vote_3`)
|
|
189
|
+
3. Each voter emits `VOTE_CAST`
|
|
190
|
+
4. `TallyVotes` router triggers on each `VOTE_CAST`
|
|
191
|
+
5. When all votes present, router emits final decision
|
|
192
|
+
|
|
193
|
+
**Key Insight**: The router runs multiple times but only emits the final signal when all votes are available (Jinja2 conditions check for all fields).
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Why Signals Beat Conversation
|
|
198
|
+
|
|
199
|
+
| Approach | Coordination Cost | Speed |
|
|
200
|
+
|----------|-------------------|-------|
|
|
201
|
+
| Conversation | Parse N paragraphs per round | Slow |
|
|
202
|
+
| Signals | Count N signal emissions | Fast |
|
|
203
|
+
|
|
204
|
+
For 12 agents voting, conversation requires parsing 12 text responses. Signals require counting 12 emissions. The signal approach scales to hundreds of agents.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Related Patterns
|
|
209
|
+
|
|
210
|
+
- [Hybrid Intelligence](hybrid_intelligence.md) — Mix deterministic and AI logic
|
|
211
|
+
- [Self-Evolving Workflows](self_evolving_workflows.md) — Agents that modify their own workflows
|