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.
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.2.dist-info}/METADATA +4 -4
  130. soe_ai-0.1.2.dist-info/RECORD +137 -0
  131. {soe_ai-0.1.1.dist-info → soe_ai-0.1.2.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.2.dist-info}/licenses/LICENSE +0 -0
  134. {soe_ai-0.1.1.dist-info → soe_ai-0.1.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,146 @@
1
+
2
+ # SOE Guide: Chapter 3 - Router Nodes
3
+
4
+ ## Introduction to Router Nodes
5
+
6
+ The **Router** is a pure routing node—it doesn't execute code or call LLMs. It simply reads the context, evaluates conditions, and emits signals to direct the workflow.
7
+
8
+ Think of a Router as a **traffic controller**: based on the current state (context), it decides which signal to emit, directing the workflow to the next step. Routers are the glue that connects your Tool and LLM nodes together.
9
+
10
+ ### When to Use Router Nodes
11
+
12
+ - **Entry Points**: Start a workflow and fan out to multiple paths
13
+ - **Conditional Branching**: Route based on context values
14
+ - **Signal Transformation**: Convert one signal to another
15
+ - **Checkpoints**: Create explicit decision points in your workflow
16
+
17
+ ### Why Router Comes Third
18
+
19
+ We covered Tool and LLM nodes first because they **do work**. Routers don't—they route. Now that you understand the nodes that execute, you can see how Routers connect them into powerful workflows.
20
+
21
+ ## Your First Router: Input Validation
22
+
23
+ Let's validate that user input exists before processing it.
24
+
25
+ ### The Workflow
26
+
27
+ ```yaml
28
+ example_workflow:
29
+ InputValidator:
30
+ node_type: router
31
+ event_triggers: [START]
32
+ event_emissions:
33
+ - signal_name: VALID_INPUT
34
+ condition: "{{ context.user_input is defined and context.user_input != '' }}"
35
+ - signal_name: INVALID_INPUT
36
+ condition: "{{ context.user_input is not defined or context.user_input == '' }}"
37
+ ```
38
+
39
+ ### How It Works
40
+
41
+ 1. **Event Trigger**: The `START` signal triggers the `InputValidator` node.
42
+ 2. **Condition Evaluation**: The router checks two conditions using Jinja2 templates:
43
+ - If `context.user_input` is defined and not empty → emit `VALID_INPUT`
44
+ - If `context.user_input` is missing or empty → emit `INVALID_INPUT`
45
+ 3. **Signal Emission**: One or more signals are emitted based on which conditions are true.
46
+
47
+ ### Key Concepts
48
+
49
+ - **Routers** evaluate Jinja2 conditions and emit signals
50
+ - **Conditions** can check if variables exist, compare values, etc.
51
+ - **Signals** are the "nervous system" of SOE—they trigger the next steps
52
+
53
+ ## Unconditional Signals
54
+
55
+ Not every router needs a condition. Sometimes you just want to forward a signal unconditionally.
56
+
57
+ ### The Workflow
58
+
59
+ ```yaml
60
+ example_workflow:
61
+ Forwarder:
62
+ node_type: router
63
+ event_triggers: [START]
64
+ event_emissions:
65
+ - signal_name: CONTINUE
66
+ ```
67
+
68
+ This router simply receives `START` and immediately emits `CONTINUE`. It's useful when you want to:
69
+ - **Rename signals**: Transform external signals to internal naming
70
+ - **Create checkpoints**: Make workflow structure explicit
71
+ - **Fan-out**: Emit multiple signals to trigger parallel processing
72
+
73
+ ## Decision Trees: Routers Calling Routers
74
+
75
+ The real power of routers emerges when you chain them together. Since each router emits signals that trigger other nodes, you can build **decision trees** (or more generally, **directed graphs**) where the workflow branches based on context.
76
+
77
+ ### The Pattern
78
+
79
+ Think of it as a flowchart:
80
+ - **Level 1 Router**: Classifies the initial request
81
+ - **Level 2+ Routers**: Handle sub-cases, triggered by signals from Level 1
82
+ - **Terminal Nodes**: Complete specific branches of the tree
83
+
84
+ ### The Workflow
85
+
86
+ ```yaml
87
+ example_workflow:
88
+ # Level 1: Is the user premium or free?
89
+ UserTierCheck:
90
+ node_type: router
91
+ event_triggers: [START]
92
+ event_emissions:
93
+ - signal_name: PREMIUM_PATH
94
+ condition: "{{ context.user_tier == 'premium' }}"
95
+ - signal_name: FREE_PATH
96
+ condition: "{{ context.user_tier == 'free' }}"
97
+
98
+ # Level 2a: Premium users get feature checks
99
+ PremiumFeatureRouter:
100
+ node_type: router
101
+ event_triggers: [PREMIUM_PATH]
102
+ event_emissions:
103
+ - signal_name: ENABLE_ADVANCED
104
+ condition: "{{ context.feature_level == 'advanced' }}"
105
+ - signal_name: ENABLE_BASIC
106
+ condition: "{{ context.feature_level != 'advanced' }}"
107
+
108
+ # Level 2b: Free users get upgrade prompts
109
+ FreeUserRouter:
110
+ node_type: router
111
+ event_triggers: [FREE_PATH]
112
+ event_emissions:
113
+ - signal_name: SHOW_UPGRADE
114
+ condition: "{{ context.show_upsell }}"
115
+ - signal_name: CONTINUE_FREE
116
+ condition: "{{ not context.show_upsell }}"
117
+ ```
118
+
119
+ ### How It Works
120
+
121
+ 1. `UserTierCheck` receives `START` and evaluates `context.user_tier`
122
+ 2. Depending on the tier, it emits either `PREMIUM_PATH` or `FREE_PATH`
123
+ 3. The corresponding router (`PremiumFeatureRouter` or `FreeUserRouter`) activates
124
+ 4. That router evaluates its own conditions and emits the final signal
125
+
126
+ ### Why This Matters
127
+
128
+ - **Modularity**: Each router handles one decision. Easy to test, easy to modify.
129
+ - **Composition**: Add new branches by adding new routers—no need to modify existing ones.
130
+ - **Visibility**: The workflow YAML *is* the flowchart. No hidden logic in code.
131
+
132
+ ## The Three Core Node Types
133
+
134
+ You now know the three core node types in SOE:
135
+
136
+ | Node | Purpose | Does Work? |
137
+ |------|---------|-----------|
138
+ | **Tool** | Execute Python functions | ✅ Yes |
139
+ | **LLM** | Call language models | ✅ Yes |
140
+ | **Router** | Route signals based on context | ❌ No (pure routing) |
141
+
142
+ With just these three nodes, you can build remarkably sophisticated workflows—including custom agent patterns, chain-of-thought reasoning, and more. The next chapter shows you how.
143
+
144
+ ## Next Steps
145
+
146
+ Now that you understand the three core node types, let's combine them into powerful patterns with [Building Custom Workflows](guide_04_patterns.md) →
@@ -0,0 +1,475 @@
1
+
2
+ # SOE Guide: Chapter 4 - Building Custom Workflows
3
+
4
+ ## The Three Core Node Types
5
+
6
+ You now know SOE's three core node types:
7
+
8
+ | Node | What It Does | Key Capability |
9
+ |------|--------------|----------------|
10
+ | **Tool** | Executes Python functions | Real-world actions |
11
+ | **LLM** | Calls language models | Intelligence & generation |
12
+ | **Router** | Routes based on context | Control flow & branching |
13
+
14
+ These three nodes are **all you need** to build sophisticated AI workflows. This chapter shows you how to combine them into powerful patterns—including building your own agent loops from scratch.
15
+
16
+ ## Why Build Custom Workflows?
17
+
18
+ The built-in **Agent Node** (covered in the next chapter) provides a convenient ReAct loop. But sometimes you need:
19
+
20
+ - **Custom reasoning patterns** (chain-of-thought, tree-of-thought, metacognition)
21
+ - **Fine-grained control** over each step
22
+ - **Hybrid logic** mixing deterministic and AI-driven decisions
23
+ - **Domain-specific agent architectures**
24
+
25
+ With these three core nodes, you can build any pattern—the Agent Node is just one opinionated implementation.
26
+
27
+ ---
28
+
29
+ ## Pattern 1: Chain of Thought
30
+
31
+ **The Pattern**: Break complex reasoning into explicit steps, where each LLM call builds on the previous one.
32
+
33
+ ### The Workflow
34
+
35
+ ```yaml
36
+ example_workflow:
37
+ Understand:
38
+ node_type: llm
39
+ event_triggers: [START]
40
+ prompt: "Analyze this problem and restate it in your own words: {{ context.problem }}"
41
+ output_field: understanding
42
+ event_emissions:
43
+ - signal_name: UNDERSTOOD
44
+
45
+ Plan:
46
+ node_type: llm
47
+ event_triggers: [UNDERSTOOD]
48
+ prompt: |
49
+ Based on your understanding: {{ context.understanding }}
50
+
51
+ Create a step-by-step plan to solve this problem.
52
+ output_field: plan
53
+ event_emissions:
54
+ - signal_name: PLANNED
55
+
56
+ Execute:
57
+ node_type: llm
58
+ event_triggers: [PLANNED]
59
+ prompt: |
60
+ Understanding: {{ context.understanding }}
61
+ Plan: {{ context.plan }}
62
+
63
+ Now execute the plan and provide the final answer.
64
+ output_field: answer
65
+ event_emissions:
66
+ - signal_name: COMPLETE
67
+ ```
68
+
69
+ ### How It Works
70
+
71
+ 1. **Understand**: LLM analyzes and restates the problem
72
+ 2. **Plan**: LLM creates a step-by-step plan based on its understanding
73
+ 3. **Execute**: LLM generates the final answer using the plan
74
+
75
+ Each step stores its output in context, and the next step reads it.
76
+
77
+ ### Why This Pattern?
78
+
79
+ - **Transparency**: Each reasoning step is visible and inspectable
80
+ - **Debuggability**: If the answer is wrong, you can see exactly where reasoning failed
81
+ - **Control**: You can add validation routers between steps
82
+
83
+ ---
84
+
85
+ ## Pattern 2: Custom ReAct Loop
86
+
87
+ **The Pattern**: Build your own Reasoning + Acting loop using the three core node types.
88
+
89
+ ### The Workflow
90
+
91
+ ```yaml
92
+ example_workflow:
93
+ Reason:
94
+ node_type: llm
95
+ event_triggers: [START, TOOL_RESULT]
96
+ prompt: |
97
+ Task: {{ context.task }}
98
+ {% if context.tool_results %}
99
+ Previous tool results: {{ context.tool_results }}
100
+ {% endif %}
101
+
102
+ Decide what to do next. If you need to use a tool, output:
103
+ {"action": "use_tool", "tool": "tool_name", "args": {...}}
104
+
105
+ If you have the final answer, output:
106
+ {"action": "finish", "answer": "your answer"}
107
+ output_field: decision
108
+ event_emissions:
109
+ - signal_name: DECIDED
110
+
111
+ Route:
112
+ node_type: router
113
+ event_triggers: [DECIDED]
114
+ event_emissions:
115
+ - signal_name: USE_TOOL
116
+ condition: "{{ context.decision.action == 'use_tool' }}"
117
+ - signal_name: FINISH
118
+ condition: "{{ context.decision.action == 'finish' }}"
119
+
120
+ Act:
121
+ node_type: tool
122
+ event_triggers: [USE_TOOL]
123
+ tool_name: dynamic_tool
124
+ context_parameter_field: decision
125
+ output_field: tool_results
126
+ event_emissions:
127
+ - signal_name: TOOL_RESULT
128
+
129
+ Complete:
130
+ node_type: router
131
+ event_triggers: [FINISH]
132
+ event_emissions:
133
+ - signal_name: TASK_COMPLETE
134
+ ```
135
+
136
+ ### How It Works
137
+
138
+ 1. **Reason**: LLM analyzes the situation and decides what to do
139
+ 2. **Route**: Router checks if LLM wants to use a tool or finish
140
+ 3. **Act**: If tool needed, execute it and loop back to Reason
141
+ 4. **Complete**: When done, emit final signal
142
+
143
+ ### The Loop Structure
144
+
145
+ ```
146
+ ┌──────────────────────────────────────────────────────┐
147
+ │ │
148
+ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
149
+ │ │ Reason │───▶│ Route │───▶│ Act │──────────┘
150
+ │ │ (LLM) │ │(Router) │ │ (Tool) │
151
+ │ └─────────┘ └────┬────┘ └─────────┘
152
+ │ │
153
+ │ ▼
154
+ │ ┌─────────┐
155
+ │ │Complete │
156
+ │ │(Router) │
157
+ │ └─────────┘
158
+ ```
159
+
160
+ This is exactly what the Agent Node does internally—but now you control every piece.
161
+
162
+ ---
163
+
164
+ ## Pattern 3: Metacognition (Self-Reflection)
165
+
166
+ **The Pattern**: Have the LLM review and critique its own output before finalizing.
167
+
168
+ ### The Workflow
169
+
170
+ ```yaml
171
+ example_workflow:
172
+ Generate:
173
+ node_type: llm
174
+ event_triggers: [START]
175
+ prompt: "Write a response to: {{ context.request }}"
176
+ output_field: draft
177
+ event_emissions:
178
+ - signal_name: DRAFT_READY
179
+
180
+ Critique:
181
+ node_type: llm
182
+ event_triggers: [DRAFT_READY]
183
+ prompt: |
184
+ Review this response for errors, unclear language, or improvements:
185
+ {{ context.draft }}
186
+
187
+ Output JSON with needs_revision (true or false) and critique fields.
188
+ output_field: review
189
+ event_emissions:
190
+ - signal_name: REVIEWED
191
+
192
+ CheckRevision:
193
+ node_type: router
194
+ event_triggers: [REVIEWED]
195
+ event_emissions:
196
+ - signal_name: REVISE
197
+ condition: "{{ context.review.needs_revision == true }}"
198
+ - signal_name: FINALIZE
199
+ condition: "{{ context.review.needs_revision == false }}"
200
+
201
+ Refine:
202
+ node_type: llm
203
+ event_triggers: [REVISE]
204
+ prompt: |
205
+ Original: {{ context.draft }}
206
+ Critique: {{ context.review.critique }}
207
+
208
+ Write an improved version addressing the feedback.
209
+ output_field: final_response
210
+ event_emissions:
211
+ - signal_name: COMPLETE
212
+
213
+ AcceptDraft:
214
+ node_type: router
215
+ event_triggers: [FINALIZE]
216
+ event_emissions:
217
+ - signal_name: COMPLETE
218
+ ```
219
+
220
+ ### How It Works
221
+
222
+ 1. **Generate**: LLM produces an initial response
223
+ 2. **Critique**: A second LLM call reviews the response for errors or improvements
224
+ 3. **Route**: Check if revision is needed
225
+ 4. **Refine** (if needed): Generate improved response based on critique
226
+ 5. **Finalize**: Output the best version
227
+
228
+ ### Why This Pattern?
229
+
230
+ - **Quality improvement**: Catches errors the first pass missed
231
+ - **Self-correction**: The model identifies its own weaknesses
232
+ - **Controllable iteration**: You decide when to stop refining
233
+
234
+ ---
235
+
236
+ ## Pattern 4: Parallel Analysis with Voting
237
+
238
+ **The Pattern**: Run multiple LLM analyses in parallel, then aggregate results.
239
+
240
+ ### The Workflow
241
+
242
+ ```yaml
243
+ example_workflow:
244
+ FanOut:
245
+ node_type: router
246
+ event_triggers: [START]
247
+ event_emissions:
248
+ - signal_name: ANALYZE_SAFETY
249
+ - signal_name: ANALYZE_QUALITY
250
+ - signal_name: ANALYZE_RELEVANCE
251
+
252
+ SafetyCheck:
253
+ node_type: llm
254
+ event_triggers: [ANALYZE_SAFETY]
255
+ prompt: |
256
+ Check if this content is safe: {{ context.content }}
257
+ Return JSON with safe (boolean) and reason (string) fields.
258
+ output_field: safety_result
259
+ event_emissions:
260
+ - signal_name: SAFETY_DONE
261
+
262
+ QualityCheck:
263
+ node_type: llm
264
+ event_triggers: [ANALYZE_QUALITY]
265
+ prompt: |
266
+ Rate the quality of this content: {{ context.content }}
267
+ Return JSON with score (1-10) and feedback (string) fields.
268
+ output_field: quality_result
269
+ event_emissions:
270
+ - signal_name: QUALITY_DONE
271
+
272
+ RelevanceCheck:
273
+ node_type: llm
274
+ event_triggers: [ANALYZE_RELEVANCE]
275
+ prompt: |
276
+ Check relevance to topic '{{ context.topic }}': {{ context.content }}
277
+ Return JSON with relevant (boolean) field.
278
+ output_field: relevance_result
279
+ event_emissions:
280
+ - signal_name: RELEVANCE_DONE
281
+
282
+ Aggregate:
283
+ node_type: tool
284
+ event_triggers: [SAFETY_DONE, QUALITY_DONE, RELEVANCE_DONE]
285
+ tool_name: aggregate_votes
286
+ context_parameter_field: vote_params
287
+ output_field: final_decision
288
+ event_emissions:
289
+ - signal_name: APPROVED
290
+ condition: "{{ result.approved == true }}"
291
+ - signal_name: REJECTED
292
+ condition: "{{ result.approved == false }}"
293
+ ```
294
+
295
+ ### How It Works
296
+
297
+ 1. **Fan-Out**: Router emits multiple signals simultaneously
298
+ 2. **Parallel Analysis**: Multiple LLM nodes run independently
299
+ 3. **Aggregate**: Tool collects all analyses and determines consensus
300
+ 4. **Route Result**: Based on the aggregated vote
301
+
302
+ ### Why This Pattern?
303
+
304
+ - **Diversity of perspective**: Different prompts catch different issues
305
+ - **Robustness**: Single LLM errors are outvoted
306
+ - **Speed**: Parallel execution (if your infrastructure supports it)
307
+
308
+ ---
309
+
310
+ ## Pattern 5: Iterative Refinement with Tools
311
+
312
+ **The Pattern**: LLM generates, tool validates, loop until valid.
313
+
314
+ ### The Workflow
315
+
316
+ ```yaml
317
+ example_workflow:
318
+ Generate:
319
+ node_type: llm
320
+ event_triggers: [START, RETRY]
321
+ prompt: |
322
+ Generate Python code for: {{ context.task }}
323
+ {% if context.errors %}
324
+ Previous attempt had these errors: {{ context.errors }}
325
+ Fix them in this attempt.
326
+ {% endif %}
327
+ output_field: code
328
+ event_emissions:
329
+ - signal_name: CODE_GENERATED
330
+
331
+ Validate:
332
+ node_type: tool
333
+ event_triggers: [CODE_GENERATED]
334
+ tool_name: lint_code
335
+ context_parameter_field: code
336
+ output_field: validation
337
+ event_emissions:
338
+ - signal_name: VALID
339
+ condition: "{{ result.errors|length == 0 }}"
340
+ - signal_name: INVALID
341
+ condition: "{{ result.errors|length > 0 }}"
342
+
343
+ PrepareRetry:
344
+ node_type: router
345
+ event_triggers: [INVALID]
346
+ event_emissions:
347
+ - signal_name: RETRY
348
+
349
+ Complete:
350
+ node_type: router
351
+ event_triggers: [VALID]
352
+ event_emissions:
353
+ - signal_name: CODE_COMPLETE
354
+ ```
355
+
356
+ ### How It Works
357
+
358
+ 1. **Generate**: LLM creates code/content
359
+ 2. **Validate**: Tool runs linter, tests, or other validation
360
+ 3. **Route**: Check validation result
361
+ 4. **Loop or Complete**: If invalid, inject errors and regenerate
362
+
363
+ ### The Loop Structure
364
+
365
+ ```
366
+ ┌────────────────────────────────────────────────┐
367
+ │ │
368
+ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
369
+ │ │ Generate │───▶│ Validate │───▶│ Route │─┘
370
+ │ │ (LLM) │ │ (Tool) │ │ (Router) │
371
+ │ └──────────┘ └──────────┘ └────┬─────┘
372
+ │ │
373
+ │ ▼
374
+ │ ┌──────────┐
375
+ │ │ Complete │
376
+ │ └──────────┘
377
+ ```
378
+
379
+ ---
380
+
381
+ ## Pattern 6: Hierarchical Task Decomposition
382
+
383
+ **The Pattern**: Break complex tasks into subtasks, solve each, then synthesize.
384
+
385
+ ### The Workflow
386
+
387
+ ```yaml
388
+ example_workflow:
389
+ Decompose:
390
+ node_type: llm
391
+ event_triggers: [START]
392
+ prompt: |
393
+ Break this task into subtasks: {{ context.task }}
394
+ Output JSON with subtasks array and types array (research, code, writing).
395
+ output_field: breakdown
396
+ event_emissions:
397
+ - signal_name: DECOMPOSED
398
+
399
+ RouteSubtasks:
400
+ node_type: router
401
+ event_triggers: [DECOMPOSED]
402
+ event_emissions:
403
+ - signal_name: DO_RESEARCH
404
+ condition: "{{ 'research' in context.breakdown.types }}"
405
+ - signal_name: DO_CODING
406
+ condition: "{{ 'code' in context.breakdown.types }}"
407
+ - signal_name: DO_WRITING
408
+ condition: "{{ 'writing' in context.breakdown.types }}"
409
+
410
+ ResearchSubtask:
411
+ node_type: llm
412
+ event_triggers: [DO_RESEARCH]
413
+ prompt: "Research: {{ context.breakdown.subtasks | selectattr('type', 'equalto', 'research') | list }}"
414
+ output_field: research_result
415
+ event_emissions:
416
+ - signal_name: SUBTASK_DONE
417
+
418
+ CodingSubtask:
419
+ node_type: llm
420
+ event_triggers: [DO_CODING]
421
+ prompt: "Code: {{ context.breakdown.subtasks | selectattr('type', 'equalto', 'code') | list }}"
422
+ output_field: code_result
423
+ event_emissions:
424
+ - signal_name: SUBTASK_DONE
425
+
426
+ Synthesize:
427
+ node_type: llm
428
+ event_triggers: [SUBTASK_DONE]
429
+ prompt: |
430
+ Combine these results into a final answer:
431
+ Research: {{ context.research_result }}
432
+ Code: {{ context.code_result }}
433
+ output_field: final_answer
434
+ event_emissions:
435
+ - signal_name: COMPLETE
436
+ ```
437
+
438
+ ### How It Works
439
+
440
+ 1. **Decompose**: LLM breaks the task into subtasks
441
+ 2. **Fan-Out**: Router emits signals for each subtask type
442
+ 3. **Solve**: Specialized nodes handle each subtask
443
+ 4. **Synthesize**: Combine subtask results into final answer
444
+
445
+ ---
446
+
447
+ ## Combining Patterns
448
+
449
+ The real power comes from combining patterns. For example:
450
+
451
+ - **Chain-of-Thought + Metacognition**: Reason step-by-step, then self-review
452
+ - **Custom ReAct + Iterative Refinement**: Agent loop with validation gates
453
+ - **Parallel Voting + Hierarchical Decomposition**: Ensemble of specialized agents
454
+
455
+ Since everything is YAML, you can compose patterns freely.
456
+
457
+ ---
458
+
459
+ ## When to Use Custom Workflows vs Agent Node
460
+
461
+ | Use Custom Workflows When... | Use Agent Node When... |
462
+ |------------------------------|------------------------|
463
+ | You need non-standard reasoning patterns | Standard ReAct loop is sufficient |
464
+ | You want explicit control over each step | You want hands-off tool-using agent |
465
+ | You're debugging or iterating on agent logic | You need quick prototyping |
466
+ | You need deterministic checkpoints | Tool selection is the main complexity |
467
+ | You're building production systems with audit requirements | Development speed matters most |
468
+
469
+ The Agent Node is a **convenience**—it encapsulates a common pattern. These custom workflows show you can build anything.
470
+
471
+ ---
472
+
473
+ ## Next Steps
474
+
475
+ Now that you can build custom agent patterns, see how the built-in [Agent Node](guide_05_agent.md) encapsulates the ReAct pattern for convenience →