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
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,168 @@
1
+ from uuid import uuid4
2
+ from typing import Dict, List, Any, Union, Optional
3
+ from .types import Backends, BroadcastSignalsCaller, NodeCaller, EventTypes, WorkflowValidationError
4
+ from .lib.register_event import register_event
5
+ from .lib.yaml_parser import parse_yaml
6
+ from .lib.operational import add_operational_state
7
+ from .lib.context_fields import set_field
8
+ from .lib.parent_sync import get_signals_for_parent
9
+ from .lib.inheritance import (
10
+ inherit_config,
11
+ inherit_context,
12
+ extract_and_save_config_sections,
13
+ )
14
+ from .validation import validate_config, validate_operational, validate_orchestrate_params
15
+ from .types import WorkflowValidationError
16
+
17
+
18
+ def orchestrate(
19
+ config: Optional[Union[str, Dict[str, Any]]],
20
+ initial_workflow_name: str,
21
+ initial_signals: List[str],
22
+ initial_context: Dict[str, Any],
23
+ backends: Backends,
24
+ broadcast_signals_caller: BroadcastSignalsCaller,
25
+ inherit_config_from_id: Optional[str] = None,
26
+ inherit_context_from_id: Optional[str] = None,
27
+ ) -> str:
28
+ """
29
+ Initialize orchestration with config and trigger initial signals.
30
+
31
+ Config can be either:
32
+
33
+ 1. Workflows only (legacy format):
34
+ config = {
35
+ "workflow_name": {
36
+ "node_name": {...}
37
+ }
38
+ }
39
+
40
+ 2. Combined config with workflows, context_schema, and identities:
41
+ config = {
42
+ "workflows": {...},
43
+ "context_schema": {...}, # optional
44
+ "identities": {...} # optional
45
+ }
46
+
47
+ 3. Inherited config (config=None, inherit_config_from_id provided):
48
+ Inherits workflows, identities, and context_schema from an existing
49
+ execution. Useful for workflow chaining and continuation.
50
+
51
+ Inheritance options:
52
+
53
+ - inherit_config_from_id: Copy workflows, identities, and context_schema
54
+ from the specified execution ID. When provided, config is optional.
55
+
56
+ - inherit_context_from_id: Copy context from the specified execution ID,
57
+ but ALWAYS reset __operational__ state. Useful for continuing work
58
+ with existing context data.
59
+
60
+ When context_schema and identities are present (either from config or
61
+ inherited), they are automatically saved to their respective backends,
62
+ keyed by the main execution ID so children can access them.
63
+ """
64
+ validate_orchestrate_params(initial_workflow_name, initial_signals)
65
+
66
+ if config is None and inherit_config_from_id is None:
67
+ raise WorkflowValidationError(
68
+ "Either 'config' or 'inherit_config_from_id' must be provided"
69
+ )
70
+
71
+ id = str(uuid4())
72
+
73
+ if inherit_config_from_id:
74
+ register_event(
75
+ backends, id, EventTypes.CONFIG_INHERITANCE_START,
76
+ {"source_execution_id": inherit_config_from_id}
77
+ )
78
+ 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:
84
+ validate_config(config)
85
+ parsed_config = parse_yaml(config)
86
+ parsed_registry = extract_and_save_config_sections(parsed_config, id, backends)
87
+
88
+ register_event(
89
+ backends, id, EventTypes.ORCHESTRATION_START,
90
+ {"workflow_name": initial_workflow_name}
91
+ )
92
+
93
+ backends.workflow.save_workflows_registry(id, parsed_registry)
94
+
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
+ )
101
+
102
+ backends.workflow.save_current_workflow_name(id, initial_workflow_name)
103
+
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
+ }
121
+
122
+ context = add_operational_state(id, context)
123
+ backends.context.save_context(id, context)
124
+
125
+ broadcast_signals_caller(id, initial_signals)
126
+
127
+ return id
128
+
129
+
130
+ def broadcast_signals(
131
+ id: str,
132
+ signals: List[str],
133
+ nodes: Dict[str, NodeCaller],
134
+ backends: Backends,
135
+ ) -> None:
136
+ """Broadcast signals to matching nodes in the current workflow"""
137
+ context = validate_operational(id, backends)
138
+
139
+ register_event(backends, id, EventTypes.SIGNALS_BROADCAST, {"signals": signals})
140
+
141
+ workflows_registry = backends.workflow.get_workflows_registry(id)
142
+
143
+ workflow_name = backends.workflow.get_current_workflow_name(id)
144
+ workflow = workflows_registry.get(workflow_name, {})
145
+
146
+ for node_name, node_config in workflow.items():
147
+ triggers = node_config.get("event_triggers", [])
148
+ if set(triggers) & set(signals):
149
+ node_type = node_config["node_type"]
150
+ node_executor = nodes[node_type]
151
+
152
+ register_event(
153
+ backends, id, EventTypes.NODE_EXECUTION,
154
+ {"node_name": node_name, "node_type": node_type}
155
+ )
156
+
157
+ node_config["name"] = node_name
158
+ node_executor(id, node_config)
159
+
160
+ context = backends.context.get_context(id)
161
+ parent_id, signals_to_sync = get_signals_for_parent(signals, context)
162
+
163
+ if parent_id and signals_to_sync:
164
+ register_event(
165
+ backends, id, EventTypes.SIGNALS_TO_PARENT,
166
+ {"signals": signals_to_sync, "parent_id": parent_id}
167
+ )
168
+ broadcast_signals(parent_id, signals_to_sync, nodes, backends)
@@ -0,0 +1,51 @@
1
+ """
2
+ Built-in tools registry
3
+ """
4
+
5
+ from .soe_inject_workflow import create_soe_inject_workflow_tool
6
+ from .soe_inject_node import create_soe_inject_node_tool
7
+ from .soe_get_workflows import create_soe_get_workflows_tool
8
+ from .soe_get_available_tools import create_soe_get_available_tools_tool
9
+ from .soe_explore_docs import create_soe_explore_docs_tool
10
+ from .soe_remove_workflow import create_soe_remove_workflow_tool
11
+ from .soe_remove_node import create_soe_remove_node_tool
12
+ from .soe_get_context import create_soe_get_context_tool
13
+ from .soe_update_context import create_soe_update_context_tool
14
+ from .soe_copy_context import create_soe_copy_context_tool
15
+ from .soe_list_contexts import create_soe_list_contexts_tool
16
+ from .soe_add_signal import create_soe_add_signal_tool
17
+ from .soe_call_tool import create_soe_call_tool_tool
18
+ from .soe_get_identities import create_soe_get_identities_tool
19
+ from .soe_inject_identity import create_soe_inject_identity_tool
20
+ from .soe_remove_identity import create_soe_remove_identity_tool
21
+ from .soe_get_context_schema import create_soe_get_context_schema_tool
22
+ from .soe_inject_context_schema_field import create_soe_inject_context_schema_field_tool
23
+ from .soe_remove_context_schema_field import create_soe_remove_context_schema_field_tool
24
+
25
+ # Registry of all available built-in tools
26
+ BUILTIN_TOOLS = {
27
+ "soe_inject_workflow": create_soe_inject_workflow_tool,
28
+ "soe_inject_node": create_soe_inject_node_tool,
29
+ "soe_get_workflows": create_soe_get_workflows_tool,
30
+ "soe_get_available_tools": create_soe_get_available_tools_tool,
31
+ "soe_explore_docs": create_soe_explore_docs_tool,
32
+ "soe_remove_workflow": create_soe_remove_workflow_tool,
33
+ "soe_remove_node": create_soe_remove_node_tool,
34
+ "soe_get_context": create_soe_get_context_tool,
35
+ "soe_update_context": create_soe_update_context_tool,
36
+ "soe_copy_context": create_soe_copy_context_tool,
37
+ "soe_list_contexts": create_soe_list_contexts_tool,
38
+ "soe_add_signal": create_soe_add_signal_tool,
39
+ "soe_call_tool": create_soe_call_tool_tool,
40
+ "soe_get_identities": create_soe_get_identities_tool,
41
+ "soe_inject_identity": create_soe_inject_identity_tool,
42
+ "soe_remove_identity": create_soe_remove_identity_tool,
43
+ "soe_get_context_schema": create_soe_get_context_schema_tool,
44
+ "soe_inject_context_schema_field": create_soe_inject_context_schema_field_tool,
45
+ "soe_remove_context_schema_field": create_soe_remove_context_schema_field_tool,
46
+ }
47
+
48
+
49
+ def get_builtin_tool_factory(tool_name: str):
50
+ """Get factory function for built-in tool"""
51
+ return BUILTIN_TOOLS.get(tool_name)
@@ -0,0 +1,82 @@
1
+ """Built-in tool to add a signal to a node's event emissions."""
2
+
3
+ from typing import Dict, Any, Callable
4
+ from ..types import EventTypes
5
+ from ..lib.register_event import register_event
6
+
7
+
8
+ def create_soe_add_signal_tool(
9
+ execution_id: str,
10
+ backends,
11
+ tools_registry: dict = None,
12
+ ) -> Callable:
13
+ """
14
+ Factory function to create add_signal tool.
15
+ """
16
+
17
+ def add_signal(
18
+ workflow_name: str, node_name: str, signal_name: str, condition: str
19
+ ) -> Dict[str, Any]:
20
+ """
21
+ Add a signal to a node's event_emissions list.
22
+
23
+ Args:
24
+ workflow_name: Name of the workflow
25
+ node_name: Name of the node
26
+ signal_name: Name of the signal to add
27
+ condition: Jinja condition for the signal
28
+
29
+ Returns:
30
+ Success confirmation
31
+ """
32
+ workflows_registry = backends.workflow.get_workflows_registry(execution_id)
33
+
34
+ if workflow_name not in workflows_registry:
35
+ raise ValueError(f"Workflow '{workflow_name}' not found")
36
+
37
+ workflow = workflows_registry[workflow_name]
38
+ if node_name not in workflow:
39
+ raise ValueError(f"Node '{node_name}' not found in workflow '{workflow_name}'")
40
+
41
+ node_config = workflow[node_name]
42
+
43
+ if "event_emissions" not in node_config:
44
+ node_config["event_emissions"] = []
45
+
46
+ # Check if signal already exists
47
+ for emission in node_config["event_emissions"]:
48
+ if emission.get("signal_name") == signal_name:
49
+ # Update existing
50
+ emission["condition"] = condition
51
+ backends.workflow.save_workflows_registry(execution_id, workflows_registry)
52
+ return {
53
+ "status": "updated",
54
+ "message": f"Updated signal '{signal_name}' in node '{node_name}'"
55
+ }
56
+
57
+ # Add new signal
58
+ node_config["event_emissions"].append({
59
+ "signal_name": signal_name,
60
+ "condition": condition
61
+ })
62
+
63
+ backends.workflow.save_workflows_registry(execution_id, workflows_registry)
64
+
65
+ register_event(
66
+ backends,
67
+ execution_id,
68
+ EventTypes.NODE_EXECUTION,
69
+ {
70
+ "tool": "add_signal",
71
+ "workflow_name": workflow_name,
72
+ "node_name": node_name,
73
+ "signal": signal_name
74
+ },
75
+ )
76
+
77
+ return {
78
+ "status": "added",
79
+ "message": f"Added signal '{signal_name}' to node '{node_name}'"
80
+ }
81
+
82
+ return add_signal
@@ -0,0 +1,111 @@
1
+ """
2
+ Built-in dynamic tool invocation.
3
+
4
+ Allows LLMs to call any registered tool by name at runtime.
5
+ This enables meta-level tool orchestration.
6
+ """
7
+
8
+ import json
9
+ from typing import Dict, Any, Callable
10
+ from ..types import EventTypes
11
+ from ..lib.register_event import register_event
12
+
13
+
14
+ def create_soe_call_tool_tool(
15
+ execution_id: str,
16
+ backends,
17
+ tools_registry: dict,
18
+ ) -> Callable:
19
+ """
20
+ Factory function to create call_tool with access to tools registry.
21
+
22
+ Args:
23
+ execution_id: Current execution ID
24
+ backends: Backend services
25
+ tools_registry: Registry of available tools {name: {"function": callable, ...}}
26
+
27
+ Returns:
28
+ Configured call_tool function
29
+ """
30
+
31
+ def call_tool(tool_name: str, arguments: str = "{}") -> Dict[str, Any]:
32
+ """
33
+ Dynamically invoke a registered tool by name.
34
+
35
+ This is a meta-tool that allows calling any other tool at runtime.
36
+ Useful for dynamic workflows where the tool to call is determined
37
+ by context or user input.
38
+
39
+ Args:
40
+ tool_name: Name of the tool to invoke (must be registered)
41
+ arguments: JSON string of arguments to pass to the tool
42
+
43
+ Returns:
44
+ The result from the invoked tool, or an error dict
45
+
46
+ Example:
47
+ call_tool("get_secret", '{"key": "password"}')
48
+ call_tool("write_file", '{"path": "test.txt", "content": "hello"}')
49
+ """
50
+ # Parse arguments
51
+ try:
52
+ args = json.loads(arguments) if arguments else {}
53
+ except json.JSONDecodeError as e:
54
+ return {
55
+ "error": f"Invalid JSON arguments: {e}",
56
+ "tool_name": tool_name,
57
+ }
58
+
59
+ # Check if tool exists
60
+ if tool_name not in tools_registry:
61
+ available = list(tools_registry.keys())
62
+ return {
63
+ "error": f"Tool '{tool_name}' not found",
64
+ "available_tools": available[:20], # Limit to avoid huge responses
65
+ }
66
+
67
+ # Get tool function
68
+ tool_entry = tools_registry[tool_name]
69
+ if isinstance(tool_entry, dict):
70
+ tool_func = tool_entry.get("function")
71
+ elif callable(tool_entry):
72
+ tool_func = tool_entry
73
+ else:
74
+ return {"error": f"Invalid tool registry entry for '{tool_name}'"}
75
+
76
+ if not callable(tool_func):
77
+ return {"error": f"Tool '{tool_name}' is not callable"}
78
+
79
+ # Log the dynamic invocation
80
+ register_event(
81
+ backends,
82
+ execution_id,
83
+ EventTypes.TOOL_CALL,
84
+ {
85
+ "meta_tool": "call_tool",
86
+ "invoked_tool": tool_name,
87
+ "arguments": args,
88
+ },
89
+ )
90
+
91
+ # Invoke the tool
92
+ try:
93
+ result = tool_func(**args)
94
+ return {
95
+ "success": True,
96
+ "tool_name": tool_name,
97
+ "result": result,
98
+ }
99
+ except TypeError as e:
100
+ # Argument mismatch
101
+ return {
102
+ "error": f"Argument error for '{tool_name}': {e}",
103
+ "tool_name": tool_name,
104
+ }
105
+ except Exception as e:
106
+ return {
107
+ "error": f"Tool '{tool_name}' failed: {e}",
108
+ "tool_name": tool_name,
109
+ }
110
+
111
+ return call_tool
@@ -0,0 +1,80 @@
1
+ """
2
+ Built-in tool: copy_context
3
+ Allows agents to copy context fields between executions or within the same execution.
4
+ """
5
+
6
+ from typing import Dict, Any, Optional
7
+
8
+
9
+ def create_soe_copy_context_tool(backends, execution_id: str, tools_registry=None):
10
+ """
11
+ Factory that creates a copy_context tool bound to the current execution.
12
+
13
+ Args:
14
+ backends: Backend instances (needs context backend)
15
+ execution_id: Current execution ID
16
+ tools_registry: Tool registry (unused, for interface compatibility)
17
+
18
+ Returns:
19
+ Configured tool function
20
+ """
21
+
22
+ def copy_context(
23
+ source_execution_id: Optional[str] = None,
24
+ fields: Optional[Dict[str, str]] = None,
25
+ all_fields: bool = False,
26
+ target_execution_id: Optional[str] = None,
27
+ ) -> Dict[str, Any]:
28
+ """
29
+ Copy context fields between executions or within the same execution.
30
+
31
+ Args:
32
+ source_execution_id: Execution ID to copy from (default: current)
33
+ fields: Dict of {source_field: target_field} to copy
34
+ all_fields: If True, copy all non-operational fields
35
+ target_execution_id: Execution ID to copy to (default: current)
36
+
37
+ Returns:
38
+ Dict with copy results
39
+ """
40
+ source_id = source_execution_id or execution_id
41
+ target_id = target_execution_id or execution_id
42
+
43
+ # Get source context
44
+ source_context = backends.context.get_context(source_id)
45
+
46
+ # Filter out operational fields
47
+ source_filtered = {k: v for k, v in source_context.items() if not k.startswith("__")}
48
+
49
+ # Get target context
50
+ target_context = backends.context.get_context(target_id)
51
+
52
+ copied_fields = {}
53
+
54
+ if all_fields:
55
+ # Copy all non-operational fields
56
+ for field, value in source_filtered.items():
57
+ target_context[field] = value
58
+ copied_fields[field] = field
59
+ elif fields:
60
+ # Copy specific field mappings
61
+ for source_field, target_field in fields.items():
62
+ if source_field in source_filtered:
63
+ target_context[target_field] = source_filtered[source_field]
64
+ copied_fields[source_field] = target_field
65
+ else:
66
+ return {"error": f"Source field '{source_field}' not found in execution {source_id}"}
67
+ else:
68
+ return {"error": "Must specify either 'fields' mapping or 'all_fields=True'"}
69
+
70
+ # Save target context
71
+ backends.context.save_context(target_id, target_context)
72
+
73
+ return {
74
+ "status": "copied",
75
+ "source_execution": source_id,
76
+ "target_execution": target_id,
77
+ "fields_copied": copied_fields,
78
+ }
79
+
80
+ return copy_context