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,259 @@
1
+
2
+ # Appendix B: Node Configuration Reference
3
+
4
+ Complete reference for all node configuration parameters across all node types.
5
+
6
+ ## Node Types Overview
7
+
8
+ | Node Type | Purpose | LLM Required |
9
+ |-----------|---------|--------------|
10
+ | `router` | Evaluate conditions and emit signals | No |
11
+ | `llm` | Single LLM call with structured output | Yes |
12
+ | `agent` | Multi-turn LLM with tool calling | Yes |
13
+ | `tool` | Execute Python function | No |
14
+ | `child` | Start sub-orchestration | No |
15
+
16
+ ## Configuration Parameters
17
+
18
+ ### Legend
19
+
20
+ - ✓ = Supported
21
+ - ✗ = Not supported
22
+ - **R** = Required
23
+ - **O** = Optional
24
+
25
+ ### Core Parameters
26
+
27
+ | Parameter | Type | Router | LLM | Agent | Tool | Child | Description |
28
+ |-----------|------|--------|-----|-------|------|-------|-------------|
29
+ | `node_type` | `str` | **R** | **R** | **R** | **R** | **R** | Node type identifier |
30
+ | `event_triggers` | `List[str]` | **R** | **R** | **R** | **R** | **R** | Signals that activate this node |
31
+ | `event_emissions` | `List[dict]` | **R** | **O** | **O** | **O** | **O** | Signals to emit (with optional conditions) |
32
+
33
+ ### Prompt & Output Parameters
34
+
35
+ | Parameter | Type | Router | LLM | Agent | Tool | Child | Description |
36
+ |-----------|------|--------|-----|-------|------|-------|-------------|
37
+ | `prompt` | `str` | ✗ | **R** | **R** | ✗ | ✗ | Jinja template for LLM prompt |
38
+ | `output_field` | `str` | ✗ | **O** | **O** | **O** | ✗ | Context field to store result |
39
+ | `identity` | `str` | ✗ | **O** | **O** | ✗ | ✗ | Key for conversation history persistence |
40
+
41
+ ### Tool Parameters
42
+
43
+ | Parameter | Type | Router | LLM | Agent | Tool | Child | Description |
44
+ |-----------|------|--------|-----|-------|------|-------|-------------|
45
+ | `tool_name` | `str` | ✗ | ✗ | ✗ | **R** | ✗ | Tool to execute from registry |
46
+ | `tools` | `List[str]` | ✗ | ✗ | **O** | ✗ | ✗ | Tool names available to agent |
47
+ | `context_parameter_field` | `str` | ✗ | ✗ | ✗ | **O** | ✗ | Context field containing tool kwargs |
48
+
49
+ ### Child Workflow Parameters
50
+
51
+ | Parameter | Type | Router | LLM | Agent | Tool | Child | Description |
52
+ |-----------|------|--------|-----|-------|------|-------|-------------|
53
+ | `child_workflow_name` | `str` | ✗ | ✗ | ✗ | ✗ | **R** | Workflow to start as child |
54
+ | `child_initial_signals` | `List[str]` | ✗ | ✗ | ✗ | ✗ | **R** | Signals to start child with |
55
+ | `signals_to_parent` | `List[str]` | ✗ | ✗ | ✗ | ✗ | **O** | Child signals that propagate to parent |
56
+ | `context_updates_to_parent` | `List[str]` | ✗ | ✗ | ✗ | ✗ | **O** | Context keys that sync to parent |
57
+ | `input_fields` | `List[str]` | ✗ | ✗ | ✗ | ✗ | **O** | Context fields to pass to child |
58
+ | `fan_out_field` | `str` | ✗ | ✗ | ✗ | ✗ | **O** | Spawn one child per item in field's history |
59
+ | `child_input_field` | `str` | ✗ | ✗ | ✗ | ✗ | **O** | Field in child context for each fan-out item |
60
+ | `spawn_interval` | `float` | ✗ | ✗ | ✗ | ✗ | **O** | Seconds to sleep between fan-out spawns |
61
+
62
+ ### Retry & Failure Parameters
63
+
64
+ | Parameter | Type | Router | LLM | Agent | Tool | Child | Description |
65
+ |-----------|------|--------|-----|-------|------|-------|-------------|
66
+ | `retries` | `int` | ✗ | **O** | **O** | ✗ | ✗ | Max LLM validation retries (default: 3) |
67
+ | `llm_failure_signal` | `str` | ✗ | **O** | **O** | ✗ | ✗ | Signal when LLM retries exhausted |
68
+
69
+ ## Tool Registry Configuration
70
+
71
+ Tools are registered with optional configuration:
72
+
73
+ ```python
74
+ # Simple format
75
+ tools_registry = {
76
+ "send_email": send_email_function,
77
+ }
78
+
79
+ # Extended format
80
+ tools_registry = {
81
+ "send_email": {
82
+ "function": send_email_function,
83
+ "max_retries": 3,
84
+ "failure_signal": "EMAIL_FAILED",
85
+ },
86
+ }
87
+ ```
88
+
89
+ | Field | Type | Required | Default | Description |
90
+ |-------|------|----------|---------|-------------|
91
+ | `function` | `Callable` | Yes | - | The Python function to execute |
92
+ | `max_retries` | `int` | No | 1 | Execution retries on failure |
93
+ | `failure_signal` | `str` | No | None | Signal when all retries exhausted |
94
+ | `process_accumulated` | `bool` | No | False | Pass full history list instead of last value |
95
+
96
+ ## Event Emissions Format
97
+
98
+ The `event_emissions` parameter accepts a list of signal definitions:
99
+
100
+
101
+ ```yaml
102
+ event_emissions:
103
+ - signal_name: SUCCESS
104
+ - signal_name: NEEDS_REVIEW
105
+ condition: "{{ result.confidence < 0.8 }}" # Jinja: evaluated programmatically
106
+ - signal_name: POSITIVE
107
+ condition: "User sentiment is positive" # Plain text: for LLM signal selection
108
+ ```
109
+
110
+
111
+ | Field | Type | Required | Description |
112
+ |-------|------|----------|-----------|
113
+ | `signal_name` | `str` | Yes | Signal to emit |
114
+ | `condition` | `str` | No | Plain text = LLM selects signal; Jinja (`{{ }}`) = evaluated programmatically |
115
+
116
+ ## Parameter Details
117
+
118
+ ### `event_triggers`
119
+
120
+ Signals that activate the node. Uses OR logic by default.
121
+
122
+ ```yaml
123
+ event_triggers: [START, RETRY, MANUAL_TRIGGER]
124
+ ```
125
+
126
+ ### `event_emissions`
127
+
128
+ Signals to emit after node execution. Behavior varies by node type:
129
+
130
+ | Node Type | Condition Context | Default Behavior |
131
+ |-----------|-------------------|------------------|
132
+ | Router | `context` | Emits first matching condition |
133
+ | LLM | N/A (uses `description` for LLM selection) | Emits all if no descriptions |
134
+ | Agent | N/A (uses `description` for LLM selection) | Emits all if no descriptions |
135
+ | Tool | `result` and `context` | Emits all on success |
136
+ | Child | `context` | Emits all after child starts |
137
+
138
+ ### `prompt`
139
+
140
+ Jinja template with access to `context`:
141
+
142
+
143
+ ```yaml
144
+ prompt: "Analyze the following: {{ context.user_input }}"
145
+ ```
146
+
147
+
148
+ ### `identity`
149
+
150
+ Enables conversation history persistence. Same identity = shared history.
151
+
152
+
153
+ ```yaml
154
+ identity: "user_session_123"
155
+ # Or dynamic:
156
+ identity: "{{ context.session_id }}"
157
+ ```
158
+
159
+
160
+ ### `retries`
161
+
162
+ Controls LLM validation retry attempts when response doesn't match expected schema.
163
+
164
+ ```yaml
165
+ retries: 5 # Default is 3
166
+ ```
167
+
168
+ ### `llm_failure_signal`
169
+
170
+ Emit signal instead of raising exception when LLM retries are exhausted:
171
+
172
+ ```yaml
173
+ llm_failure_signal: LLM_FAILED
174
+ ```
175
+
176
+ ## Quick Reference by Node Type
177
+
178
+ ### Router Node
179
+
180
+
181
+ ```yaml
182
+ MyRouter:
183
+ node_type: router
184
+ event_triggers: [START] # Required
185
+ event_emissions: # Required
186
+ - signal_name: VALID
187
+ condition: "{{ context.input }}"
188
+ - signal_name: INVALID
189
+ ```
190
+
191
+
192
+ ### LLM Node
193
+
194
+
195
+ ```yaml
196
+ MyLLM:
197
+ node_type: llm
198
+ event_triggers: [START] # Required
199
+ prompt: "Process: {{ context.data }}" # Required
200
+ output_field: result # Optional
201
+ identity: session_123 # Optional
202
+ retries: 3 # Optional (default: 3)
203
+ llm_failure_signal: LLM_FAILED # Optional
204
+ event_emissions: # Optional
205
+ - signal_name: DONE
206
+ ```
207
+
208
+
209
+ ### Agent Node
210
+
211
+
212
+ ```yaml
213
+ MyAgent:
214
+ node_type: agent
215
+ event_triggers: [START] # Required
216
+ prompt: "Help with: {{ context.task }}" # Required
217
+ tools: [search, calculate] # Optional
218
+ output_field: result # Optional
219
+ identity: agent_session # Optional
220
+ retries: 3 # Optional (default: 3)
221
+ llm_failure_signal: AGENT_FAILED # Optional
222
+ event_emissions: # Optional
223
+ - signal_name: DONE
224
+ ```
225
+
226
+
227
+ ### Tool Node
228
+
229
+
230
+ ```yaml
231
+ MyTool:
232
+ node_type: tool
233
+ event_triggers: [START] # Required
234
+ tool_name: send_email # Required
235
+ context_parameter_field: email_data # Optional
236
+ output_field: email_result # Optional
237
+ event_emissions: # Optional
238
+ - signal_name: SENT
239
+ - signal_name: NEEDS_RETRY
240
+ condition: "{{ result.status == 'pending' }}"
241
+ - signal_name: HIGH_PRIORITY
242
+ condition: "{{ result.sent and context.email_data.priority == 'high' }}"
243
+ ```
244
+
245
+
246
+ ### Child Node
247
+
248
+ ```yaml
249
+ MyChild:
250
+ node_type: child
251
+ event_triggers: [START] # Required
252
+ child_workflow_name: sub_workflow # Required
253
+ child_initial_signals: [BEGIN] # Required
254
+ input_fields: [user_data] # Optional
255
+ signals_to_parent: [CHILD_DONE] # Optional
256
+ context_updates_to_parent: [result] # Optional
257
+ event_emissions: # Optional
258
+ - signal_name: CHILD_STARTED
259
+ ```
@@ -0,0 +1,331 @@
1
+ # SOE Concepts: The 7 Primitives
2
+
3
+ SOE is built on **7 core primitives**. Understanding these gives you complete mastery of the system.
4
+
5
+ | Primitive | Purpose |
6
+ |-----------|---------|
7
+ | **Signals** | Communication between nodes |
8
+ | **Context** | Shared state dictionary |
9
+ | **Workflows** | YAML definitions of nodes |
10
+ | **Backends** | Pluggable storage |
11
+ | **Nodes** | Execution units |
12
+ | **Identities** | System prompts for LLMs |
13
+ | **Context Schema** | Type validation for outputs |
14
+
15
+ ---
16
+
17
+ ## 1. Signals
18
+
19
+ **What:** Named events that flow through the system.
20
+
21
+ **Purpose:** Communication between nodes. Signals are the *only* way nodes interact.
22
+
23
+
24
+ ```yaml
25
+ event_emissions:
26
+ - signal_name: USER_VALIDATED
27
+ condition: "{{ context.user_id is defined }}"
28
+ ```
29
+
30
+
31
+ **Key Properties:**
32
+ - Signals are strings (e.g., `START`, `COMPLETE`, `ERROR`)
33
+ - Multiple signals can be emitted at once
34
+ - Signals trigger nodes via `event_triggers`
35
+ - Signals are stateless—they carry no payload (use Context for data)
36
+
37
+ **Examples:**
38
+ - `START` — Initial trigger for a workflow
39
+ - `TOOL_SUCCESS` — A tool completed successfully
40
+ - `AGENT_NEEDS_INPUT` — Agent requires user interaction
41
+
42
+ ---
43
+
44
+ ## 2. Context
45
+
46
+ **What:** A shared dictionary of data for the current execution.
47
+
48
+ **Purpose:** State management. All nodes read from and write to Context.
49
+
50
+ ```python
51
+ context = backends.context.get_context(execution_id)
52
+ # {"user_id": "123", "validated": True, "result": "Hello!"}
53
+ ```
54
+
55
+ **Key Properties:**
56
+ - One Context per execution ID
57
+ - Persisted via `ContextBackend`
58
+ - LLM/Agent/Tool nodes can update Context via `output_field`
59
+ - Jinja2 templates access Context: `{{ context.user_id }}`
60
+
61
+ **Examples:**
62
+ ```yaml
63
+ # LLM/Agent nodes store results via output_field
64
+ output_field: result
65
+ ```
66
+
67
+ ---
68
+
69
+ ## 3. Workflows
70
+
71
+ **What:** YAML definitions that describe node configurations and their relationships.
72
+
73
+ **Purpose:** The blueprint for orchestration. Defines *what* happens, not *how*.
74
+
75
+
76
+ ```yaml
77
+ my_workflow:
78
+ ValidateInput:
79
+ node_type: router
80
+ event_triggers: [START]
81
+ event_emissions:
82
+ - signal_name: VALID
83
+ condition: "{{ context.data is defined }}"
84
+
85
+ ProcessData:
86
+ node_type: tool
87
+ event_triggers: [VALID]
88
+ tool_name: process
89
+ input_fields: [data]
90
+ output_field: result
91
+ ```
92
+
93
+
94
+ **Key Properties:**
95
+ - Multiple workflows can exist in one registry
96
+ - Workflows are parsed at runtime (Jinja2 + YAML)
97
+ - Workflows are portable—same YAML runs on any infrastructure
98
+ - Child nodes can spawn sub-workflows
99
+
100
+ ---
101
+
102
+ ## 4. Backends
103
+
104
+ **What:** Pluggable persistence implementations.
105
+
106
+ **Purpose:** Store state, workflows, telemetry, and history. Infrastructure-agnostic.
107
+
108
+ ```python
109
+ from soe.local_backends import create_in_memory_backends
110
+
111
+ backends = create_in_memory_backends()
112
+ ```
113
+
114
+ **The 5 Backend Types:**
115
+
116
+ | Backend | Interface | Purpose |
117
+ |---------|-----------|---------|
118
+ | `context` | `ContextBackend` | Execution state |
119
+ | `workflow` | `WorkflowBackend` | Workflow definitions |
120
+ | `telemetry` | `TelemetryBackend` | Logs and events |
121
+ | `conversation_history` | `ConversationHistoryBackend` | LLM chat history |
122
+ | `schema` | `SchemaBackend` | Context validation schemas |
123
+
124
+ **Key Properties:**
125
+ - Follow Python `Protocol` (structural typing)
126
+ - Built-in: In-Memory, Local Files
127
+ - Custom: PostgreSQL, DynamoDB, Redis, etc.
128
+
129
+ See [Chapter 10: Infrastructure](../guide_10_infrastructure.md) for implementation details.
130
+
131
+ ---
132
+
133
+ ## 5. Nodes
134
+
135
+ **What:** Execution units that perform work when triggered by signals.
136
+
137
+ **Purpose:** The building blocks of workflows. Each node type has a specific role.
138
+
139
+ **The 3 Core Node Types:**
140
+
141
+ | Node Type | Purpose | Does Work? |
142
+ |-----------|---------|-----------|
143
+ | `tool` | Execute Python functions | ✅ Yes |
144
+ | `llm` | Generate text via LLM | ✅ Yes |
145
+ | `router` | Evaluate conditions, emit signals | ❌ Pure routing |
146
+
147
+ With these three core nodes, you can build any workflow pattern—including custom agent loops.
148
+
149
+ **Additional Node Types (Convenience):**
150
+
151
+ | Node Type | Purpose | Description |
152
+ |-----------|---------|-------------|
153
+ | `agent` | Autonomous reasoning with tools | Built-in ReAct loop |
154
+ | `child` | Spawn sub-workflows | Sub-orchestration |
155
+
156
+ **Key Properties:**
157
+ - Nodes are stateless—all state lives in Context
158
+ - Nodes communicate only via Signals
159
+ - Nodes are created by factory functions
160
+ - Multiple instances of the same type can exist
161
+
162
+ ```yaml
163
+ ValidateUser:
164
+ node_type: router
165
+ event_triggers: [START]
166
+ ...
167
+
168
+ ValidatePayment:
169
+ node_type: router
170
+ event_triggers: [USER_VALID]
171
+ ...
172
+ ```
173
+
174
+ ---
175
+
176
+ ## 6. Callers
177
+
178
+ **What:** Functions that control how signals are broadcast.
179
+
180
+ **Purpose:** The communication layer. Enables distribution without changing workflows.
181
+
182
+ ```python
183
+ def broadcast_signals_caller(id: str, signals: List[str]) -> None:
184
+ broadcast_signals(id, signals, nodes, backends)
185
+ ```
186
+
187
+ **Key Properties:**
188
+ - The default caller is synchronous and in-process
189
+ - Custom callers enable distribution (HTTP, Lambda, SQS, etc.)
190
+ - Callers wrap `broadcast_signals` with additional behavior
191
+ - The same workflow works with any caller
192
+
193
+ **Distribution Examples:**
194
+ - **HTTP Caller:** Signals become webhook POSTs
195
+ - **Lambda Caller:** Signals trigger AWS Lambda functions
196
+ - **SQS Caller:** Signals become queue messages
197
+
198
+ See [Chapter 10: Infrastructure](../guide_10_infrastructure.md) for implementation details.
199
+
200
+ ---
201
+
202
+ ## 6. Identities
203
+
204
+ **What:** System prompts for LLM and Agent nodes.
205
+
206
+ **Purpose:** Define roles and consistent behavior for LLM calls without repeating in every prompt.
207
+
208
+ ```yaml
209
+ identities:
210
+ analyst: |
211
+ You are a senior data analyst. Be thorough and precise.
212
+ Always cite sources when making claims.
213
+ reviewer: |
214
+ You are a code reviewer. Focus on correctness and maintainability.
215
+ ```
216
+
217
+ **Key Properties:**
218
+ - Defined in config YAML alongside workflows
219
+ - Referenced by `identity` field in LLM/Agent nodes
220
+ - Stored by `IdentityBackend`
221
+ - Keyed by `main_execution_id` (child workflows access parent's identities)
222
+ - Removes the need to specify role in every prompt
223
+
224
+ **Usage in Nodes:**
225
+ ```yaml
226
+ Analyzer:
227
+ node_type: llm
228
+ identity: analyst # References identity defined above
229
+ prompt: "Analyze: {{ context.input }}"
230
+ ```
231
+
232
+ See [Chapter 7: Identity](../guide_07_identity.md) for details.
233
+
234
+ ---
235
+
236
+ ## 7. Context Schema
237
+
238
+ **What:** Type definitions for context fields.
239
+
240
+ **Purpose:** Validate LLM output matches expected types before downstream use.
241
+
242
+ ```yaml
243
+ context_schema:
244
+ summary:
245
+ type: string
246
+ description: A one-sentence summary
247
+ result:
248
+ type: object
249
+ description: The workflow result
250
+ ```
251
+
252
+ **Key Properties:**
253
+ - Defined in config YAML alongside workflows
254
+ - Validates LLM `output_field` values
255
+ - Stored by `ContextSchemaBackend`
256
+ - Keyed by `main_execution_id` (child workflows access parent's schema)
257
+ - Removes the need to specify output format in every prompt
258
+
259
+ **Available Types:**
260
+ | Type | Python Type |
261
+ |------|-------------|
262
+ | `string` | `str` |
263
+ | `integer` | `int` |
264
+ | `number` | `float` |
265
+ | `boolean` | `bool` |
266
+ | `object` | `dict` |
267
+ | `list` | `list` |
268
+
269
+ See [Chapter 6: Context Schema](../guide_06_schema.md) for details.
270
+
271
+ ---
272
+
273
+ ## How Primitives Interact
274
+
275
+ ```
276
+ ┌─────────────────────────────────────────────────────────────┐
277
+ │ CONFIG (YAML) │
278
+ │ workflows, identities, context_schema │
279
+ └─────────────────────────────────────────────────────────────┘
280
+
281
+
282
+ ┌─────────────────────────────────────────────────────────────┐
283
+ │ ORCHESTRATION │
284
+ │ Parses config, initializes backends, broadcasts signals │
285
+ └─────────────────────────────────────────────────────────────┘
286
+
287
+
288
+ ┌─────────────────────────────────────────────────────────────┐
289
+ │ SIGNALS │
290
+ │ Flow through the system: START → VALID → COMPLETE │
291
+ └─────────────────────────────────────────────────────────────┘
292
+
293
+
294
+ ┌─────────────────────────────────────────────────────────────┐
295
+ │ NODES │
296
+ │ Triggered by signals, read/write Context, emit signals │
297
+ │ router | tool | llm | agent | child │
298
+ │ LLM/Agent nodes use: Identities + Context Schema │
299
+ └─────────────────────────────────────────────────────────────┘
300
+
301
+
302
+ ┌─────────────────────────────────────────────────────────────┐
303
+ │ CONTEXT │
304
+ │ Shared state: {"user": "alice", "result": "..."} │
305
+ │ Accessible via Jinja: {{ context.user }} │
306
+ └─────────────────────────────────────────────────────────────┘
307
+
308
+
309
+ ┌─────────────────────────────────────────────────────────────┐
310
+ │ BACKENDS │
311
+ │ context | workflow | identity | context_schema | telemetry │
312
+ └─────────────────────────────────────────────────────────────┘
313
+ ```
314
+
315
+ See [Chapter 9: Ecosystem](../guide_09_ecosystem.md) for multi-workflow patterns.
316
+
317
+ ---
318
+
319
+ ## Summary Table
320
+
321
+ | Primitive | What | Where Defined | Stored By |
322
+ |-----------|------|---------------|-----------|
323
+ | **Signals** | Named events | YAML `event_emissions` | — (transient) |
324
+ | **Context** | Shared state | Runtime | `ContextBackend` |
325
+ | **Workflows** | Node blueprints | YAML files | `WorkflowBackend` |
326
+ | **Backends** | Persistence | Python classes | — |
327
+ | **Nodes** | Execution units | YAML + factories | — |
328
+ | **Identities** | System prompts | Config YAML | `IdentityBackend` |
329
+ | **Context Schema** | Type validation | Config YAML | `ContextSchemaBackend` |
330
+
331
+ Master these 7 primitives, and you can build anything with SOE.