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,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: "&#123;&#123; context.approve_count < context.threshold &#125;&#125;"
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
+ &#123;&#123; (context.votes | selectattr('vote', 'equalto', 'approve') | list | length) >= 2 &#125;&#125;
87
+ - signal_name: REJECTED
88
+ condition: |
89
+ &#123;&#123; (context.votes | selectattr('vote', 'equalto', 'approve') | list | length) < 2 &#125;&#125;
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: &#123;&#123; context.content &#125;&#125;
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: &#123;&#123; context.content &#125;&#125;
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: &#123;&#123; context.content &#125;&#125;
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
+ &#123;&#123; 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 &#125;&#125;
179
+ - signal_name: REJECTED
180
+ condition: |
181
+ &#123;&#123; 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 &#125;&#125;
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