soe-ai 0.1.0__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 ADDED
@@ -0,0 +1,50 @@
1
+ """
2
+ Orchestration Engine - MVP
3
+ Agent orchestration with event-driven workflow engine
4
+ """
5
+
6
+ from .broker import orchestrate, broadcast_signals
7
+ from .nodes import (
8
+ AgentRequest,
9
+ AgentResponse,
10
+ ToolNodeConfigurationError,
11
+ ToolParameterError,
12
+ )
13
+ from .types import (
14
+ # Backend protocols - for building custom backends
15
+ Backends,
16
+ ContextBackend,
17
+ WorkflowBackend,
18
+ TelemetryBackend,
19
+ ConversationHistoryBackend,
20
+ ContextSchemaBackend,
21
+ IdentityBackend,
22
+ # LLM protocol - for integrating custom LLM providers
23
+ CallLlm,
24
+ )
25
+ from .init import create_all_nodes, setup_orchestration
26
+
27
+ __all__ = [
28
+ # Core functions
29
+ "orchestrate",
30
+ "broadcast_signals",
31
+ # Easy setup
32
+ "create_all_nodes",
33
+ "setup_orchestration",
34
+ # Agent types
35
+ "AgentRequest",
36
+ "AgentResponse",
37
+ # Tool errors
38
+ "ToolNodeConfigurationError",
39
+ "ToolParameterError",
40
+ # Backend protocols
41
+ "Backends",
42
+ "ContextBackend",
43
+ "WorkflowBackend",
44
+ "TelemetryBackend",
45
+ "ConversationHistoryBackend",
46
+ "ContextSchemaBackend",
47
+ "IdentityBackend",
48
+ # LLM protocol
49
+ "CallLlm",
50
+ ]
soe/broker.py ADDED
@@ -0,0 +1,169 @@
1
+ from uuid import uuid4
2
+ from typing import Dict, List, Any, Union, Callable, Optional
3
+ from .types import Backends, BroadcastSignalsCaller
4
+ from .local_backends import EventTypes
5
+ from .lib.register_event import register_event
6
+ from .lib.yaml_parser import parse_yaml
7
+ from .lib.operational import add_operational_state
8
+ from .lib.context_fields import set_field
9
+ from .lib.parent_sync import get_signals_for_parent
10
+ from .lib.inheritance import (
11
+ inherit_config,
12
+ inherit_context,
13
+ extract_and_save_config_sections,
14
+ )
15
+ from .validation import validate_config, validate_operational, validate_orchestrate_params
16
+ from .types import WorkflowValidationError
17
+
18
+
19
+ def orchestrate(
20
+ config: Optional[Union[str, Dict[str, Any]]],
21
+ initial_workflow_name: str,
22
+ initial_signals: List[str],
23
+ initial_context: Dict[str, Any],
24
+ backends: Backends,
25
+ broadcast_signals_caller: BroadcastSignalsCaller,
26
+ inherit_config_from_id: Optional[str] = None,
27
+ inherit_context_from_id: Optional[str] = None,
28
+ ) -> str:
29
+ """
30
+ Initialize orchestration with config and trigger initial signals.
31
+
32
+ Config can be either:
33
+
34
+ 1. Workflows only (legacy format):
35
+ config = {
36
+ "workflow_name": {
37
+ "node_name": {...}
38
+ }
39
+ }
40
+
41
+ 2. Combined config with workflows, context_schema, and identities:
42
+ config = {
43
+ "workflows": {...},
44
+ "context_schema": {...}, # optional
45
+ "identities": {...} # optional
46
+ }
47
+
48
+ 3. Inherited config (config=None, inherit_config_from_id provided):
49
+ Inherits workflows, identities, and context_schema from an existing
50
+ execution. Useful for workflow chaining and continuation.
51
+
52
+ Inheritance options:
53
+
54
+ - inherit_config_from_id: Copy workflows, identities, and context_schema
55
+ from the specified execution ID. When provided, config is optional.
56
+
57
+ - inherit_context_from_id: Copy context from the specified execution ID,
58
+ but ALWAYS reset __operational__ state. Useful for continuing work
59
+ with existing context data.
60
+
61
+ When context_schema and identities are present (either from config or
62
+ inherited), they are automatically saved to their respective backends,
63
+ keyed by the main execution ID so children can access them.
64
+ """
65
+ validate_orchestrate_params(initial_workflow_name, initial_signals)
66
+
67
+ if config is None and inherit_config_from_id is None:
68
+ raise WorkflowValidationError(
69
+ "Either 'config' or 'inherit_config_from_id' must be provided"
70
+ )
71
+
72
+ id = str(uuid4())
73
+
74
+ if inherit_config_from_id:
75
+ register_event(
76
+ backends, id, EventTypes.CONFIG_INHERITANCE_START,
77
+ {"source_execution_id": inherit_config_from_id}
78
+ )
79
+ parsed_registry = inherit_config(inherit_config_from_id, id, backends)
80
+ if config:
81
+ validate_config(config)
82
+ parsed_config = parse_yaml(config)
83
+ parsed_registry = extract_and_save_config_sections(parsed_config, id, backends)
84
+ else:
85
+ validate_config(config)
86
+ parsed_config = parse_yaml(config)
87
+ parsed_registry = extract_and_save_config_sections(parsed_config, id, backends)
88
+
89
+ register_event(
90
+ backends, id, EventTypes.ORCHESTRATION_START,
91
+ {"workflow_name": initial_workflow_name}
92
+ )
93
+
94
+ backends.workflow.save_workflows_registry(id, parsed_registry)
95
+
96
+ if initial_workflow_name not in parsed_registry:
97
+ available = list(parsed_registry.keys())
98
+ raise WorkflowValidationError(
99
+ f"Workflow '{initial_workflow_name}' not found in config. "
100
+ f"Available workflows: {available}"
101
+ )
102
+
103
+ backends.workflow.save_current_workflow_name(id, initial_workflow_name)
104
+
105
+ if inherit_context_from_id:
106
+ register_event(
107
+ backends, id, EventTypes.CONTEXT_INHERITANCE_START,
108
+ )
109
+ context = inherit_context(inherit_context_from_id, backends)
110
+ if initial_context:
111
+ register_event(
112
+ backends, id, EventTypes.CONTEXT_MERGE,
113
+ {"fields": list(initial_context.keys())}
114
+ )
115
+ for field, value in initial_context.items():
116
+ set_field(context, field, value)
117
+ else:
118
+ context = {
119
+ k: [v] if not k.startswith("__") else v
120
+ for k, v in initial_context.items()
121
+ }
122
+
123
+ context = add_operational_state(id, context)
124
+ backends.context.save_context(id, context)
125
+
126
+ broadcast_signals_caller(id, initial_signals)
127
+
128
+ return id
129
+
130
+
131
+ def broadcast_signals(
132
+ id: str,
133
+ signals: List[str],
134
+ nodes: Dict[str, Callable[[str, Dict[str, Any]], None]],
135
+ backends: Backends,
136
+ ) -> None:
137
+ """Broadcast signals to matching nodes in the current workflow"""
138
+ context = validate_operational(id, backends)
139
+
140
+ register_event(backends, id, EventTypes.SIGNALS_BROADCAST, {"signals": signals})
141
+
142
+ workflows_registry = backends.workflow.soe_get_workflows_registry(id)
143
+
144
+ workflow_name = backends.workflow.get_current_workflow_name(id)
145
+ workflow = workflows_registry.get(workflow_name, {})
146
+
147
+ for node_name, node_config in workflow.items():
148
+ triggers = node_config.get("event_triggers", [])
149
+ if set(triggers) & set(signals):
150
+ node_type = node_config["node_type"]
151
+ node_executor = nodes[node_type]
152
+
153
+ register_event(
154
+ backends, id, EventTypes.NODE_EXECUTION,
155
+ {"node_name": node_name, "node_type": node_type}
156
+ )
157
+
158
+ node_config["name"] = node_name
159
+ node_executor(id, node_config)
160
+
161
+ context = backends.context.get_context(id)
162
+ parent_id, signals_to_sync = get_signals_for_parent(signals, context)
163
+
164
+ if parent_id and signals_to_sync:
165
+ register_event(
166
+ backends, id, EventTypes.SIGNALS_TO_PARENT,
167
+ {"signals": signals_to_sync, "parent_id": parent_id}
168
+ )
169
+ broadcast_signals(parent_id, signals_to_sync, nodes, backends)