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,195 @@
1
+ """
2
+ Config validation - validates config structure before orchestration starts.
3
+
4
+ Supports two config formats:
5
+ 1. Single Workflow format: Dict of workflow definitions directly
6
+ 2. Combined config format: Dict with 'workflows', 'context_schema', 'identities' keys
7
+
8
+ Runs once at orchestration start, before any execution.
9
+ """
10
+
11
+ from typing import Dict, Any
12
+
13
+ from ..types import WorkflowValidationError
14
+ from ..nodes.router.validation import validate_node_config as validate_router
15
+ from ..nodes.agent.validation import validate_node_config as validate_agent
16
+ from ..nodes.llm.validation import validate_node_config as validate_llm
17
+ from ..nodes.child.validation import validate_node_config as validate_child
18
+ from ..nodes.tool.validation import validate_node_config as validate_tool
19
+ from ..lib.yaml_parser import parse_yaml
20
+
21
+
22
+ NODE_VALIDATORS = {
23
+ "router": validate_router,
24
+ "agent": validate_agent,
25
+ "llm": validate_llm,
26
+ "child": validate_child,
27
+ "tool": validate_tool,
28
+ }
29
+
30
+
31
+ def _validate_workflow_section(workflow_name: str, workflow: Dict[str, Any]) -> None:
32
+ """
33
+ Validate all nodes in a workflow section.
34
+
35
+ Args:
36
+ workflow_name: Name of the workflow (for error messages)
37
+ workflow: Workflow definition dict
38
+
39
+ Raises:
40
+ WorkflowValidationError: If any node configuration is invalid
41
+ """
42
+ if not workflow:
43
+ raise WorkflowValidationError(
44
+ f"Workflow '{workflow_name}' is empty - at least one node is required"
45
+ )
46
+
47
+ for node_name, node_config in workflow.items():
48
+ if node_name.startswith("__"):
49
+ raise WorkflowValidationError(
50
+ f"Workflow '{workflow_name}': node name '{node_name}' is reserved - "
51
+ f"names starting with '__' are reserved for internal use"
52
+ )
53
+ node_type = node_config.get("node_type")
54
+
55
+ if not node_type:
56
+ raise WorkflowValidationError(
57
+ f"Workflow '{workflow_name}', node '{node_name}': "
58
+ f"'node_type' is required - specify the type (router, agent, llm, tool, child)"
59
+ )
60
+
61
+ if node_type.startswith("_"):
62
+ continue
63
+
64
+ validator = NODE_VALIDATORS.get(node_type)
65
+ if not validator:
66
+ valid_types = ", ".join(NODE_VALIDATORS.keys())
67
+ raise WorkflowValidationError(
68
+ f"Workflow '{workflow_name}', node '{node_name}': "
69
+ f"unknown node_type '{node_type}'. Valid types are: {valid_types}"
70
+ )
71
+
72
+ try:
73
+ validator(node_config)
74
+ except WorkflowValidationError as e:
75
+ raise WorkflowValidationError(
76
+ f"Workflow '{workflow_name}', node '{node_name}': {e}"
77
+ ) from e
78
+
79
+
80
+ def _validate_context_schema_section(context_schema: Dict[str, Any]) -> None:
81
+ """
82
+ Validate context_schema section of config.
83
+
84
+ Args:
85
+ context_schema: Context schema definitions
86
+
87
+ Raises:
88
+ WorkflowValidationError: If schema format is invalid
89
+ """
90
+ if not isinstance(context_schema, dict):
91
+ raise WorkflowValidationError(
92
+ "'context_schema' section must be an object mapping field names to schemas"
93
+ )
94
+
95
+ for field_name, field_schema in context_schema.items():
96
+ if not isinstance(field_schema, (dict, str)):
97
+ raise WorkflowValidationError(
98
+ f"context_schema.{field_name}: schema must be an object or type string"
99
+ )
100
+
101
+
102
+ def _validate_identities_section(identities: Dict[str, str]) -> None:
103
+ """
104
+ Validate identities section of config.
105
+
106
+ Args:
107
+ identities: Identity definitions (name -> system prompt)
108
+
109
+ Raises:
110
+ WorkflowValidationError: If identity format is invalid
111
+ """
112
+ if not isinstance(identities, dict):
113
+ raise WorkflowValidationError(
114
+ "'identities' section must be an object mapping identity names to prompts"
115
+ )
116
+
117
+ for identity_name, identity_prompt in identities.items():
118
+ if not isinstance(identity_prompt, str):
119
+ raise WorkflowValidationError(
120
+ f"identities.{identity_name}: identity prompt must be a string, "
121
+ f"got {type(identity_prompt).__name__}"
122
+ )
123
+
124
+
125
+ def validate_config(config) -> Dict[str, Any]:
126
+ """
127
+ Parse and validate config.
128
+
129
+ Supports two formats:
130
+ 1. Legacy format: Dict of workflow definitions directly
131
+ 2. Combined config format: Dict with 'workflows', 'context_schema', 'identities' keys
132
+
133
+ Args:
134
+ config: YAML string or dict (workflows only or combined config)
135
+
136
+ Returns:
137
+ Parsed config dict (either workflows directly or combined structure)
138
+
139
+ Raises:
140
+ WorkflowValidationError: If any configuration is invalid
141
+ """
142
+ parsed = parse_yaml(config)
143
+
144
+ if "workflows" in parsed:
145
+ workflows = parsed["workflows"]
146
+ if not isinstance(workflows, dict):
147
+ raise WorkflowValidationError(
148
+ "'workflows' section must be an object containing workflow definitions"
149
+ )
150
+ for workflow_name, workflow in workflows.items():
151
+ if not isinstance(workflow, dict):
152
+ raise WorkflowValidationError(
153
+ f"Workflow '{workflow_name}' must be an object containing node definitions"
154
+ )
155
+ _validate_workflow_section(workflow_name, workflow)
156
+
157
+ context_schema = parsed.get("context_schema")
158
+ if context_schema is not None:
159
+ _validate_context_schema_section(context_schema)
160
+
161
+ identities = parsed.get("identities")
162
+ if identities is not None:
163
+ _validate_identities_section(identities)
164
+ else:
165
+ for workflow_name, workflow in parsed.items():
166
+ if not isinstance(workflow, dict):
167
+ raise WorkflowValidationError(
168
+ f"Workflow '{workflow_name}' must be an object containing node definitions"
169
+ )
170
+ _validate_workflow_section(workflow_name, workflow)
171
+
172
+ return parsed
173
+
174
+
175
+ validate_workflow = _validate_workflow_section
176
+
177
+
178
+ def validate_orchestrate_params(
179
+ initial_workflow_name: str,
180
+ initial_signals: list,
181
+ ) -> None:
182
+ """Validate orchestrate() parameters before execution starts."""
183
+ if not isinstance(initial_signals, list):
184
+ raise WorkflowValidationError(
185
+ f"'initial_signals' must be a list, got {type(initial_signals).__name__}. "
186
+ f"Example: initial_signals=['START']"
187
+ )
188
+ if not initial_signals:
189
+ raise WorkflowValidationError(
190
+ "'initial_signals' cannot be empty - at least one signal is required to start execution"
191
+ )
192
+ if not isinstance(initial_workflow_name, str) or not initial_workflow_name:
193
+ raise WorkflowValidationError(
194
+ "'initial_workflow_name' must be a non-empty string"
195
+ )
@@ -0,0 +1,54 @@
1
+ """
2
+ Jinja template validation utilities.
3
+
4
+ Used during config validation to catch Jinja errors early.
5
+ """
6
+
7
+ from jinja2 import Environment, BaseLoader, TemplateSyntaxError
8
+ from ..types import WorkflowValidationError
9
+
10
+
11
+ def _dummy_accumulated_filter(value):
12
+ """Dummy accumulated filter for validation - just returns value as list."""
13
+ return [value] if value is not None else []
14
+
15
+
16
+ def validate_jinja_syntax(template: str, context_description: str) -> None:
17
+ """
18
+ Validate Jinja template syntax at config time.
19
+
20
+ Called during config validation to catch Jinja errors early.
21
+
22
+ Catches:
23
+ - Unclosed braces {{ without }}
24
+ - Unknown filters like | capitalize_all
25
+ - Basic syntax errors
26
+
27
+ Does NOT catch:
28
+ - Runtime errors like division by zero (depends on context values)
29
+ - Undefined variables (depends on context at runtime)
30
+
31
+ Args:
32
+ template: The Jinja template string to validate
33
+ context_description: Description for error messages (e.g., "condition for signal 'DONE'")
34
+
35
+ Raises:
36
+ WorkflowValidationError: If template has syntax or filter errors
37
+ """
38
+ if not template or ("{{" not in template and "{%" not in template):
39
+ return
40
+ try:
41
+ env = Environment(loader=BaseLoader())
42
+ env.filters["accumulated"] = _dummy_accumulated_filter
43
+ env.parse(template)
44
+ env.from_string(template)
45
+ except TemplateSyntaxError as e:
46
+ raise WorkflowValidationError(
47
+ f"{context_description}: Jinja syntax error - {e.message}"
48
+ )
49
+ except Exception as e:
50
+ error_msg = str(e)
51
+ if "filter" in error_msg.lower():
52
+ raise WorkflowValidationError(
53
+ f"{context_description}: {error_msg}"
54
+ )
@@ -0,0 +1,110 @@
1
+ """
2
+ Operational validation - validates runtime state before node execution.
3
+
4
+ This validates that the context and backends are in a valid state
5
+ for node execution. Runs before each node executes so that operational
6
+ code can trust the structure and avoid defensive programming.
7
+
8
+ Fail-fast: If validation fails, raise immediately with clear message.
9
+ """
10
+
11
+ from typing import Dict, Any
12
+
13
+ from ..types import Backends, SoeError
14
+
15
+
16
+ class OperationalValidationError(SoeError):
17
+ """Raised when operational context is invalid."""
18
+ pass
19
+
20
+
21
+ def validate_operational(
22
+ execution_id: str,
23
+ backends: Backends,
24
+ ) -> Dict[str, Any]:
25
+ """
26
+ Validate that operational context exists and has required structure.
27
+
28
+ Call this before any node execution to ensure __operational__ is valid.
29
+ Returns the context so caller doesn't need to fetch it again.
30
+
31
+ Args:
32
+ execution_id: The execution ID
33
+ backends: Backend services
34
+
35
+ Returns:
36
+ The validated context dict
37
+
38
+ Raises:
39
+ OperationalValidationError: If context or __operational__ is invalid
40
+ """
41
+ context = backends.context.get_context(execution_id)
42
+
43
+ if not context:
44
+ raise OperationalValidationError(
45
+ f"No context found for execution_id '{execution_id}'. "
46
+ f"Context must be initialized before node execution."
47
+ )
48
+
49
+ operational = context.get("__operational__")
50
+
51
+ if operational is None:
52
+ raise OperationalValidationError(
53
+ f"Missing '__operational__' in context for execution_id '{execution_id}'. "
54
+ f"Call initialize_operational_context() before node execution."
55
+ )
56
+
57
+ required_fields = ["signals", "nodes", "llm_calls", "tool_calls", "errors", "main_execution_id"]
58
+ missing = [f for f in required_fields if f not in operational]
59
+
60
+ if missing:
61
+ raise OperationalValidationError(
62
+ f"Invalid '__operational__' structure for execution_id '{execution_id}'. "
63
+ f"Missing fields: {missing}"
64
+ )
65
+
66
+ if not isinstance(operational["signals"], list):
67
+ raise OperationalValidationError(
68
+ f"Invalid '__operational__.signals' - must be a list, got {type(operational['signals']).__name__}"
69
+ )
70
+
71
+ if not isinstance(operational["nodes"], dict):
72
+ raise OperationalValidationError(
73
+ f"Invalid '__operational__.nodes' - must be a dict, got {type(operational['nodes']).__name__}"
74
+ )
75
+
76
+ if not isinstance(operational["llm_calls"], int):
77
+ raise OperationalValidationError(
78
+ f"Invalid '__operational__.llm_calls' - must be an int, got {type(operational['llm_calls']).__name__}"
79
+ )
80
+
81
+ if not isinstance(operational["tool_calls"], int):
82
+ raise OperationalValidationError(
83
+ f"Invalid '__operational__.tool_calls' - must be an int, got {type(operational['tool_calls']).__name__}"
84
+ )
85
+
86
+ if not isinstance(operational["errors"], int):
87
+ raise OperationalValidationError(
88
+ f"Invalid '__operational__.errors' - must be an int, got {type(operational['errors']).__name__}"
89
+ )
90
+
91
+ return context
92
+
93
+
94
+ def validate_backends(backends: Backends) -> None:
95
+ """
96
+ Validate that backends has required attributes.
97
+
98
+ Args:
99
+ backends: Backend services
100
+
101
+ Raises:
102
+ OperationalValidationError: If backends is invalid
103
+ """
104
+ required = ["context", "workflow"]
105
+
106
+ for attr in required:
107
+ if not hasattr(backends, attr) or getattr(backends, attr) is None:
108
+ raise OperationalValidationError(
109
+ f"Invalid backends: missing required attribute '{attr}'"
110
+ )
@@ -0,0 +1,262 @@
1
+ Metadata-Version: 2.4
2
+ Name: soe-ai
3
+ Version: 0.2.0b1
4
+ Summary: Signal-driven Orchestration Engine - Agent orchestration with event-driven workflow engine
5
+ Author-email: Pedro Garcia <pgarcia14180@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/pgarcia14180/soe
8
+ Project-URL: Documentation, https://github.com/pgarcia14180/soe/tree/master/docs
9
+ Project-URL: Repository, https://github.com/pgarcia14180/soe
10
+ Project-URL: Issues, https://github.com/pgarcia14180/soe/issues
11
+ Keywords: orchestration,agent,workflow,automation
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Requires-Python: >=3.8
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: pyyaml>=6.0
24
+ Requires-Dist: pydantic>=2.0.0
25
+ Requires-Dist: jinja2>=2.11.3
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7.0; extra == "dev"
28
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
29
+ Requires-Dist: black>=23.0; extra == "dev"
30
+ Requires-Dist: mypy>=1.0; extra == "dev"
31
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
32
+ Provides-Extra: integration
33
+ Requires-Dist: openai>=1.0.0; extra == "integration"
34
+ Requires-Dist: requests>=2.28.0; extra == "integration"
35
+ Dynamic: license-file
36
+
37
+ # SOE — Signal-driven Orchestration Engine
38
+
39
+ **A protocol for orchestrating AI workflows through signals.**
40
+
41
+ ---
42
+
43
+ ## What SOE Is
44
+
45
+ SOE is an orchestration engine where nodes communicate through **signals** rather than direct function calls. You define workflows in YAML, and the engine handles execution.
46
+
47
+ | Approach | How It Works | Trade-off |
48
+ |----------|--------------|-----------|
49
+ | Chain-based | `Step A → B → C → D` | Simple but rigid |
50
+ | SOE Signal-based | `[SIGNAL] → all listeners respond` | Flexible, requires understanding signals |
51
+
52
+ **The C++ analogy**: Like C++ gives you control over memory and execution (compared to higher-level languages), SOE gives you control over orchestration primitives. You decide how state is stored, how LLMs are called, and how signals are broadcast. This requires more setup but means no vendor lock-in and full observability.
53
+
54
+ ---
55
+
56
+ ## What SOE Does
57
+
58
+ SOE orchestrates workflows through **signals**. Nodes don't call each other—they emit signals that other nodes listen for.
59
+
60
+ ```yaml
61
+ example_workflow:
62
+ ValidateInput:
63
+ node_type: router
64
+ event_triggers: [START]
65
+ event_emissions:
66
+ - signal_name: VALID
67
+ condition: "{{ context.data is defined }}"
68
+ - signal_name: INVALID
69
+
70
+ ProcessData:
71
+ node_type: llm
72
+ event_triggers: [VALID]
73
+ prompt: "Process this: {{ context.data }}"
74
+ output_field: result
75
+ event_emissions:
76
+ - signal_name: DONE
77
+ ```
78
+
79
+ **That's the entire workflow definition.** No SDK, no decorators, no base classes.
80
+
81
+ ---
82
+
83
+ ## Why SOE
84
+
85
+ ### 1. Infrastructure-Agnostic
86
+ SOE defines **protocols**, not implementations. Swap PostgreSQL for DynamoDB. Replace OpenAI with a local LLM. Deploy to Lambda, Kubernetes, or a single Python script. Your workflow YAML stays the same.
87
+
88
+ ### 2. Context-Driven with Jinja
89
+ All workflow state flows through **context**—a shared dictionary accessible via Jinja2 templates. This means:
90
+ - Conditions like `{{ context.user_validated }}` are readable and debuggable
91
+ - LLM prompts can interpolate any context field
92
+ - No hidden state—everything is inspectable
93
+
94
+ ### 3. Purely Deterministic or Hybrid Agentic
95
+ SOE is a complete orchestration solution. You can use it as a purely deterministic engine for standard business logic, or mix in LLM-driven "Agentic" behavior.
96
+ - **Deterministic**: Use `router` and `tool` nodes for 100% predictable workflows.
97
+ - **Agentic**: Add `llm` and `agent` nodes for creative, reasoning-based tasks.
98
+ You get the safety of code with the flexibility of AI in a single, unified system.
99
+
100
+ ### 4. Portable
101
+ Workflows are YAML. Run them locally, in CI, in production. Extract them, version them, share them.
102
+
103
+ ### 5. Self-Evolving
104
+ Workflows can modify themselves at runtime. Built-in tools like `inject_workflow`, `inject_node_configuration`, and `add_signal` allow agents to:
105
+ - Create new workflows dynamically
106
+ - Add or modify nodes in existing workflows
107
+ - Update signal routing on the fly
108
+
109
+ This enables **meta-programming**: an AI system that can extend its own capabilities without human intervention.
110
+
111
+ ---
112
+
113
+ ## What SOE Unlocks
114
+
115
+ SOE is a **Protocol for Intelligence** that unlocks new forms of intelligent behavior:
116
+
117
+ ### Self-Evolving Intelligence
118
+ AI systems that can rewrite and improve themselves at runtime - the ultimate evolution of software.
119
+
120
+ ### Swarm Intelligence
121
+ Efficient collective decision-making among multiple agents through signal-based consensus.
122
+
123
+ ### Hybrid Intelligence
124
+ Seamless combination of deterministic logic and AI creativity with programmatic safety rails.
125
+
126
+ ### Fractal Intelligence
127
+ Hierarchical agent organizations that scale complexity while remaining manageable.
128
+
129
+ ### Infrastructure Intelligence
130
+ AI orchestration that works everywhere - from edge devices to cloud platforms.
131
+
132
+ ---
133
+
134
+ ## Installation
135
+
136
+ ```bash
137
+ # With uv (recommended)
138
+ uv add soe-ai
139
+
140
+ # With pip
141
+ pip install soe-ai
142
+
143
+ # From source
144
+ git clone https://github.com/pgarcia14180/soe.git
145
+ cd soe && uv sync
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Quick Start
151
+
152
+ ### 1. Provide Your LLM
153
+
154
+ SOE is LLM-agnostic. You must provide a `call_llm` function that matches this signature:
155
+
156
+ ```python
157
+ def call_llm(
158
+ prompt: str,
159
+ config: dict,
160
+ ) -> str:
161
+ """
162
+ Called by SOE when a node needs LLM processing.
163
+
164
+ Args:
165
+ prompt: The rendered prompt string (includes instructions, context, and schemas)
166
+ config: The full node configuration from YAML (useful for model parameters)
167
+
168
+ Returns:
169
+ The raw text response from the LLM.
170
+ """
171
+ # Example with OpenAI:
172
+ from openai import OpenAI
173
+ client = OpenAI()
174
+ response = client.chat.completions.create(
175
+ model=config.get("model", "gpt-4o"),
176
+ messages=[{"role": "user", "content": prompt}],
177
+ )
178
+ return response.choices[0].message.content
179
+ ```
180
+
181
+ ### 2. Run a Workflow
182
+
183
+ ```python
184
+ from soe import orchestrate, create_all_nodes
185
+ from soe.local_backends import create_local_backends
186
+
187
+ # Your workflow (can also be loaded from file or database)
188
+ workflow = """
189
+ example_workflow:
190
+ Start:
191
+ node_type: router
192
+ event_triggers: [START]
193
+ event_emissions:
194
+ - signal_name: DONE
195
+ """
196
+
197
+ # Create backends (storage for context, workflows, etc.)
198
+ backends = create_local_backends("./data")
199
+
200
+ # Create all node handlers (pass your call_llm function)
201
+ nodes, broadcast = create_all_nodes(backends, call_llm=call_llm)
202
+
203
+ # Run the workflow
204
+ execution_id = orchestrate(
205
+ config=workflow,
206
+ initial_workflow_name="example_workflow",
207
+ initial_signals=["START"],
208
+ initial_context={"user": "alice"},
209
+ backends=backends,
210
+ broadcast_signals_caller=broadcast,
211
+ )
212
+ # When orchestrate() returns, the workflow is complete
213
+ ```
214
+
215
+ **For product managers and less technical users**: The Quick Start above is all you need to run a workflow. The `config` parameter accepts YAML defining your workflow structure. The `initial_context` is where you pass input data (like user IDs, requests, etc.).
216
+
217
+ ---
218
+
219
+ ## Documentation
220
+
221
+ | Audience | Start Here |
222
+ |----------|------------|
223
+ | **Builders** (workflow authors) | [Documentation](soe/docs/index.md) — Step-by-step chapters |
224
+ | **Engineers** (infrastructure) | [Infrastructure Guide](soe/docs/guide_10_infrastructure.md) — Backend protocols |
225
+ | **Researchers** (advanced patterns) | [Advanced Patterns](docs/advanced_patterns/) — Swarm, hybrid, self-evolving |
226
+
227
+ ---
228
+
229
+ ## Node Types
230
+
231
+ | Node | Purpose |
232
+ |------|---------|
233
+ | `router` | Conditional signal emission (no LLM) |
234
+ | `llm` | Single LLM call with output |
235
+ | `agent` | Multi-turn LLM with tool access |
236
+ | `tool` | Execute Python functions |
237
+ | `child` | Spawn sub-workflows |
238
+
239
+ ---
240
+
241
+ ## Backend Protocols
242
+
243
+ Implement these to plug SOE into your infrastructure:
244
+
245
+ | Protocol | Purpose |
246
+ |----------|---------|
247
+ | `ContextBackend` | Workflow state storage |
248
+ | `WorkflowBackend` | Workflow definitions |
249
+ | `ContextSchemaBackend` | Output validation (optional) |
250
+ | `IdentityBackend` | LLM system prompts (optional) |
251
+ | `ConversationHistoryBackend` | Agent memory (optional) |
252
+ | `TelemetryBackend` | Observability (optional) |
253
+
254
+ **Recommendation**: Use the same database for context, workflows, identities, and context_schema—just separate tables. The backend methods handle table creation.
255
+
256
+ See [Infrastructure Guide](docs/guide_10_infrastructure.md) for PostgreSQL, DynamoDB, and Lambda examples.
257
+
258
+ ---
259
+
260
+ ## License
261
+
262
+ MIT