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.
- soe/builtin_tools/__init__.py +39 -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_workflows.py +63 -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_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/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 +118 -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 +1 -1
- 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/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.1.1.dist-info → soe_ai-0.1.3.dist-info}/METADATA +5 -5
- soe_ai-0.1.3.dist-info/RECORD +137 -0
- {soe_ai-0.1.1.dist-info → soe_ai-0.1.3.dist-info}/WHEEL +1 -1
- soe_ai-0.1.1.dist-info/RECORD +0 -10
- {soe_ai-0.1.1.dist-info → soe_ai-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {soe_ai-0.1.1.dist-info → soe_ai-0.1.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
|
|
2
|
+
# Backends: Pluggable Storage
|
|
3
|
+
|
|
4
|
+
Backends are **dumb data retrievers**—they store and retrieve data, nothing more. All business logic lives in the orchestration engine, not in backends.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## The Principle: Dumb Data Retrievers
|
|
9
|
+
|
|
10
|
+
Backends follow a simple principle:
|
|
11
|
+
|
|
12
|
+
1. **Store data** when told to
|
|
13
|
+
2. **Retrieve data** when asked
|
|
14
|
+
3. **No business logic** — no validation, no transformation, no side effects
|
|
15
|
+
|
|
16
|
+
This makes backends trivially swappable. Whether you use in-memory, local files, PostgreSQL, or DynamoDB, the orchestration engine works exactly the same.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## The 6 Backend Types
|
|
21
|
+
|
|
22
|
+
| Backend | Protocol | Required | Purpose |
|
|
23
|
+
|---------|----------|----------|---------|
|
|
24
|
+
| `context` | `ContextBackend` | ✅ Yes | Execution state dictionary |
|
|
25
|
+
| `workflow` | `WorkflowBackend` | ✅ Yes | Workflow definitions and current workflow name |
|
|
26
|
+
| `telemetry` | `TelemetryBackend` | ❌ Optional | Logs and events for debugging |
|
|
27
|
+
| `conversation_history` | `ConversationHistoryBackend` | ❌ Optional | LLM chat history per identity |
|
|
28
|
+
| `context_schema` | `ContextSchemaBackend` | ❌ Optional | Type validation for outputs |
|
|
29
|
+
| `identity` | `IdentityBackend` | ❌ Optional | System prompts for LLM identities |
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Quick Start: Built-in Backends
|
|
34
|
+
|
|
35
|
+
SOE provides two factory functions for common use cases:
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from soe.local_backends import create_in_memory_backends, create_local_backends
|
|
39
|
+
|
|
40
|
+
# For testing: everything in memory (lost on restart)
|
|
41
|
+
backends = create_in_memory_backends()
|
|
42
|
+
|
|
43
|
+
# For development: persisted to local files
|
|
44
|
+
backends = create_local_backends(
|
|
45
|
+
context_storage_dir="./data/contexts",
|
|
46
|
+
workflow_storage_dir="./data/workflows",
|
|
47
|
+
telemetry_storage_dir="./data/telemetry",
|
|
48
|
+
conversation_history_storage_dir="./data/conversations",
|
|
49
|
+
context_schema_storage_dir="./data/schemas",
|
|
50
|
+
identity_storage_dir="./data/identities",
|
|
51
|
+
)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Implementing Custom Backends
|
|
57
|
+
|
|
58
|
+
Each backend is a Python Protocol (structural typing). Implement the required methods and you're done.
|
|
59
|
+
|
|
60
|
+
### ContextBackend (Required)
|
|
61
|
+
|
|
62
|
+
Stores execution context—the shared state dictionary.
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from typing import Dict, Any
|
|
66
|
+
|
|
67
|
+
class MyContextBackend:
|
|
68
|
+
"""Store contexts in your database."""
|
|
69
|
+
|
|
70
|
+
def save_context(self, id: str, context: Dict[str, Any]) -> None:
|
|
71
|
+
"""Save context for an execution ID."""
|
|
72
|
+
# Your database write here
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
def get_context(self, id: str) -> Dict[str, Any]:
|
|
76
|
+
"""Get context for an execution ID."""
|
|
77
|
+
# Your database read here
|
|
78
|
+
return {}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### WorkflowBackend (Required)
|
|
82
|
+
|
|
83
|
+
Stores workflow definitions and tracks current workflow.
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from typing import Dict, Any
|
|
87
|
+
|
|
88
|
+
class MyWorkflowBackend:
|
|
89
|
+
"""Store workflows in your database."""
|
|
90
|
+
|
|
91
|
+
def save_workflows_registry(self, id: str, workflows: Dict[str, Any]) -> None:
|
|
92
|
+
"""Save all workflows for an execution."""
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
def get_workflows_registry(self, id: str) -> Dict[str, Any]:
|
|
96
|
+
"""Get all workflows for an execution."""
|
|
97
|
+
return {}
|
|
98
|
+
|
|
99
|
+
def save_current_workflow_name(self, id: str, name: str) -> None:
|
|
100
|
+
"""Save current workflow name (for child transitions)."""
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
def get_current_workflow_name(self, id: str) -> str:
|
|
104
|
+
"""Get current workflow name."""
|
|
105
|
+
return ""
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### TelemetryBackend (Optional)
|
|
109
|
+
|
|
110
|
+
Logs events for debugging and observability.
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
class MyTelemetryBackend:
|
|
114
|
+
"""Log events to your observability stack."""
|
|
115
|
+
|
|
116
|
+
def log_event(self, execution_id: str, event_type: str, **event_data) -> None:
|
|
117
|
+
"""Log an event. event_data contains timestamp and event-specific fields."""
|
|
118
|
+
pass
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### ConversationHistoryBackend (Optional)
|
|
122
|
+
|
|
123
|
+
Persists LLM conversation history per identity.
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from typing import List, Dict, Any
|
|
127
|
+
|
|
128
|
+
class MyConversationHistoryBackend:
|
|
129
|
+
"""Store chat history in your database."""
|
|
130
|
+
|
|
131
|
+
def get_conversation_history(self, identity: str) -> List[Dict[str, Any]]:
|
|
132
|
+
"""Get conversation history. Returns list of {role, content} dicts."""
|
|
133
|
+
return []
|
|
134
|
+
|
|
135
|
+
def append_to_conversation_history(self, identity: str, entry: Dict[str, Any]) -> None:
|
|
136
|
+
"""Append a single message to history."""
|
|
137
|
+
pass
|
|
138
|
+
|
|
139
|
+
def save_conversation_history(self, identity: str, history: List[Dict[str, Any]]) -> None:
|
|
140
|
+
"""Replace entire history."""
|
|
141
|
+
pass
|
|
142
|
+
|
|
143
|
+
def delete_conversation_history(self, identity: str) -> None:
|
|
144
|
+
"""Delete all history for an identity."""
|
|
145
|
+
pass
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### ContextSchemaBackend (Optional)
|
|
149
|
+
|
|
150
|
+
Stores output field type schemas for validation.
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
from typing import Dict, Any, Optional
|
|
154
|
+
|
|
155
|
+
class MyContextSchemaBackend:
|
|
156
|
+
"""Store context schemas for output validation."""
|
|
157
|
+
|
|
158
|
+
def save_context_schema(self, execution_id: str, schema: Dict[str, Any]) -> None:
|
|
159
|
+
"""Save context schema for an execution."""
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
def get_context_schema(self, execution_id: str) -> Optional[Dict[str, Any]]:
|
|
163
|
+
"""Get context schema. Returns None if not found."""
|
|
164
|
+
return None
|
|
165
|
+
|
|
166
|
+
def delete_context_schema(self, execution_id: str) -> bool:
|
|
167
|
+
"""Delete context schema. Returns True if deleted."""
|
|
168
|
+
return False
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### IdentityBackend (Optional)
|
|
172
|
+
|
|
173
|
+
Stores LLM identity system prompts.
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
from typing import Dict, Optional
|
|
177
|
+
|
|
178
|
+
class MyIdentityBackend:
|
|
179
|
+
"""Store identity definitions."""
|
|
180
|
+
|
|
181
|
+
def save_identities(self, execution_id: str, identities: Dict[str, str]) -> None:
|
|
182
|
+
"""Save identity definitions. Format: {identity_name: system_prompt}"""
|
|
183
|
+
pass
|
|
184
|
+
|
|
185
|
+
def get_identities(self, execution_id: str) -> Optional[Dict[str, str]]:
|
|
186
|
+
"""Get all identities for an execution."""
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
def get_identity(self, execution_id: str, identity_name: str) -> Optional[str]:
|
|
190
|
+
"""Get a specific identity's system prompt."""
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
def delete_identities(self, execution_id: str) -> bool:
|
|
194
|
+
"""Delete all identities for an execution."""
|
|
195
|
+
return False
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Combining Into a Backends Container
|
|
201
|
+
|
|
202
|
+
Pass your backends to orchestrate via a container object:
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
class MyBackends:
|
|
206
|
+
def __init__(self):
|
|
207
|
+
self.context = MyContextBackend()
|
|
208
|
+
self.workflow = MyWorkflowBackend()
|
|
209
|
+
self.telemetry = MyTelemetryBackend() # or None
|
|
210
|
+
self.conversation_history = MyConversationHistoryBackend() # or None
|
|
211
|
+
self.context_schema = MyContextSchemaBackend() # or None
|
|
212
|
+
self.identity = MyIdentityBackend() # or None
|
|
213
|
+
|
|
214
|
+
backends = MyBackends()
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Database Recommendations
|
|
220
|
+
|
|
221
|
+
### Single Database, Multiple Tables
|
|
222
|
+
|
|
223
|
+
We recommend using **one database** with **separate tables** for each backend:
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
your_database/
|
|
227
|
+
├── contexts # ContextBackend
|
|
228
|
+
├── workflows # WorkflowBackend
|
|
229
|
+
├── telemetry # TelemetryBackend
|
|
230
|
+
├── conversations # ConversationHistoryBackend
|
|
231
|
+
├── context_schemas # ContextSchemaBackend
|
|
232
|
+
└── identities # IdentityBackend
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
This gives you:
|
|
236
|
+
- **Transactional consistency** across backends
|
|
237
|
+
- **Simpler deployment** (one connection string)
|
|
238
|
+
- **Easier backups** (one database to backup)
|
|
239
|
+
|
|
240
|
+
### Example: PostgreSQL Tables
|
|
241
|
+
|
|
242
|
+
```sql
|
|
243
|
+
CREATE TABLE contexts (
|
|
244
|
+
execution_id VARCHAR PRIMARY KEY,
|
|
245
|
+
context JSONB NOT NULL,
|
|
246
|
+
created_at TIMESTAMP DEFAULT NOW()
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
CREATE TABLE workflows (
|
|
250
|
+
execution_id VARCHAR PRIMARY KEY,
|
|
251
|
+
registry JSONB NOT NULL,
|
|
252
|
+
current_workflow VARCHAR NOT NULL
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
CREATE TABLE telemetry (
|
|
256
|
+
id SERIAL PRIMARY KEY,
|
|
257
|
+
execution_id VARCHAR NOT NULL,
|
|
258
|
+
event_type VARCHAR NOT NULL,
|
|
259
|
+
event_data JSONB,
|
|
260
|
+
timestamp TIMESTAMP DEFAULT NOW()
|
|
261
|
+
);
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Key Insight: Execution ID as Primary Key
|
|
267
|
+
|
|
268
|
+
Most backends key data by `execution_id`:
|
|
269
|
+
|
|
270
|
+
- **Context**: One context dict per execution
|
|
271
|
+
- **Workflows**: Workflow registry per execution
|
|
272
|
+
- **Telemetry**: Events indexed by execution
|
|
273
|
+
|
|
274
|
+
For **conversation history**, **context schema**, and **identities**, data is keyed by `main_execution_id`—this allows child workflows to access parent's definitions.
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## See Also
|
|
279
|
+
|
|
280
|
+
- [Chapter 10: Infrastructure](../guide_10_infrastructure.md) — Full implementation examples
|
|
281
|
+
- [Primitives Overview](primitives.md) — All 7 SOE primitives
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
|
|
2
|
+
# Context: State Management with History
|
|
3
|
+
|
|
4
|
+
Context is the shared state dictionary for an execution. Unlike simple key-value stores, SOE context maintains **history** for each field—every update is appended, not replaced.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Context as History
|
|
9
|
+
|
|
10
|
+
Each context field is stored as a **list** of values:
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
# After three tool calls that update "result":
|
|
14
|
+
context = {
|
|
15
|
+
"result": ["first", "second", "third"], # History preserved
|
|
16
|
+
"user_id": ["alice"], # Single value, still a list
|
|
17
|
+
"__operational__": {...} # Internal fields not wrapped
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
When you read a field:
|
|
22
|
+
- **Reading normally** returns the **latest** value (last item)
|
|
23
|
+
- **Reading with `| accumulated`** returns the **full history** (all items)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Reading Context in Jinja
|
|
28
|
+
|
|
29
|
+
### Get Latest Value (Default)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
```yaml
|
|
33
|
+
prompt: |
|
|
34
|
+
User {{ context.user_id }} asked: {{ context.user_request }}
|
|
35
|
+
|
|
36
|
+
Previous result: {{ context.result }}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
This returns the **most recent** value for each field.
|
|
41
|
+
|
|
42
|
+
### Get Full History with `| accumulated`
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
```yaml
|
|
46
|
+
prompt: |
|
|
47
|
+
All results so far:
|
|
48
|
+
{% for r in context.result | accumulated %}
|
|
49
|
+
- {{ r }}
|
|
50
|
+
{% endfor %}
|
|
51
|
+
|
|
52
|
+
condition: "{{ (context.result | accumulated) | length >= 3 }}"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
Use `| accumulated` when you need:
|
|
57
|
+
- All previous LLM responses
|
|
58
|
+
- Aggregating results from multiple tool calls
|
|
59
|
+
- Checking how many updates have occurred
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Context in Conditions
|
|
64
|
+
|
|
65
|
+
Context is available in all condition expressions:
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
```yaml
|
|
69
|
+
event_emissions:
|
|
70
|
+
# Check latest value
|
|
71
|
+
- signal_name: HAS_RESULT
|
|
72
|
+
condition: "{{ context.result is defined }}"
|
|
73
|
+
|
|
74
|
+
# Check history length
|
|
75
|
+
- signal_name: ENOUGH_DATA
|
|
76
|
+
condition: "{{ (context.data | accumulated) | length >= 5 }}"
|
|
77
|
+
|
|
78
|
+
# Complex conditions
|
|
79
|
+
- signal_name: VALIDATED
|
|
80
|
+
condition: "{{ context.user_id and context.user_profile.verified }}"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Context in Prompts
|
|
87
|
+
|
|
88
|
+
Full context is available in LLM/Agent prompts:
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
```yaml
|
|
92
|
+
prompt: |
|
|
93
|
+
You are helping user {{ context.user_id }}.
|
|
94
|
+
|
|
95
|
+
Their profile: {{ context.user_profile | tojson }}
|
|
96
|
+
|
|
97
|
+
Conversation so far:
|
|
98
|
+
{% for msg in context.messages | accumulated %}
|
|
99
|
+
{{ msg.role }}: {{ msg.content }}
|
|
100
|
+
{% endfor %}
|
|
101
|
+
|
|
102
|
+
Current request: {{ context.user_request }}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## How Context is Updated
|
|
109
|
+
|
|
110
|
+
### Tool Nodes
|
|
111
|
+
|
|
112
|
+
Tool output is saved via `output_field`:
|
|
113
|
+
|
|
114
|
+
```yaml
|
|
115
|
+
ProcessData:
|
|
116
|
+
node_type: tool
|
|
117
|
+
tool_name: process
|
|
118
|
+
input_fields: [raw_data]
|
|
119
|
+
output_field: processed # Tool return value saved here
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Each execution **appends** to the field's history.
|
|
123
|
+
|
|
124
|
+
### LLM/Agent Nodes
|
|
125
|
+
|
|
126
|
+
LLM response is saved via `output_field`:
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
```yaml
|
|
130
|
+
Summarize:
|
|
131
|
+
node_type: llm
|
|
132
|
+
prompt: "Summarize: {{ context.data }}"
|
|
133
|
+
output_field: summary # LLM response saved here
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
### Initial Context
|
|
138
|
+
|
|
139
|
+
When starting orchestration:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
orchestrate(
|
|
143
|
+
config=workflow,
|
|
144
|
+
initial_workflow_name="my_workflow",
|
|
145
|
+
initial_signals=["START"],
|
|
146
|
+
initial_context={
|
|
147
|
+
"user_id": "alice",
|
|
148
|
+
"user_request": "Help me with X"
|
|
149
|
+
},
|
|
150
|
+
backends=backends,
|
|
151
|
+
broadcast_signals_caller=broadcast,
|
|
152
|
+
)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Initial context fields are wrapped in lists automatically:
|
|
156
|
+
```python
|
|
157
|
+
# Internal representation:
|
|
158
|
+
{"user_id": ["alice"], "user_request": ["Help me with X"]}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## The `__operational__` Field
|
|
164
|
+
|
|
165
|
+
Context includes a special `__operational__` field that tracks execution metadata:
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
context["__operational__"] = {
|
|
169
|
+
"signals": ["START", "VALIDATED"], # Signals emitted
|
|
170
|
+
"nodes": {"ValidateInput": 1}, # Node execution counts
|
|
171
|
+
"llm_calls": 3, # Total LLM calls
|
|
172
|
+
"tool_calls": 5, # Total tool calls
|
|
173
|
+
"errors": 0, # Error count
|
|
174
|
+
"main_execution_id": "abc-123" # Root execution ID
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Important**: Fields starting with `__` are **not** wrapped in lists—they store direct values.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Practical Examples
|
|
183
|
+
|
|
184
|
+
### Aggregating Multiple Results
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
```yaml
|
|
188
|
+
# Fan-out pattern: multiple child workflows write to same field
|
|
189
|
+
AggregateResults:
|
|
190
|
+
node_type: router
|
|
191
|
+
event_triggers: [CHILD_COMPLETE]
|
|
192
|
+
event_emissions:
|
|
193
|
+
- signal_name: ALL_DONE
|
|
194
|
+
condition: "{{ (context.child_results | accumulated) | length >= 3 }}"
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
### Retry with History
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
```yaml
|
|
202
|
+
RetryLogic:
|
|
203
|
+
node_type: router
|
|
204
|
+
event_triggers: [FAILED]
|
|
205
|
+
event_emissions:
|
|
206
|
+
- signal_name: RETRY
|
|
207
|
+
condition: "{{ (context.attempts | accumulated) | length < 3 }}"
|
|
208
|
+
- signal_name: GIVE_UP
|
|
209
|
+
condition: "{{ (context.attempts | accumulated) | length >= 3 }}"
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
### Building Context Over Time
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
```yaml
|
|
217
|
+
# Each step adds to enrichment
|
|
218
|
+
EnrichStep1:
|
|
219
|
+
node_type: tool
|
|
220
|
+
tool_name: get_user_profile
|
|
221
|
+
input_fields: [user_id]
|
|
222
|
+
output_field: enrichment
|
|
223
|
+
|
|
224
|
+
EnrichStep2:
|
|
225
|
+
node_type: tool
|
|
226
|
+
tool_name: get_purchase_history
|
|
227
|
+
input_fields: [user_id]
|
|
228
|
+
output_field: enrichment # Same field, appends
|
|
229
|
+
|
|
230
|
+
# Later: context.enrichment | accumulated = [profile, history]
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Context Backend
|
|
237
|
+
|
|
238
|
+
Context is persisted via `ContextBackend`:
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
# Save
|
|
242
|
+
backends.context.save_context(execution_id, context)
|
|
243
|
+
|
|
244
|
+
# Retrieve
|
|
245
|
+
context = backends.context.get_context(execution_id)
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
See [Backends](backends.md) for implementation details.
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## See Also
|
|
253
|
+
|
|
254
|
+
- [Backends](backends.md) — How context is persisted
|
|
255
|
+
- [Signals](signals.md) — How signals trigger nodes based on context
|
|
256
|
+
- [Primitives Overview](primitives.md) — All 7 SOE primitives
|