soe-ai 0.2.0b1__py3-none-any.whl → 0.2.0b3__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/broker.py CHANGED
@@ -4,15 +4,18 @@ from .types import Backends, BroadcastSignalsCaller, NodeCaller, EventTypes, Wor
4
4
  from .lib.register_event import register_event
5
5
  from .lib.yaml_parser import parse_yaml
6
6
  from .lib.operational import add_operational_state
7
- from .lib.context_fields import set_field
8
7
  from .lib.parent_sync import get_signals_for_parent
9
8
  from .lib.inheritance import (
10
9
  inherit_config,
11
- inherit_context,
12
10
  extract_and_save_config_sections,
11
+ prepare_initial_context,
12
+ )
13
+ from .validation import (
14
+ validate_config,
15
+ validate_operational,
16
+ validate_orchestrate_params,
17
+ validate_initial_workflow,
13
18
  )
14
- from .validation import validate_config, validate_operational, validate_orchestrate_params
15
- from .types import WorkflowValidationError
16
19
 
17
20
 
18
21
  def orchestrate(
@@ -70,17 +73,15 @@ def orchestrate(
70
73
 
71
74
  id = str(uuid4())
72
75
 
76
+ parsed_registry = {}
73
77
  if inherit_config_from_id:
74
78
  register_event(
75
79
  backends, id, EventTypes.CONFIG_INHERITANCE_START,
76
80
  {"source_execution_id": inherit_config_from_id}
77
81
  )
78
82
  parsed_registry = inherit_config(inherit_config_from_id, id, backends)
79
- if config:
80
- validate_config(config)
81
- parsed_config = parse_yaml(config)
82
- parsed_registry = extract_and_save_config_sections(parsed_config, id, backends)
83
- else:
83
+
84
+ if config:
84
85
  validate_config(config)
85
86
  parsed_config = parse_yaml(config)
86
87
  parsed_registry = extract_and_save_config_sections(parsed_config, id, backends)
@@ -92,32 +93,13 @@ def orchestrate(
92
93
 
93
94
  backends.workflow.save_workflows_registry(id, parsed_registry)
94
95
 
95
- if initial_workflow_name not in parsed_registry:
96
- available = list(parsed_registry.keys())
97
- raise WorkflowValidationError(
98
- f"Workflow '{initial_workflow_name}' not found in config. "
99
- f"Available workflows: {available}"
100
- )
96
+ validate_initial_workflow(initial_workflow_name, parsed_registry)
101
97
 
102
98
  backends.workflow.save_current_workflow_name(id, initial_workflow_name)
103
99
 
104
- if inherit_context_from_id:
105
- register_event(
106
- backends, id, EventTypes.CONTEXT_INHERITANCE_START,
107
- )
108
- context = inherit_context(inherit_context_from_id, backends)
109
- if initial_context:
110
- register_event(
111
- backends, id, EventTypes.CONTEXT_MERGE,
112
- {"fields": list(initial_context.keys())}
113
- )
114
- for field, value in initial_context.items():
115
- set_field(context, field, value)
116
- else:
117
- context = {
118
- k: [v] if not k.startswith("__") else v
119
- for k, v in initial_context.items()
120
- }
100
+ context = prepare_initial_context(
101
+ id, initial_context, backends, inherit_context_from_id
102
+ )
121
103
 
122
104
  context = add_operational_state(id, context)
123
105
  backends.context.save_context(id, context)
@@ -5,6 +5,8 @@ Allows agents to write context fields dynamically.
5
5
 
6
6
  from typing import Any, Dict
7
7
 
8
+ from ..lib.context_fields import set_field
9
+
8
10
 
9
11
  def create_soe_update_context_tool(backends, execution_id: str, tools_registry=None):
10
12
  """
@@ -41,9 +43,10 @@ def create_soe_update_context_tool(backends, execution_id: str, tools_registry=N
41
43
  if not filtered_updates:
42
44
  return {"status": "no valid updates (operational fields cannot be updated)"}
43
45
 
44
- # Get current context and update
46
+ # Get current context and update using set_field for proper list wrapping
45
47
  context = backends.context.get_context(execution_id)
46
- context.update(filtered_updates)
48
+ for field, value in filtered_updates.items():
49
+ set_field(context, field, value)
47
50
  backends.context.save_context(execution_id, context)
48
51
 
49
52
  return {
soe/docs/guide_01_tool.md CHANGED
@@ -39,6 +39,58 @@ example_workflow:
39
39
  3. **`output_field`**: Where to store the result in context.
40
40
  4. **`event_emissions`**: Signals to emit after execution (conditions evaluate `result`).
41
41
 
42
+ ## Passing Parameters to Tools
43
+
44
+ There are two ways to pass parameters to a tool: **inline parameters** (hardcoded in YAML) or **context parameters** (dynamic from context).
45
+
46
+ ### Option 1: Inline Parameters (Static)
47
+
48
+ Use `parameters` to specify tool arguments directly in the workflow YAML:
49
+
50
+ ```yaml
51
+ example_workflow:
52
+ ReadToolDocs:
53
+ node_type: tool
54
+ event_triggers: [START]
55
+ tool_name: soe_explore_docs
56
+ parameters:
57
+ path: "soe/docs/guide_01_tool.md"
58
+ action: "read"
59
+ output_field: tool_documentation
60
+ event_emissions:
61
+ - signal_name: DOCS_READY
62
+ ```
63
+
64
+ **Jinja templates work in parameters:**
65
+
66
+ ```yaml
67
+ example_workflow:
68
+ FetchUserData:
69
+ node_type: tool
70
+ event_triggers: [START]
71
+ tool_name: fetch_data
72
+ parameters:
73
+ user_id: "{{ context.current_user_id }}"
74
+ include_history: true
75
+ output_field: user_data
76
+ event_emissions:
77
+ - signal_name: DATA_FETCHED
78
+ ```
79
+
80
+ ### Option 2: Context Parameters (Dynamic)
81
+
82
+ Use `context_parameter_field` when the parameters come from another node's output or initial context:
83
+
84
+ ```yaml
85
+ example_workflow:
86
+ SendEmail:
87
+ node_type: tool
88
+ event_triggers: [START]
89
+ tool_name: send_email
90
+ context_parameter_field: email_data
91
+ output_field: email_result
92
+ ```
93
+
42
94
  ### Understanding context_parameter_field
43
95
 
44
96
  The `context_parameter_field` specifies which context field contains the parameters to pass to your tool. This field must contain a dictionary that will be unpacked as keyword arguments.
soe/docs/guide_02_llm.md CHANGED
@@ -79,7 +79,7 @@ This pattern is incredibly useful for:
79
79
 
80
80
  ## LLM Signal Selection (Resolution Step)
81
81
 
82
- When an LLM node has multiple signals with **conditions** (plain text, not Jinja), the LLM itself decides which signal to emit.
82
+ When an LLM node has multiple signals with **conditions** (plain text, not Jinja), the LLM itself decides which signals to emit.
83
83
 
84
84
  ### The Workflow
85
85
 
@@ -103,8 +103,8 @@ example_workflow:
103
103
 
104
104
  1. The LLM analyzes the sentiment
105
105
  2. SOE sees multiple signals with plain-text conditions (no `{{ }}`)
106
- 3. SOE asks the LLM: "Based on your analysis, which signal should be emitted?" using the conditions as descriptions
107
- 4. The LLM returns the appropriate signal (POSITIVE, NEGATIVE, or NEUTRAL)
106
+ 3. SOE asks the LLM: "Select ALL signals that apply" using the conditions as descriptions
107
+ 4. The LLM returns a list of matching signals (can be none, one, or multiple)
108
108
 
109
109
  This is called the **resolution step** - it lets the LLM make routing decisions based on its understanding.
110
110
 
@@ -115,7 +115,7 @@ The `condition` field controls how signals are emitted:
115
115
  | Condition | Behavior |
116
116
  |-----------|----------|
117
117
  | **No condition** | Signal is always emitted |
118
- | **Plain text** | Semantic—LLM selects which signal to emit based on the description |
118
+ | **Plain text** | Semantic—LLM selects any/all signals that apply based on the descriptions |
119
119
  | **Jinja template (`{{ }}`)** | Programmatic—evaluated against `context`, emits if truthy |
120
120
 
121
121
  **How SOE decides:**
@@ -123,7 +123,7 @@ The `condition` field controls how signals are emitted:
123
123
  1. **No conditions**: All signals emit unconditionally after node execution
124
124
  2. **Has conditions**: SOE checks if they contain `{{ }}`:
125
125
  - **Yes (Jinja)**: Evaluate expression against `context`—emit if result is truthy
126
- - **No (plain text)**: Ask LLM to choose which signal best matches its output
126
+ - **No (plain text)**: Ask LLM to select which signals apply (multi-select)
127
127
 
128
128
  ## Testing LLM Nodes
129
129
 
soe/docs/index.md CHANGED
@@ -81,10 +81,12 @@ Built-in tools enable granular runtime modifications:
81
81
 
82
82
  | Built-in | Description |
83
83
  |----------|-------------|
84
- | [explore_docs](builtins/explore_docs.md) | Make SOE self-aware by exploring its documentation |
84
+ | [soe_explore_docs](builtins/soe_explore_docs.md) | Make SOE self-aware by exploring its documentation |
85
85
  | [workflows](builtins/workflows.md) | Query, inject, and modify workflows at runtime |
86
86
  | [context](builtins/context.md) | Read, update, and copy execution context |
87
87
  | [tools](builtins/tools.md) | Discover and dynamically call registered tools |
88
+ | [identity](builtins/identity.md) | Query, inject, and remove identity definitions |
89
+ | [context_schema](builtins/context_schema.md) | Query, inject, and remove context schema fields |
88
90
 
89
91
  ---
90
92
 
@@ -44,6 +44,7 @@ Complete reference for all node configuration parameters across all node types.
44
44
  |-----------|------|--------|-----|-------|------|-------|-------------|
45
45
  | `tool_name` | `str` | ✗ | ✗ | ✗ | **R** | ✗ | Tool to execute from registry |
46
46
  | `tools` | `List[str]` | ✗ | ✗ | **O** | ✗ | ✗ | Tool names available to agent |
47
+ | `parameters` | `Dict` | ✗ | ✗ | ✗ | **O** | ✗ | Inline tool kwargs (supports Jinja) |
47
48
  | `context_parameter_field` | `str` | ✗ | ✗ | ✗ | **O** | ✗ | Context field containing tool kwargs |
48
49
 
49
50
  ### Child Workflow Parameters
@@ -122,7 +122,7 @@ example_workflow:
122
122
 
123
123
  Both `PROCESSING_DONE` and `LOG_EVENT` emit every time the node runs.
124
124
 
125
- > **Note**: For Router nodes, multiple unconditional signals all emit simultaneously (fan-out pattern). For LLM/Agent nodes with multiple signals, the LLM must select one - use Jinja conditions like `{{ true }}` if you want all signals to emit.
125
+ > **Note**: For Router nodes, multiple unconditional signals all emit simultaneously (fan-out pattern). For LLM/Agent nodes with multiple signals, the LLM can select any/all that apply—including none.
126
126
 
127
127
  ### Mode 2: Jinja Template (Programmatic)
128
128
 
@@ -207,8 +207,9 @@ The behavior depends on the node type:
207
207
  │ └─ Zero signals? → Nothing emitted │
208
208
  │ └─ Single signal? → Emit unconditionally │
209
209
  │ └─ Multiple signals? │
210
- │ └─ LLM selects ONE signal
210
+ │ └─ LLM selects ANY/ALL that apply
211
211
  │ (uses conditions as semantic descriptions) │
212
+ │ (can select none, one, or multiple) │
212
213
  │ │
213
214
  └─────────────────────────────────────────────────────────────┘
214
215
 
@@ -300,7 +301,7 @@ example_workflow:
300
301
  condition: "The message is factual, neutral, or emotionally ambiguous"
301
302
  ```
302
303
 
303
- **LLM Selection Mechanism**: SOE adds a `selected_signal` field to the response model, forcing the LLM to choose from the options. The condition text serves as the description.
304
+ **LLM Selection Mechanism**: SOE adds a `selected_signals` field to the response model, allowing the LLM to select any/all signals that apply. The condition text serves as the description.
304
305
 
305
306
  ---
306
307
 
@@ -627,26 +628,26 @@ When the child updates these keys, they're automatically copied to the parent's
627
628
 
628
629
  ## LLM Signal Selection: Under the Hood
629
630
 
630
- When the LLM selects a signal, SOE:
631
+ When the LLM selects signals, SOE:
631
632
 
632
- 1. **Builds a response model** with a `selected_signal` field:
633
+ 1. **Builds a response model** with a `selected_signals` field (list):
633
634
  ```python
634
635
  class Response(BaseModel):
635
636
  response: str
636
- selected_signal: Literal["POSITIVE", "NEGATIVE", "NEUTRAL"]
637
+ selected_signals: List[Literal["POSITIVE", "NEGATIVE", "NEUTRAL"]] = []
637
638
  ```
638
639
 
639
640
  2. **Provides descriptions** from the `condition` field:
640
641
  ```
641
- Select one of these signals based on your response:
642
+ Select ALL signals that apply (can be none, one, or multiple):
642
643
  - POSITIVE: The message expresses positive sentiment
643
644
  - NEGATIVE: The message expresses negative sentiment
644
645
  - NEUTRAL: The message is neutral
645
646
  ```
646
647
 
647
- 3. **Extracts the selection** and emits that signal.
648
+ 3. **Extracts the selection** and emits all selected signals (can be empty).
648
649
 
649
- This is why plain-text conditions are called "semantic"—the LLM understands the description and makes a judgment call.
650
+ This is why plain-text conditions are called "semantic"—the LLM understands the descriptions and selects all that apply.
650
651
 
651
652
  ---
652
653