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.
Files changed (145) hide show
  1. soe/__init__.py +50 -0
  2. soe/broker.py +168 -0
  3. soe/builtin_tools/__init__.py +51 -0
  4. soe/builtin_tools/soe_add_signal.py +82 -0
  5. soe/builtin_tools/soe_call_tool.py +111 -0
  6. soe/builtin_tools/soe_copy_context.py +80 -0
  7. soe/builtin_tools/soe_explore_docs.py +290 -0
  8. soe/builtin_tools/soe_get_available_tools.py +42 -0
  9. soe/builtin_tools/soe_get_context.py +50 -0
  10. soe/builtin_tools/soe_get_context_schema.py +56 -0
  11. soe/builtin_tools/soe_get_identities.py +63 -0
  12. soe/builtin_tools/soe_get_workflows.py +63 -0
  13. soe/builtin_tools/soe_inject_context_schema_field.py +80 -0
  14. soe/builtin_tools/soe_inject_identity.py +64 -0
  15. soe/builtin_tools/soe_inject_node.py +86 -0
  16. soe/builtin_tools/soe_inject_workflow.py +105 -0
  17. soe/builtin_tools/soe_list_contexts.py +73 -0
  18. soe/builtin_tools/soe_remove_context_schema_field.py +61 -0
  19. soe/builtin_tools/soe_remove_identity.py +61 -0
  20. soe/builtin_tools/soe_remove_node.py +72 -0
  21. soe/builtin_tools/soe_remove_workflow.py +62 -0
  22. soe/builtin_tools/soe_update_context.py +54 -0
  23. soe/docs/_config.yml +10 -0
  24. soe/docs/advanced_patterns/guide_fanout_and_aggregations.md +318 -0
  25. soe/docs/advanced_patterns/guide_inheritance.md +435 -0
  26. soe/docs/advanced_patterns/hybrid_intelligence.md +237 -0
  27. soe/docs/advanced_patterns/index.md +49 -0
  28. soe/docs/advanced_patterns/operational.md +781 -0
  29. soe/docs/advanced_patterns/self_evolving_workflows.md +385 -0
  30. soe/docs/advanced_patterns/swarm_intelligence.md +211 -0
  31. soe/docs/builtins/context.md +164 -0
  32. soe/docs/builtins/context_schema.md +158 -0
  33. soe/docs/builtins/identity.md +139 -0
  34. soe/docs/builtins/soe_explore_docs.md +135 -0
  35. soe/docs/builtins/tools.md +164 -0
  36. soe/docs/builtins/workflows.md +199 -0
  37. soe/docs/guide_00_getting_started.md +341 -0
  38. soe/docs/guide_01_tool.md +206 -0
  39. soe/docs/guide_02_llm.md +143 -0
  40. soe/docs/guide_03_router.md +146 -0
  41. soe/docs/guide_04_patterns.md +475 -0
  42. soe/docs/guide_05_agent.md +159 -0
  43. soe/docs/guide_06_schema.md +397 -0
  44. soe/docs/guide_07_identity.md +540 -0
  45. soe/docs/guide_08_child.md +612 -0
  46. soe/docs/guide_09_ecosystem.md +690 -0
  47. soe/docs/guide_10_infrastructure.md +427 -0
  48. soe/docs/guide_11_builtins.md +126 -0
  49. soe/docs/index.md +104 -0
  50. soe/docs/primitives/backends.md +281 -0
  51. soe/docs/primitives/context.md +256 -0
  52. soe/docs/primitives/node_reference.md +259 -0
  53. soe/docs/primitives/primitives.md +331 -0
  54. soe/docs/primitives/signals.md +865 -0
  55. soe/docs_index.py +2 -0
  56. soe/init.py +165 -0
  57. soe/lib/__init__.py +0 -0
  58. soe/lib/child_context.py +46 -0
  59. soe/lib/context_fields.py +51 -0
  60. soe/lib/inheritance.py +172 -0
  61. soe/lib/jinja_render.py +113 -0
  62. soe/lib/operational.py +51 -0
  63. soe/lib/parent_sync.py +71 -0
  64. soe/lib/register_event.py +75 -0
  65. soe/lib/schema_validation.py +134 -0
  66. soe/lib/yaml_parser.py +14 -0
  67. soe/local_backends/__init__.py +18 -0
  68. soe/local_backends/factory.py +124 -0
  69. soe/local_backends/in_memory/context.py +38 -0
  70. soe/local_backends/in_memory/conversation_history.py +60 -0
  71. soe/local_backends/in_memory/identity.py +52 -0
  72. soe/local_backends/in_memory/schema.py +40 -0
  73. soe/local_backends/in_memory/telemetry.py +38 -0
  74. soe/local_backends/in_memory/workflow.py +33 -0
  75. soe/local_backends/storage/context.py +57 -0
  76. soe/local_backends/storage/conversation_history.py +82 -0
  77. soe/local_backends/storage/identity.py +118 -0
  78. soe/local_backends/storage/schema.py +96 -0
  79. soe/local_backends/storage/telemetry.py +72 -0
  80. soe/local_backends/storage/workflow.py +56 -0
  81. soe/nodes/__init__.py +13 -0
  82. soe/nodes/agent/__init__.py +10 -0
  83. soe/nodes/agent/factory.py +134 -0
  84. soe/nodes/agent/lib/loop_handlers.py +150 -0
  85. soe/nodes/agent/lib/loop_state.py +157 -0
  86. soe/nodes/agent/lib/prompts.py +65 -0
  87. soe/nodes/agent/lib/tools.py +35 -0
  88. soe/nodes/agent/stages/__init__.py +12 -0
  89. soe/nodes/agent/stages/parameter.py +37 -0
  90. soe/nodes/agent/stages/response.py +54 -0
  91. soe/nodes/agent/stages/router.py +37 -0
  92. soe/nodes/agent/state.py +111 -0
  93. soe/nodes/agent/types.py +66 -0
  94. soe/nodes/agent/validation/__init__.py +11 -0
  95. soe/nodes/agent/validation/config.py +95 -0
  96. soe/nodes/agent/validation/operational.py +24 -0
  97. soe/nodes/child/__init__.py +3 -0
  98. soe/nodes/child/factory.py +61 -0
  99. soe/nodes/child/state.py +59 -0
  100. soe/nodes/child/validation/__init__.py +11 -0
  101. soe/nodes/child/validation/config.py +126 -0
  102. soe/nodes/child/validation/operational.py +28 -0
  103. soe/nodes/lib/conditions.py +71 -0
  104. soe/nodes/lib/context.py +24 -0
  105. soe/nodes/lib/conversation_history.py +77 -0
  106. soe/nodes/lib/identity.py +64 -0
  107. soe/nodes/lib/llm_resolver.py +142 -0
  108. soe/nodes/lib/output.py +68 -0
  109. soe/nodes/lib/response_builder.py +91 -0
  110. soe/nodes/lib/signal_emission.py +79 -0
  111. soe/nodes/lib/signals.py +54 -0
  112. soe/nodes/lib/tools.py +100 -0
  113. soe/nodes/llm/__init__.py +7 -0
  114. soe/nodes/llm/factory.py +103 -0
  115. soe/nodes/llm/state.py +76 -0
  116. soe/nodes/llm/types.py +12 -0
  117. soe/nodes/llm/validation/__init__.py +11 -0
  118. soe/nodes/llm/validation/config.py +89 -0
  119. soe/nodes/llm/validation/operational.py +23 -0
  120. soe/nodes/router/__init__.py +3 -0
  121. soe/nodes/router/factory.py +37 -0
  122. soe/nodes/router/state.py +32 -0
  123. soe/nodes/router/validation/__init__.py +11 -0
  124. soe/nodes/router/validation/config.py +58 -0
  125. soe/nodes/router/validation/operational.py +16 -0
  126. soe/nodes/tool/factory.py +66 -0
  127. soe/nodes/tool/lib/__init__.py +11 -0
  128. soe/nodes/tool/lib/conditions.py +35 -0
  129. soe/nodes/tool/lib/failure.py +28 -0
  130. soe/nodes/tool/lib/parameters.py +67 -0
  131. soe/nodes/tool/state.py +66 -0
  132. soe/nodes/tool/types.py +27 -0
  133. soe/nodes/tool/validation/__init__.py +15 -0
  134. soe/nodes/tool/validation/config.py +132 -0
  135. soe/nodes/tool/validation/operational.py +16 -0
  136. soe/types.py +209 -0
  137. soe/validation/__init__.py +18 -0
  138. soe/validation/config.py +195 -0
  139. soe/validation/jinja.py +54 -0
  140. soe/validation/operational.py +110 -0
  141. soe_ai-0.2.0b1.dist-info/METADATA +262 -0
  142. soe_ai-0.2.0b1.dist-info/RECORD +145 -0
  143. soe_ai-0.2.0b1.dist-info/WHEEL +5 -0
  144. soe_ai-0.2.0b1.dist-info/licenses/LICENSE +21 -0
  145. soe_ai-0.2.0b1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,67 @@
1
+ """Tool parameter extraction and validation utilities."""
2
+
3
+ import inspect
4
+ from typing import Dict, Any, Callable, Optional
5
+
6
+ from ....lib.context_fields import get_field
7
+ from ..types import ToolParameterError
8
+
9
+
10
+ def extract_tool_parameters(
11
+ context: Dict[str, Any],
12
+ context_parameter_field: Optional[str],
13
+ ) -> Dict[str, Any]:
14
+ """Extract tool parameters from context.
15
+
16
+ Args:
17
+ context: The workflow context
18
+ context_parameter_field: Name of the context field containing tool kwargs
19
+
20
+ Returns:
21
+ Dict of parameters to pass to the tool function
22
+
23
+ Raises:
24
+ ToolParameterError: If field is missing or not a dict
25
+ """
26
+ if not context_parameter_field:
27
+ return {}
28
+
29
+ if context_parameter_field not in context:
30
+ raise ToolParameterError(f"Context missing required field: {context_parameter_field}")
31
+
32
+ parameters = get_field(context, context_parameter_field)
33
+
34
+ if not isinstance(parameters, dict):
35
+ raise ToolParameterError(
36
+ f"Context field '{context_parameter_field}' must be a dict of parameters, got {type(parameters)}"
37
+ )
38
+
39
+ return parameters
40
+
41
+
42
+ def validate_tool_parameters(
43
+ tool_function: Callable, parameters: Dict[str, Any], tool_name: str
44
+ ) -> None:
45
+ """Validate parameters match tool function signature."""
46
+ signature = inspect.signature(tool_function)
47
+
48
+ has_var_keyword = any(
49
+ param.kind == inspect.Parameter.VAR_KEYWORD
50
+ for param in signature.parameters.values()
51
+ )
52
+
53
+ for param_name, param in signature.parameters.items():
54
+ if param.kind in (inspect.Parameter.VAR_KEYWORD, inspect.Parameter.VAR_POSITIONAL):
55
+ continue
56
+ if param.default == inspect.Parameter.empty:
57
+ if param_name not in parameters:
58
+ raise ToolParameterError(
59
+ f"Tool '{tool_name}' missing required parameter: {param_name}"
60
+ )
61
+
62
+ if not has_var_keyword:
63
+ for param_name in parameters.keys():
64
+ if param_name not in signature.parameters:
65
+ raise ToolParameterError(
66
+ f"Tool '{tool_name}' unexpected parameter: {param_name}"
67
+ )
@@ -0,0 +1,66 @@
1
+ """
2
+ Tool node state retrieval.
3
+ """
4
+
5
+ from typing import Callable, Dict, Any, List, Optional
6
+ from pydantic import BaseModel, ConfigDict
7
+
8
+ from ...types import Backends
9
+ from ...lib.yaml_parser import parse_yaml
10
+ from ...lib.context_fields import get_field
11
+ from ..lib.tools import get_tool_from_registry
12
+ from .types import ToolsRegistry
13
+
14
+
15
+ class ToolOperationalState(BaseModel):
16
+ """All data needed for tool node execution."""
17
+ model_config = ConfigDict(arbitrary_types_allowed=True)
18
+
19
+ context: Dict[str, Any]
20
+ main_execution_id: str
21
+ tool_name: str
22
+ tool_function: Callable
23
+ max_retries: int
24
+ failure_signal: Optional[str]
25
+ output_field: Optional[str]
26
+ event_emissions: List[Dict[str, Any]]
27
+ parameters: Any # Can be Dict or List when process_accumulated=True
28
+ process_accumulated: bool = False
29
+
30
+
31
+ def get_operational_state(
32
+ execution_id: str,
33
+ node_config: Dict[str, Any],
34
+ backends: Backends,
35
+ tools_registry: ToolsRegistry,
36
+ ) -> ToolOperationalState:
37
+ """Retrieve all state needed for tool node execution."""
38
+ context = backends.context.get_context(execution_id)
39
+ operational = context["__operational__"]
40
+ tool_name = node_config["tool_name"]
41
+ tool_function, max_retries, failure_signal, process_accumulated = get_tool_from_registry(
42
+ tool_name, tools_registry, execution_id, backends
43
+ )
44
+
45
+ context_parameter_field = node_config.get("context_parameter_field")
46
+ if context_parameter_field and context_parameter_field in context:
47
+ if process_accumulated:
48
+ raw_params = context[context_parameter_field]
49
+ else:
50
+ raw_params = get_field(context, context_parameter_field)
51
+ parameters = parse_yaml(raw_params) if isinstance(raw_params, str) else raw_params
52
+ else:
53
+ parameters = {}
54
+
55
+ return ToolOperationalState(
56
+ context=context,
57
+ main_execution_id=operational["main_execution_id"],
58
+ tool_name=tool_name,
59
+ tool_function=tool_function,
60
+ max_retries=max_retries,
61
+ failure_signal=failure_signal,
62
+ output_field=node_config.get("output_field"),
63
+ event_emissions=node_config.get("event_emissions", []),
64
+ parameters=parameters,
65
+ process_accumulated=process_accumulated,
66
+ )
@@ -0,0 +1,27 @@
1
+ """
2
+ Tool node models and exceptions
3
+ """
4
+
5
+ from typing import Callable, TypedDict, Union, Dict
6
+
7
+
8
+ class ToolRegistryEntry(TypedDict, total=False):
9
+ """Extended tool registry entry with optional configuration"""
10
+ function: Callable
11
+ max_retries: int
12
+ failure_signal: str
13
+
14
+
15
+ ToolsRegistry = Dict[str, Union[Callable, ToolRegistryEntry]]
16
+
17
+
18
+ class ToolNodeConfigurationError(Exception):
19
+ """Raised when tool node configuration is invalid"""
20
+
21
+ pass
22
+
23
+
24
+ class ToolParameterError(Exception):
25
+ """Raised when tool parameters don't match signature"""
26
+
27
+ pass
@@ -0,0 +1,15 @@
1
+ """
2
+ Tool node validation.
3
+
4
+ - config.py: Config validation at orchestration start
5
+ - operational.py: Runtime validation before execution (fail-fast)
6
+ """
7
+
8
+ from .config import validate_node_config, validate_tool_node_config
9
+ from .operational import validate_tool_node_runtime
10
+
11
+ __all__ = [
12
+ "validate_node_config",
13
+ "validate_tool_node_config",
14
+ "validate_tool_node_runtime",
15
+ ]
@@ -0,0 +1,132 @@
1
+ """
2
+ Tool node configuration validation.
3
+
4
+ Called once at orchestration start, not during node execution.
5
+ """
6
+
7
+ from typing import Dict, Any, Callable, Union
8
+
9
+ from ....types import WorkflowValidationError
10
+ from ....builtin_tools import get_builtin_tool_factory
11
+ from ..types import ToolRegistryEntry, ToolsRegistry
12
+
13
+
14
+ def _get_function_from_entry(entry: Union[Callable, ToolRegistryEntry]) -> Callable:
15
+ """Extract the callable from a registry entry"""
16
+ if callable(entry):
17
+ return entry
18
+ return entry.get("function")
19
+
20
+
21
+ def _validate_registry_entry(tool_name: str, entry: Union[Callable, ToolRegistryEntry]) -> None:
22
+ """
23
+ Validate a tool registry entry format.
24
+
25
+ Raises:
26
+ WorkflowValidationError: If entry format is invalid
27
+ """
28
+ if callable(entry):
29
+ return
30
+
31
+ if not isinstance(entry, dict):
32
+ raise WorkflowValidationError(
33
+ f"Tool '{tool_name}' registry entry must be a callable or dict with 'function' key"
34
+ )
35
+
36
+ if "function" not in entry:
37
+ raise WorkflowValidationError(
38
+ f"Tool '{tool_name}' registry entry dict must have 'function' key"
39
+ )
40
+
41
+ if not callable(entry["function"]):
42
+ raise WorkflowValidationError(
43
+ f"Tool '{tool_name}' 'function' must be callable"
44
+ )
45
+
46
+ max_retries = entry.get("max_retries")
47
+ if max_retries is not None:
48
+ if not isinstance(max_retries, int) or max_retries < 0:
49
+ raise WorkflowValidationError(
50
+ f"Tool '{tool_name}' 'max_retries' must be a non-negative integer"
51
+ )
52
+
53
+ failure_signal = entry.get("failure_signal")
54
+ if failure_signal is not None:
55
+ if not isinstance(failure_signal, str):
56
+ raise WorkflowValidationError(
57
+ f"Tool '{tool_name}' 'failure_signal' must be a string"
58
+ )
59
+
60
+
61
+ def validate_node_config(node_config: Dict[str, Any]) -> None:
62
+ """
63
+ Validate tool node configuration (structure only, no runtime checks).
64
+ Called once at orchestration start, not during node execution.
65
+
66
+ Raises:
67
+ WorkflowValidationError: If configuration is invalid
68
+ """
69
+ event_triggers = node_config.get("event_triggers")
70
+ if not event_triggers:
71
+ raise WorkflowValidationError(
72
+ "'event_triggers' is required - specify which signals activate this tool node"
73
+ )
74
+ if not isinstance(event_triggers, list):
75
+ raise WorkflowValidationError(
76
+ "'event_triggers' must be a list, e.g., [\"EXECUTE_TOOL\"]"
77
+ )
78
+
79
+ if "tool_name" not in node_config:
80
+ raise WorkflowValidationError(
81
+ "'tool_name' is required - specify which tool to execute"
82
+ )
83
+
84
+ event_emissions = node_config.get("event_emissions")
85
+ if event_emissions is not None:
86
+ if not isinstance(event_emissions, list):
87
+ raise WorkflowValidationError(
88
+ "'event_emissions' must be a list of signal definitions"
89
+ )
90
+ for i, emission in enumerate(event_emissions):
91
+ if not isinstance(emission, dict):
92
+ raise WorkflowValidationError(
93
+ f"'event_emissions[{i}]' must be a dict with 'signal_name'"
94
+ )
95
+ if "signal_name" not in emission:
96
+ raise WorkflowValidationError(
97
+ f"'event_emissions[{i}]' must have 'signal_name'"
98
+ )
99
+
100
+ output_field = node_config.get("output_field")
101
+ if output_field is not None:
102
+ if not isinstance(output_field, str):
103
+ raise WorkflowValidationError(
104
+ "'output_field' must be a string - the context field name to store the tool output"
105
+ )
106
+ if output_field == "__operational__":
107
+ raise WorkflowValidationError(
108
+ "'output_field' cannot be '__operational__' - this is a reserved system field"
109
+ )
110
+
111
+
112
+ def validate_tool_node_config(
113
+ node_config: Dict[str, Any], tools_registry: ToolsRegistry
114
+ ) -> None:
115
+ """Validate tool node configuration with runtime checks (tool registry)"""
116
+
117
+ validate_node_config(node_config)
118
+
119
+ tool_name = node_config["tool_name"]
120
+ if tool_name not in tools_registry:
121
+ if not get_builtin_tool_factory(tool_name):
122
+ raise WorkflowValidationError(
123
+ f"Tool '{tool_name}' not found in tools_registry or builtin tools"
124
+ )
125
+ return
126
+
127
+ entry = tools_registry[tool_name]
128
+ _validate_registry_entry(tool_name, entry)
129
+
130
+ tool_function = _get_function_from_entry(entry)
131
+ if not callable(tool_function):
132
+ raise WorkflowValidationError(f"Tool '{tool_name}' is not callable")
@@ -0,0 +1,16 @@
1
+ """Tool node operational validation.
2
+
3
+ Calls shared operational validation. Tool node has no additional backend requirements.
4
+ """
5
+
6
+ from typing import Dict, Any
7
+ from ....types import Backends
8
+ from ....validation.operational import validate_operational
9
+
10
+
11
+ def validate_tool_node_runtime(
12
+ execution_id: str,
13
+ backends: Backends,
14
+ ) -> Dict[str, Any]:
15
+ """Validate runtime state for Tool node. Delegates to shared validation."""
16
+ return validate_operational(execution_id, backends)
soe/types.py ADDED
@@ -0,0 +1,209 @@
1
+ """
2
+ Types for orchestration system
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import Protocol, Optional, Any, Dict, List
8
+
9
+
10
+ class EventTypes:
11
+ """Constants for telemetry event types"""
12
+
13
+ ORCHESTRATION_START = "orchestration_start"
14
+ SIGNALS_BROADCAST = "signals_broadcast"
15
+ NODE_EXECUTION = "node_execution"
16
+ TOOL_CALL = "tool_call"
17
+ LLM_CALL = "llm_call"
18
+ SIGNALS_TO_PARENT = "signals_to_parent"
19
+ NODE_ERROR = "node_error"
20
+ CONTEXT_WARNING = "context_warning"
21
+ AGENT_TOOLS_LOADED = "agent_tools_loaded"
22
+ AGENT_TOOL_CALL = "agent_tool_call"
23
+ AGENT_TOOL_RESULT = "agent_tool_result"
24
+ AGENT_TOOL_NOT_FOUND = "agent_tool_not_found"
25
+ CONFIG_INHERITANCE_START = "config_inheritance_start"
26
+ CONTEXT_INHERITANCE_START = "context_inheritance_start"
27
+ CONTEXT_MERGE = "context_merge"
28
+
29
+
30
+ class TelemetryBackend(Protocol):
31
+ """Protocol for telemetry backend"""
32
+
33
+ def log_event(self, execution_id: str, event_type: str, **event_data) -> None:
34
+ ...
35
+
36
+
37
+ class ContextBackend(Protocol):
38
+ """Protocol for context backend"""
39
+
40
+ def save_context(self, id: str, context: dict) -> None:
41
+ ...
42
+
43
+ def get_context(self, id: str) -> dict:
44
+ ...
45
+
46
+
47
+ class WorkflowBackend(Protocol):
48
+ """Protocol for workflow backend"""
49
+
50
+ def save_workflows_registry(self, id: str, workflows: Dict[str, Any]) -> None:
51
+ ...
52
+
53
+ def get_workflows_registry(self, id: str) -> Any:
54
+ ...
55
+
56
+ def save_current_workflow_name(self, id: str, name: str) -> None:
57
+ ...
58
+
59
+ def get_current_workflow_name(self, id: str) -> str:
60
+ ...
61
+
62
+
63
+ class BroadcastSignalsCaller(Protocol):
64
+ """Protocol for broadcast signals caller function"""
65
+
66
+ def __call__(self, execution_id: str, signals: List[str]) -> None:
67
+ ...
68
+
69
+
70
+ class NodeCaller(Protocol):
71
+ """Protocol for node caller function"""
72
+
73
+ def __call__(self, execution_id: str, node_config: Dict[str, Any]) -> None:
74
+ ...
75
+
76
+
77
+ class LlmNodeCaller(NodeCaller):
78
+ """Protocol for LLM node caller function"""
79
+ pass
80
+
81
+
82
+ class RouterNodeCaller(NodeCaller):
83
+ """Protocol for router node caller function"""
84
+ pass
85
+
86
+
87
+ class AgentNodeCaller(NodeCaller):
88
+ """Protocol for agent node caller function"""
89
+ pass
90
+
91
+
92
+ class ToolNodeCaller(NodeCaller):
93
+ """Protocol for tool node caller function"""
94
+ pass
95
+
96
+
97
+ class ChildNodeCaller(NodeCaller):
98
+ """Protocol for child node caller function"""
99
+ pass
100
+
101
+
102
+ class OrchestrateCaller(Protocol):
103
+ """Protocol for starting child workflows (wrapper around orchestrate)"""
104
+
105
+ def __call__(
106
+ self,
107
+ config: Any,
108
+ initial_workflow_name: str,
109
+ initial_signals: List[str],
110
+ initial_context: Dict[str, Any],
111
+ backends: "Backends",
112
+ inherit_config_from_id: Optional[str] = None,
113
+ inherit_context_from_id: Optional[str] = None,
114
+ ) -> str:
115
+ ...
116
+
117
+
118
+ class CallLlm(Protocol):
119
+ """Protocol for LLM caller function"""
120
+
121
+ def __call__(self, prompt: str, config: Dict[str, Any]) -> str:
122
+ ...
123
+
124
+
125
+ class ConversationHistoryBackend(Protocol):
126
+ """Protocol for conversation history backend"""
127
+
128
+ def get_conversation_history(self, identity: str) -> List[Dict[str, Any]]:
129
+ ...
130
+
131
+ def append_to_conversation_history(self, identity: str, entry: Dict[str, Any]) -> None:
132
+ ...
133
+
134
+ def save_conversation_history(self, identity: str, history: List[Dict[str, Any]]) -> None:
135
+ ...
136
+
137
+ def delete_conversation_history(self, identity: str) -> None:
138
+ ...
139
+
140
+
141
+ class ContextSchemaBackend(Protocol):
142
+ """Protocol for context schema backend - stores workflow context field schemas.
143
+
144
+ Context schemas define the structure and types of context fields used in workflows.
145
+ These are keyed by execution_id (main_execution_id) so children can access parent's schemas.
146
+ """
147
+
148
+ def save_context_schema(self, execution_id: str, schema: Dict[str, Any]) -> None:
149
+ """Save context schema for an execution."""
150
+ ...
151
+
152
+ def get_context_schema(self, execution_id: str) -> Optional[Dict[str, Any]]:
153
+ """Get context schema for an execution."""
154
+ ...
155
+
156
+ def delete_context_schema(self, execution_id: str) -> bool:
157
+ """Delete context schema for an execution."""
158
+ ...
159
+
160
+
161
+ class IdentityBackend(Protocol):
162
+ """Protocol for identity backend - stores workflow identity definitions.
163
+
164
+ Identities define the participating personas/roles in a workflow.
165
+ These are used as the initial system prompt for conversation history.
166
+ Keyed by execution_id (main_execution_id) so children can access parent's identities.
167
+
168
+ Identity format is simple: identity_name -> system_prompt (string)
169
+ Example:
170
+ assistant: "You are a helpful assistant."
171
+ coding_expert: "You are an expert programmer."
172
+ """
173
+
174
+ def save_identities(self, execution_id: str, identities: Dict[str, str]) -> None:
175
+ """Save identity definitions for an execution."""
176
+ ...
177
+
178
+ def get_identities(self, execution_id: str) -> Optional[Dict[str, str]]:
179
+ """Get all identity definitions for an execution."""
180
+ ...
181
+
182
+ def get_identity(self, execution_id: str, identity_name: str) -> Optional[str]:
183
+ """Get a specific identity's system prompt."""
184
+ ...
185
+
186
+ def delete_identities(self, execution_id: str) -> bool:
187
+ """Delete identity definitions for an execution."""
188
+ ...
189
+
190
+
191
+ class Backends(Protocol):
192
+ """Protocol for orchestration backends"""
193
+
194
+ context: ContextBackend
195
+ workflow: WorkflowBackend
196
+ telemetry: Optional[TelemetryBackend]
197
+ conversation_history: Optional[ConversationHistoryBackend]
198
+ context_schema: Optional[ContextSchemaBackend]
199
+ identity: Optional[IdentityBackend]
200
+
201
+
202
+ class SoeError(Exception):
203
+ """Base class for all SOE exceptions"""
204
+ pass
205
+
206
+
207
+ class WorkflowValidationError(SoeError):
208
+ """Raised when workflow or node configuration is invalid (static/startup)"""
209
+ pass
@@ -0,0 +1,18 @@
1
+ """
2
+ Validation module for SOE.
3
+
4
+ Two types of validation:
5
+ 1. config.py - Validates config structure at orchestration start
6
+ 2. operational.py - Validates runtime state before node execution (fail-fast)
7
+ """
8
+
9
+ from .config import validate_config, validate_workflow, validate_orchestrate_params
10
+ from .operational import validate_operational, OperationalValidationError
11
+
12
+ __all__ = [
13
+ "validate_config",
14
+ "validate_workflow",
15
+ "validate_orchestrate_params",
16
+ "validate_operational",
17
+ "OperationalValidationError",
18
+ ]