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.
Files changed (145) hide show
  1. soe/__init__.py +50 -0
  2. soe/broker.py +168 -0
  3. soe/builtin_tools/__init__.py +51 -0
  4. soe/builtin_tools/soe_add_signal.py +82 -0
  5. soe/builtin_tools/soe_call_tool.py +111 -0
  6. soe/builtin_tools/soe_copy_context.py +80 -0
  7. soe/builtin_tools/soe_explore_docs.py +290 -0
  8. soe/builtin_tools/soe_get_available_tools.py +42 -0
  9. soe/builtin_tools/soe_get_context.py +50 -0
  10. soe/builtin_tools/soe_get_context_schema.py +56 -0
  11. soe/builtin_tools/soe_get_identities.py +63 -0
  12. soe/builtin_tools/soe_get_workflows.py +63 -0
  13. soe/builtin_tools/soe_inject_context_schema_field.py +80 -0
  14. soe/builtin_tools/soe_inject_identity.py +64 -0
  15. soe/builtin_tools/soe_inject_node.py +86 -0
  16. soe/builtin_tools/soe_inject_workflow.py +105 -0
  17. soe/builtin_tools/soe_list_contexts.py +73 -0
  18. soe/builtin_tools/soe_remove_context_schema_field.py +61 -0
  19. soe/builtin_tools/soe_remove_identity.py +61 -0
  20. soe/builtin_tools/soe_remove_node.py +72 -0
  21. soe/builtin_tools/soe_remove_workflow.py +62 -0
  22. soe/builtin_tools/soe_update_context.py +54 -0
  23. soe/docs/_config.yml +10 -0
  24. soe/docs/advanced_patterns/guide_fanout_and_aggregations.md +318 -0
  25. soe/docs/advanced_patterns/guide_inheritance.md +435 -0
  26. soe/docs/advanced_patterns/hybrid_intelligence.md +237 -0
  27. soe/docs/advanced_patterns/index.md +49 -0
  28. soe/docs/advanced_patterns/operational.md +781 -0
  29. soe/docs/advanced_patterns/self_evolving_workflows.md +385 -0
  30. soe/docs/advanced_patterns/swarm_intelligence.md +211 -0
  31. soe/docs/builtins/context.md +164 -0
  32. soe/docs/builtins/context_schema.md +158 -0
  33. soe/docs/builtins/identity.md +139 -0
  34. soe/docs/builtins/soe_explore_docs.md +135 -0
  35. soe/docs/builtins/tools.md +164 -0
  36. soe/docs/builtins/workflows.md +199 -0
  37. soe/docs/guide_00_getting_started.md +341 -0
  38. soe/docs/guide_01_tool.md +206 -0
  39. soe/docs/guide_02_llm.md +143 -0
  40. soe/docs/guide_03_router.md +146 -0
  41. soe/docs/guide_04_patterns.md +475 -0
  42. soe/docs/guide_05_agent.md +159 -0
  43. soe/docs/guide_06_schema.md +397 -0
  44. soe/docs/guide_07_identity.md +540 -0
  45. soe/docs/guide_08_child.md +612 -0
  46. soe/docs/guide_09_ecosystem.md +690 -0
  47. soe/docs/guide_10_infrastructure.md +427 -0
  48. soe/docs/guide_11_builtins.md +126 -0
  49. soe/docs/index.md +104 -0
  50. soe/docs/primitives/backends.md +281 -0
  51. soe/docs/primitives/context.md +256 -0
  52. soe/docs/primitives/node_reference.md +259 -0
  53. soe/docs/primitives/primitives.md +331 -0
  54. soe/docs/primitives/signals.md +865 -0
  55. soe/docs_index.py +2 -0
  56. soe/init.py +165 -0
  57. soe/lib/__init__.py +0 -0
  58. soe/lib/child_context.py +46 -0
  59. soe/lib/context_fields.py +51 -0
  60. soe/lib/inheritance.py +172 -0
  61. soe/lib/jinja_render.py +113 -0
  62. soe/lib/operational.py +51 -0
  63. soe/lib/parent_sync.py +71 -0
  64. soe/lib/register_event.py +75 -0
  65. soe/lib/schema_validation.py +134 -0
  66. soe/lib/yaml_parser.py +14 -0
  67. soe/local_backends/__init__.py +18 -0
  68. soe/local_backends/factory.py +124 -0
  69. soe/local_backends/in_memory/context.py +38 -0
  70. soe/local_backends/in_memory/conversation_history.py +60 -0
  71. soe/local_backends/in_memory/identity.py +52 -0
  72. soe/local_backends/in_memory/schema.py +40 -0
  73. soe/local_backends/in_memory/telemetry.py +38 -0
  74. soe/local_backends/in_memory/workflow.py +33 -0
  75. soe/local_backends/storage/context.py +57 -0
  76. soe/local_backends/storage/conversation_history.py +82 -0
  77. soe/local_backends/storage/identity.py +118 -0
  78. soe/local_backends/storage/schema.py +96 -0
  79. soe/local_backends/storage/telemetry.py +72 -0
  80. soe/local_backends/storage/workflow.py +56 -0
  81. soe/nodes/__init__.py +13 -0
  82. soe/nodes/agent/__init__.py +10 -0
  83. soe/nodes/agent/factory.py +134 -0
  84. soe/nodes/agent/lib/loop_handlers.py +150 -0
  85. soe/nodes/agent/lib/loop_state.py +157 -0
  86. soe/nodes/agent/lib/prompts.py +65 -0
  87. soe/nodes/agent/lib/tools.py +35 -0
  88. soe/nodes/agent/stages/__init__.py +12 -0
  89. soe/nodes/agent/stages/parameter.py +37 -0
  90. soe/nodes/agent/stages/response.py +54 -0
  91. soe/nodes/agent/stages/router.py +37 -0
  92. soe/nodes/agent/state.py +111 -0
  93. soe/nodes/agent/types.py +66 -0
  94. soe/nodes/agent/validation/__init__.py +11 -0
  95. soe/nodes/agent/validation/config.py +95 -0
  96. soe/nodes/agent/validation/operational.py +24 -0
  97. soe/nodes/child/__init__.py +3 -0
  98. soe/nodes/child/factory.py +61 -0
  99. soe/nodes/child/state.py +59 -0
  100. soe/nodes/child/validation/__init__.py +11 -0
  101. soe/nodes/child/validation/config.py +126 -0
  102. soe/nodes/child/validation/operational.py +28 -0
  103. soe/nodes/lib/conditions.py +71 -0
  104. soe/nodes/lib/context.py +24 -0
  105. soe/nodes/lib/conversation_history.py +77 -0
  106. soe/nodes/lib/identity.py +64 -0
  107. soe/nodes/lib/llm_resolver.py +142 -0
  108. soe/nodes/lib/output.py +68 -0
  109. soe/nodes/lib/response_builder.py +91 -0
  110. soe/nodes/lib/signal_emission.py +79 -0
  111. soe/nodes/lib/signals.py +54 -0
  112. soe/nodes/lib/tools.py +100 -0
  113. soe/nodes/llm/__init__.py +7 -0
  114. soe/nodes/llm/factory.py +103 -0
  115. soe/nodes/llm/state.py +76 -0
  116. soe/nodes/llm/types.py +12 -0
  117. soe/nodes/llm/validation/__init__.py +11 -0
  118. soe/nodes/llm/validation/config.py +89 -0
  119. soe/nodes/llm/validation/operational.py +23 -0
  120. soe/nodes/router/__init__.py +3 -0
  121. soe/nodes/router/factory.py +37 -0
  122. soe/nodes/router/state.py +32 -0
  123. soe/nodes/router/validation/__init__.py +11 -0
  124. soe/nodes/router/validation/config.py +58 -0
  125. soe/nodes/router/validation/operational.py +16 -0
  126. soe/nodes/tool/factory.py +66 -0
  127. soe/nodes/tool/lib/__init__.py +11 -0
  128. soe/nodes/tool/lib/conditions.py +35 -0
  129. soe/nodes/tool/lib/failure.py +28 -0
  130. soe/nodes/tool/lib/parameters.py +67 -0
  131. soe/nodes/tool/state.py +66 -0
  132. soe/nodes/tool/types.py +27 -0
  133. soe/nodes/tool/validation/__init__.py +15 -0
  134. soe/nodes/tool/validation/config.py +132 -0
  135. soe/nodes/tool/validation/operational.py +16 -0
  136. soe/types.py +209 -0
  137. soe/validation/__init__.py +18 -0
  138. soe/validation/config.py +195 -0
  139. soe/validation/jinja.py +54 -0
  140. soe/validation/operational.py +110 -0
  141. soe_ai-0.2.0b1.dist-info/METADATA +262 -0
  142. soe_ai-0.2.0b1.dist-info/RECORD +145 -0
  143. soe_ai-0.2.0b1.dist-info/WHEEL +5 -0
  144. soe_ai-0.2.0b1.dist-info/licenses/LICENSE +21 -0
  145. soe_ai-0.2.0b1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,164 @@
1
+
2
+ # Built-in: Tool Management
3
+
4
+ ## Dynamic Tool Discovery and Invocation
5
+
6
+ These built-in tools enable workflows to **discover and invoke tools dynamically**. Rather than hardcoding tool references, workflows can query available capabilities and invoke tools by name at runtime—enabling truly flexible, self-evolving systems.
7
+
8
+ ---
9
+
10
+ ## Available Tools
11
+
12
+ | Tool | Purpose |
13
+ |------|---------|
14
+ | `soe_get_available_tools` | List all registered tools |
15
+ | `soe_call_tool` | Invoke a tool by name dynamically |
16
+
17
+ ---
18
+
19
+ ## soe_get_available_tools
20
+
21
+ Discover all tools registered in the current orchestration context.
22
+
23
+ ```yaml
24
+ example_workflow:
25
+ ListTools:
26
+ node_type: tool
27
+ event_triggers: [START]
28
+ tool_name: soe_get_available_tools
29
+ output_field: available_tools
30
+ event_emissions:
31
+ - signal_name: TOOLS_LISTED
32
+ ```
33
+
34
+ Returns a list of tool names that can be invoked with `soe_call_tool`.
35
+
36
+ ### Use Cases
37
+
38
+ - **Self-awareness** — Let LLMs understand available capabilities
39
+ - **Dynamic routing** — Choose tools based on current needs
40
+ - **Validation** — Check if a required tool exists before invoking
41
+
42
+ ---
43
+
44
+ ## soe_call_tool
45
+
46
+ Invoke any registered tool by name with JSON arguments.
47
+
48
+ ```yaml
49
+ example_workflow:
50
+ CallDynamicTool:
51
+ node_type: tool
52
+ event_triggers: [START]
53
+ tool_name: soe_call_tool
54
+ context_parameter_field: tool_invocation
55
+ output_field: tool_result
56
+ event_emissions:
57
+ - signal_name: TOOL_CALLED
58
+ ```
59
+
60
+ ### Context Setup
61
+
62
+ The node expects `tool_invocation` in context with:
63
+
64
+ ```python
65
+ {
66
+ "tool_name": "name_of_tool",
67
+ "arguments": '{"arg1": "value1", "arg2": "value2"}'
68
+ }
69
+ ```
70
+
71
+ ### Return Value
72
+
73
+ Returns a result dictionary:
74
+
75
+ ```python
76
+ {
77
+ "success": True,
78
+ "result": { ... } # Tool's return value
79
+ }
80
+ ```
81
+
82
+ Or on error:
83
+
84
+ ```python
85
+ {
86
+ "success": False,
87
+ "error": "Error message"
88
+ }
89
+ ```
90
+
91
+ ### Use Cases
92
+
93
+ - **LLM-driven tool selection** — Let LLMs choose and invoke tools
94
+ - **Dynamic dispatch** — Route to tools based on runtime conditions
95
+ - **Tool orchestration** — Build meta-tools that compose other tools
96
+
97
+ ---
98
+
99
+ ## Dynamic Tool Pattern
100
+
101
+ Combine discovery and invocation for fully dynamic behavior:
102
+
103
+ ```yaml
104
+ dynamic_tool_workflow:
105
+ DiscoverTools:
106
+ node_type: tool
107
+ event_triggers: [START]
108
+ tool_name: soe_get_available_tools
109
+ output_field: available_tools
110
+ event_emissions:
111
+ - signal_name: TOOLS_DISCOVERED
112
+
113
+ InvokeTool:
114
+ node_type: tool
115
+ event_triggers: [TOOLS_DISCOVERED]
116
+ tool_name: soe_call_tool
117
+ context_parameter_field: tool_invocation
118
+ output_field: invocation_result
119
+ event_emissions:
120
+ - signal_name: INVOCATION_COMPLETE
121
+ ```
122
+
123
+ This pattern enables:
124
+
125
+ 1. **Discovery** — First, query available tools
126
+ 2. **Decision** — LLM or router selects appropriate tool
127
+ 3. **Invocation** — Execute the chosen tool dynamically
128
+
129
+ ---
130
+
131
+ ## When to Use
132
+
133
+ ### Use Dynamic Invocation When:
134
+
135
+ - Tool selection depends on runtime context
136
+ - LLMs need to choose from available capabilities
137
+ - Building meta-tools that orchestrate other tools
138
+ - Creating plugin-like architectures
139
+
140
+ ### Use Direct Tool Nodes When:
141
+
142
+ - Tool is always the same
143
+ - No runtime decision needed
144
+ - Performance is critical (direct is faster)
145
+
146
+ ---
147
+
148
+ ## Security Considerations
149
+
150
+ `soe_call_tool` can only invoke tools in the `tools_registry`. It cannot:
151
+
152
+ - Execute arbitrary code
153
+ - Access tools not explicitly registered
154
+ - Bypass tool retry/error handling
155
+
156
+ The registry acts as a whitelist—only tools you register are available.
157
+
158
+ ---
159
+
160
+ ## Related
161
+
162
+ - [Context Management](context.md) — Managing execution state
163
+ - [Workflow Management](workflows.md) — Runtime workflow modification
164
+ - [Self-Evolving Workflows](../advanced_patterns/self_evolving_workflows.md) — Building autonomous systems
@@ -0,0 +1,199 @@
1
+
2
+ # Built-in: Workflow Modification
3
+
4
+ ## Runtime Workflow Evolution
5
+
6
+ These built-in tools enable workflows to **modify themselves at runtime**. This is the core capability for self-evolution—a workflow can inspect its structure, add new nodes, inject new workflows, and remove obsolete components.
7
+
8
+ ---
9
+
10
+ ## Available Tools
11
+
12
+ | Tool | Purpose |
13
+ |------|---------|
14
+ | `soe_get_workflows` | Query registered workflow definitions |
15
+ | `soe_inject_workflow` | Add new workflows to the registry |
16
+ | `soe_inject_node` | Add or modify nodes in existing workflows |
17
+ | `soe_remove_workflow` | Remove workflows from registry |
18
+ | `soe_remove_node` | Remove nodes from workflows |
19
+
20
+ ---
21
+
22
+ ## soe_get_workflows
23
+
24
+ Query the current workflow registry to see all registered workflows and their structure.
25
+
26
+ ```yaml
27
+ example_workflow:
28
+ GetWorkflows:
29
+ node_type: tool
30
+ event_triggers: [START]
31
+ tool_name: soe_get_workflows
32
+ output_field: workflows_list
33
+ event_emissions:
34
+ - signal_name: WORKFLOWS_RETRIEVED
35
+ ```
36
+
37
+ Returns a dictionary of all registered workflows with their node configurations.
38
+
39
+ ### Use Cases
40
+
41
+ - **Introspection** — See what workflows are available
42
+ - **Validation** — Check if a workflow exists before spawning it
43
+ - **Evolution planning** — Understand current structure before modifying
44
+
45
+ ---
46
+
47
+ ## soe_inject_workflow
48
+
49
+ Add a completely new workflow to the registry at runtime.
50
+
51
+ ```yaml
52
+ example_workflow:
53
+ InjectNew:
54
+ node_type: tool
55
+ event_triggers: [START]
56
+ tool_name: soe_inject_workflow
57
+ context_parameter_field: workflow_to_inject
58
+ output_field: injection_result
59
+ event_emissions:
60
+ - signal_name: WORKFLOW_INJECTED
61
+ condition: "{{ result.status == 'success' }}"
62
+ - signal_name: INJECTION_FAILED
63
+ condition: "{{ result.status != 'success' }}"
64
+ ```
65
+
66
+ ### Context Setup
67
+
68
+ The `workflow_to_inject` context field should contain:
69
+
70
+ ```python
71
+ {
72
+ "workflow_name": "new_workflow",
73
+ "workflow_definition": {
74
+ "StartNode": {
75
+ "node_type": "router",
76
+ "event_triggers": ["START"],
77
+ "event_emissions": [{"signal_name": "READY"}]
78
+ }
79
+ }
80
+ }
81
+ ```
82
+
83
+ ### Use Cases
84
+
85
+ - **Dynamic workflow creation** — LLM designs new workflows
86
+ - **Plugin systems** — Load workflows based on configuration
87
+ - **A/B testing** — Inject alternative workflow versions
88
+
89
+ ---
90
+
91
+ ## soe_inject_node
92
+
93
+ Add or modify a single node in an existing workflow.
94
+
95
+ ```yaml
96
+ example_workflow:
97
+ InjectNode:
98
+ node_type: tool
99
+ event_triggers: [START]
100
+ tool_name: soe_inject_node
101
+ context_parameter_field: node_to_inject
102
+ output_field: node_injection_result
103
+ event_emissions:
104
+ - signal_name: NODE_INJECTED
105
+ condition: "{{ result.status == 'success' }}"
106
+ ```
107
+
108
+ ### Context Setup
109
+
110
+ The `node_to_inject` context field should contain:
111
+
112
+ ```python
113
+ {
114
+ "workflow_name": "example_workflow",
115
+ "node_name": "NewProcessor",
116
+ "node_configuration": {
117
+ "node_type": "tool",
118
+ "event_triggers": ["PROCESS"],
119
+ "tool_name": "process_data",
120
+ "output_field": "result",
121
+ "event_emissions": [{"signal_name": "PROCESSED"}]
122
+ }
123
+ }
124
+ ```
125
+
126
+ ### Use Cases
127
+
128
+ - **Incremental evolution** — Add nodes one at a time
129
+ - **Patching** — Modify existing node behavior
130
+ - **Extension** — Add new capabilities to existing workflows
131
+
132
+ ---
133
+
134
+ ## soe_remove_workflow
135
+
136
+ Remove a workflow from the registry.
137
+
138
+ ```yaml
139
+ example_workflow:
140
+ RemoveOldWorkflow:
141
+ node_type: tool
142
+ event_triggers: [START]
143
+ tool_name: soe_remove_workflow
144
+ context_parameter_field: remove_params
145
+ output_field: removal_result
146
+ event_emissions:
147
+ - signal_name: WORKFLOW_REMOVED
148
+ ```
149
+
150
+ ---
151
+
152
+ ## soe_remove_node
153
+
154
+ Remove a specific node from a workflow.
155
+
156
+ ```yaml
157
+ example_workflow:
158
+ RemoveNode:
159
+ node_type: tool
160
+ event_triggers: [START]
161
+ tool_name: soe_remove_node
162
+ context_parameter_field: remove_params
163
+ output_field: node_removal_result
164
+ event_emissions:
165
+ - signal_name: NODE_REMOVED
166
+ ```
167
+
168
+ ---
169
+
170
+ ## Evolution Pattern
171
+
172
+ Combine these tools for full self-evolution:
173
+
174
+ ```yaml
175
+ evolving_workflow:
176
+ AnalyzeState:
177
+ node_type: tool
178
+ event_triggers: [START]
179
+ tool_name: soe_get_workflows
180
+ output_field: current_state
181
+ event_emissions:
182
+ - signal_name: STATE_ANALYZED
183
+
184
+ ApplyImprovement:
185
+ node_type: tool
186
+ event_triggers: [STATE_ANALYZED]
187
+ tool_name: soe_inject_node
188
+ context_parameter_field: designed_node
189
+ output_field: injection_result
190
+ event_emissions:
191
+ - signal_name: EVOLVED
192
+ ```
193
+
194
+ ---
195
+
196
+ ## Related
197
+
198
+ - [Built-in Tools Overview](../guide_11_builtins.md) — All available built-ins
199
+ - [Self-Evolving Workflows](../advanced_patterns/self_evolving_workflows.md) — Complete evolution patterns
@@ -0,0 +1,341 @@
1
+ # SOE Guide: Getting Started
2
+
3
+ ## What is SOE?
4
+
5
+ SOE (Signal-driven Orchestration Engine) is a Python library for building workflows through **signal-based orchestration**. Workflows are defined in YAML and executed by nodes that communicate through signals.
6
+
7
+ **If you've already seen the [README](../README.md)**, this guide expands on the quick start with explanations and guidance to other guides.
8
+
9
+ ---
10
+
11
+ ## The 7 Primitives
12
+
13
+ SOE is built on **7 core concepts**. Understanding these gives you complete control:
14
+
15
+ | Primitive | What It Is | Quick Example |
16
+ |-----------|------------|---------------|
17
+ | **Signals** | Named events for node communication | `START`, `DONE`, `ERROR` |
18
+ | **Context** | Shared state dictionary (with Jinja access) | `{{ context.user_id }}` |
19
+ | **Workflows** | YAML definitions of nodes and their relationships | See below |
20
+ | **Backends** | Pluggable storage implementations | PostgreSQL, DynamoDB, in-memory |
21
+ | **Nodes** | Execution units (router, llm, tool, agent, child) | `node_type: llm` |
22
+ | **Identities** | System prompts for LLM/Agent nodes | Optional role definitions |
23
+ | **Context Schema** | Type validation for LLM outputs | Optional field definitions |
24
+
25
+ **Key insight**: Context is accessible via Jinja2 in conditions and prompts. This makes workflows readable and debuggable.
26
+
27
+ For detailed coverage, see [The 7 Primitives](primitives/primitives.md).
28
+
29
+ ---
30
+
31
+ ## Signal Types
32
+
33
+ SOE supports different signal emission patterns:
34
+
35
+ | Type | Description | Example |
36
+ |------|-------------|---------|
37
+ | **Conditional** | Emitted when Jinja condition is true | `condition: "{{ context.valid }}"` |
38
+ | **Unconditional** | Always emitted (no condition) | `signal_name: DONE` |
39
+ | **Semantic** | LLM selects signal (for testing/prototyping) | `signal_name: APPROVE` with LLM choice |
40
+
41
+ ---
42
+
43
+ ## Key Concept: Synchronous Execution
44
+
45
+ SOE executes **synchronously** within a single Python process. This is intentional:
46
+
47
+ ```python
48
+ # This is how SOE works - synchronous, blocking execution
49
+ execution_id = orchestrate(
50
+ config=my_workflow,
51
+ initial_workflow_name="example_workflow",
52
+ initial_signals=["START"],
53
+ initial_context={"user": "alice"},
54
+ backends=backends,
55
+ broadcast_signals_caller=broadcast_signals_caller,
56
+ )
57
+ # When this returns, the entire workflow has completed
58
+ ```
59
+
60
+ **Why synchronous?**
61
+
62
+ 1. **Simplicity** — No async/await complexity, no event loop management
63
+ 2. **Predictability** — Easy to debug, test, and reason about
64
+ 3. **Portability** — The same workflow runs locally, in Lambda, in Jenkins, anywhere
65
+
66
+ **But what about distribution?**
67
+
68
+ The synchronous nature is about *how a single workflow executes*. Distribution happens at the **caller level**:
69
+
70
+ - Replace `broadcast_signals_caller` with an HTTP webhook → signals trigger remote services
71
+ - Replace `broadcast_signals_caller` with an SQS publisher → signals become queue messages
72
+ - Replace backends with PostgreSQL/DynamoDB → state becomes distributed
73
+
74
+ Your workflow YAML stays exactly the same. See [Chapter 10: Custom Infrastructure](guide_10_infrastructure.md) for details.
75
+
76
+ ---
77
+
78
+ ## Installation
79
+
80
+ ```bash
81
+ # With uv (recommended)
82
+ uv add soe-ai
83
+
84
+ # Or with pip
85
+ pip install soe-ai
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Quick Start: Copy-Paste Template
91
+
92
+ Here's a minimal working example. Copy this, add your own workflow, and run:
93
+
94
+ ```python
95
+ from soe import orchestrate, broadcast_signals
96
+ from soe.local_backends import create_in_memory_backends
97
+ from soe.nodes.router.factory import create_router_node_caller
98
+
99
+ # 1. Define your workflow in YAML
100
+ workflow_yaml = """
101
+ example_workflow:
102
+ Start:
103
+ node_type: router
104
+ event_triggers: [START]
105
+ event_emissions:
106
+ - signal_name: VALID
107
+ condition: "{{ context.user_input is defined }}"
108
+ - signal_name: INVALID
109
+ condition: "{{ context.user_input is not defined }}"
110
+
111
+ # Routers can also act as signal converters (tunneling)
112
+ # This decouples downstream nodes from knowing validation logic
113
+ OnValid:
114
+ node_type: router
115
+ event_triggers: [VALID]
116
+ event_emissions:
117
+ - signal_name: DONE
118
+
119
+ OnInvalid:
120
+ node_type: router
121
+ event_triggers: [INVALID]
122
+ event_emissions:
123
+ - signal_name: DONE
124
+ """
125
+
126
+ # 2. Create in-memory backends (simplest option)
127
+ backends = create_in_memory_backends()
128
+
129
+ # 3. Set up nodes and caller
130
+ nodes = {}
131
+
132
+ def broadcast_signals_caller(id: str, signals):
133
+ broadcast_signals(id, signals, nodes, backends)
134
+
135
+ nodes["router"] = create_router_node_caller(backends, broadcast_signals_caller)
136
+
137
+ # 4. Execute!
138
+ execution_id = orchestrate(
139
+ config=workflow_yaml,
140
+ initial_workflow_name="example_workflow",
141
+ initial_signals=["START"],
142
+ initial_context={"user_input": "Hello, SOE!"},
143
+ backends=backends,
144
+ broadcast_signals_caller=broadcast_signals_caller,
145
+ )
146
+
147
+ # 5. Check the result
148
+ context = backends.context.get_context(execution_id)
149
+ print(f"Status: {context['status']}") # Output: Status: success
150
+ ```
151
+
152
+ ---
153
+
154
+ ## Adding More Node Types
155
+
156
+ The template above only uses `router` nodes. To add more capabilities:
157
+
158
+ ### Adding Tool Nodes
159
+
160
+ ```python
161
+ from soe.nodes.tool.factory import create_tool_node_caller
162
+
163
+ # Define your tools
164
+ def greet(name: str) -> dict:
165
+ return {"greeting": f"Hello, {name}!"}
166
+
167
+ tools_registry = {"greet": greet}
168
+
169
+ # Add to nodes
170
+ nodes["tool"] = create_tool_node_caller(backends, tools_registry, broadcast_signals_caller)
171
+ ```
172
+
173
+ ### Adding LLM Nodes
174
+
175
+ SOE **does not provide** an LLM. Instead, you provide a `call_llm` function that wraps your chosen provider:
176
+
177
+ ```python
178
+ from soe.nodes.llm.factory import create_llm_node_caller
179
+
180
+ # Define your LLM caller (wrap your API)
181
+ def call_llm(prompt: str, config: dict) -> str:
182
+ """
183
+ The LLM caller contract:
184
+ - prompt: The rendered prompt string (text in)
185
+ - config: The full node configuration from YAML
186
+ - returns: The LLM response (text out)
187
+ """
188
+ # Example: Using OpenAI
189
+ import openai
190
+ response = openai.chat.completions.create(
191
+ model=config.get("model", "gpt-4o"), # Custom field from YAML
192
+ messages=[{"role": "user", "content": prompt}]
193
+ )
194
+ return response.choices[0].message.content
195
+
196
+ # Add to nodes
197
+ nodes["llm"] = create_llm_node_caller(backends, call_llm, broadcast_signals_caller)
198
+ ```
199
+
200
+ The `config` parameter receives the **entire node configuration** from your YAML. This lets you add custom fields:
201
+
202
+ ```yaml
203
+ MyLLM:
204
+ node_type: llm
205
+ prompt: "Summarize: {{ context.text }}"
206
+ model: gpt-4o-mini # Custom field - your call_llm can read this
207
+ temperature: 0.7 # Another custom field
208
+ output_field: summary
209
+ ```
210
+
211
+ See [Chapter 10: Infrastructure](guide_10_infrastructure.md#the-llm-caller) for detailed examples.
212
+
213
+ ### Adding Agent Nodes
214
+
215
+ ```python
216
+ from soe.nodes.agent.factory import create_agent_node_caller
217
+
218
+ # Agent needs both LLM and tools
219
+ tools = [{"function": greet, "max_retries": 0}]
220
+ nodes["agent"] = create_agent_node_caller(backends, tools, call_llm, broadcast_signals_caller)
221
+ ```
222
+
223
+ ### Adding Child Workflow Nodes
224
+
225
+ ```python
226
+ import copy
227
+ from soe import orchestrate, broadcast_signals
228
+ from soe.nodes.child.factory import create_child_node_caller
229
+ from soe.lib.yaml_parser import parse_yaml
230
+
231
+ # Create orchestrate caller for child workflows
232
+ def orchestrate_caller(config, initial_workflow_name, initial_signals, initial_context, backends):
233
+ if isinstance(config, str):
234
+ config = parse_yaml(config)
235
+ else:
236
+ config = copy.deepcopy(config)
237
+
238
+ def broadcast_signals_caller(execution_id, signals):
239
+ broadcast_signals(execution_id, signals, nodes, backends)
240
+
241
+ return orchestrate(
242
+ config=config,
243
+ initial_workflow_name=initial_workflow_name,
244
+ initial_signals=initial_signals,
245
+ initial_context=initial_context,
246
+ backends=backends,
247
+ broadcast_signals_caller=broadcast_signals_caller,
248
+ )
249
+
250
+ nodes["child"] = create_child_node_caller(backends, orchestrate_caller)
251
+ ```
252
+
253
+ ---
254
+
255
+ ## Helper Function: All Nodes at Once
256
+
257
+ For convenience, you can set up all node types in one call using the built-in `create_all_nodes` helper:
258
+
259
+ ```python
260
+ from soe import create_all_nodes, orchestrate
261
+ from soe.local_backends import create_in_memory_backends
262
+
263
+ backends = create_in_memory_backends()
264
+
265
+ # Set up all nodes (pass your call_llm and tools_registry)
266
+ nodes, broadcast = create_all_nodes(
267
+ backends,
268
+ call_llm=my_call_llm,
269
+ tools_registry=my_tools
270
+ )
271
+
272
+ # Usage
273
+ execution_id = orchestrate(
274
+ config=workflow_yaml,
275
+ initial_workflow_name="example_workflow",
276
+ initial_signals=["START"],
277
+ initial_context={"user_input": "Hello!"},
278
+ backends=backends,
279
+ broadcast_signals_caller=broadcast,
280
+ )
281
+ ```
282
+
283
+ ---
284
+
285
+ ## Choosing Your Infrastructure
286
+
287
+ SOE supports different infrastructure configurations depending on your needs:
288
+
289
+ | Scenario | Backends | Caller | Use Case |
290
+ |----------|----------|--------|----------|
291
+ | **Development** | In-Memory | Local | Fast iteration, unit tests |
292
+ | **Local Debugging** | Local Files | Local | Inspect state in JSON files |
293
+ | **Distributed State** | PostgreSQL/DynamoDB | Local | Shared state, multiple workers |
294
+ | **Fully Distributed** | PostgreSQL/DynamoDB | HTTP/SQS/Lambda | Scalable production |
295
+
296
+ For custom backends and distributed callers, see [Chapter 10: Infrastructure](guide_10_infrastructure.md).
297
+
298
+ ---
299
+
300
+ ## Workflow Portability
301
+
302
+ A critical design principle: **your workflow YAML works everywhere**.
303
+
304
+ ```yaml
305
+ # This exact workflow runs:
306
+ # - Locally with in-memory backends
307
+ # - In production with PostgreSQL + Lambda callers
308
+ # - In Jenkins with file backends + webhook callers
309
+ example_workflow:
310
+ ValidateInput:
311
+ node_type: router
312
+ event_triggers: [START]
313
+ event_emissions:
314
+ - signal_name: VALID
315
+ condition: "{{ context.data is defined }}"
316
+ ```
317
+
318
+ The workflow defines *what* happens. The infrastructure (backends + callers) defines *how* it executes. Change the infrastructure without touching workflow logic.
319
+
320
+ ---
321
+
322
+ ## Next Steps
323
+
324
+ **Understand the Building Blocks:**
325
+ - **[The 7 Primitives](primitives/primitives.md)** — Deep dive into signals, context, workflows, and backends
326
+
327
+ **Core Guides:**
328
+ 1. **[Chapter 1: Tool Nodes](guide_01_tool.md)** — Execute Python functions—APIs, databases, real work
329
+ 2. **[Chapter 2: LLM Nodes](guide_02_llm.md)** — Add language model capabilities
330
+ 3. **[Chapter 3: Router Nodes](guide_03_router.md)** — Pure routing, branching, no execution
331
+ 4. **[Chapter 4: Patterns](guide_04_patterns.md)** — Combine primitives: ReAct, chain-of-thought
332
+ 5. **[Chapter 5: Agent Nodes](guide_05_agent.md)** — Built-in ReAct loop for tool-using agents
333
+ 6. **[Chapter 6: Schema](guide_06_schema.md)** — Validate LLM outputs with context schema
334
+ 7. **[Chapter 7: Identity](guide_07_identity.md)** — System prompts and conversation history
335
+ 8. **[Chapter 8: Child Workflows](guide_08_child.md)** — Nested workflows with signal communication
336
+ 9. **[Chapter 9: Ecosystem](guide_09_ecosystem.md)** — Multi-workflow registries and versioning
337
+ 10. **[Chapter 10: Infrastructure](guide_10_infrastructure.md)** — Custom backends and callers
338
+ 11. **[Chapter 11: Built-in Tools](guide_11_builtins.md)** — Self-evolution, documentation exploration, and runtime modification
339
+
340
+ **Advanced:**
341
+ - **[Self-Evolving Workflows](advanced_patterns/self_evolving_workflows.md)** — Workflows that modify themselves at runtime