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,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
|