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,290 @@
1
+ from typing import Literal, Optional, List, Callable
2
+ from pathlib import Path
3
+ import os
4
+
5
+ from soe.docs_index import DOCS_INDEX
6
+
7
+ # Get the SOE package root directory (where soe/docs/ lives)
8
+ # This goes from soe/builtin_tools/soe_explore_docs.py → soe/builtin_tools → soe/soe → soe/
9
+ SOE_ROOT = Path(__file__).resolve().parent.parent.parent
10
+
11
+
12
+ def create_soe_explore_docs_tool(
13
+ execution_id: str = None,
14
+ backends = None,
15
+ tools_registry: dict = None,
16
+ ) -> Callable:
17
+ """
18
+ Factory for the soe_explore_docs tool.
19
+
20
+ Args:
21
+ execution_id: ID to access workflow data via backends (unused by this tool)
22
+ backends: Backend services (unused by this tool)
23
+ tools_registry: Optional registry of available tools (unused by this tool)
24
+
25
+ Returns:
26
+ Configured soe_explore_docs function
27
+ """
28
+ return soe_explore_docs
29
+
30
+
31
+ def soe_explore_docs(
32
+ path: str,
33
+ action: Literal["list", "read", "search", "tree", "get_tags"],
34
+ query: Optional[str] = None,
35
+ tag: Optional[str] = None
36
+ ) -> str:
37
+ """
38
+ Explore SOE documentation using a file-system-like interface.
39
+
40
+ This tool lets you navigate and read the SOE documentation hierarchy.
41
+ Start with action='list' at path='/' to see available docs.
42
+
43
+ Args:
44
+ path: Path to explore. Use '/' for root, 'docs/guide_01_basics.md' for a file,
45
+ or 'docs/guide_01_basics.md/Section Title' for a specific section.
46
+ action: What to do at the path:
47
+ - 'list': Show children (files in a dir, sections in a file)
48
+ - 'read': Get the content of a file or section
49
+ - 'tree': Show recursive structure from this path
50
+ - 'search': Find docs matching query/tag (path ignored)
51
+ - 'get_tags': List all available tags (path ignored)
52
+ query: Search term for 'search' action. Matches against path/title.
53
+ tag: Filter by tag for 'search' action. Use 'get_tags' to see available tags.
54
+
55
+ Returns:
56
+ For 'list': Lines like "[DIR] name/", "[FILE] name.md", "[SEC] Section Title"
57
+ For 'read': The markdown content of the file or section
58
+ For 'tree': Indented tree structure with [D]/[F]/[S] markers
59
+ For 'search': List of matching paths
60
+ For 'get_tags': List of available tags
61
+
62
+ Examples:
63
+ soe_explore_docs(path="/", action="list") # See all docs
64
+ soe_explore_docs(path="docs/guide_01_basics.md", action="list") # See sections
65
+ soe_explore_docs(path="docs/guide_01_basics.md", action="read") # Read full file
66
+ soe_explore_docs(path="docs/guide_01_basics.md/Quick Start", action="read") # Read section
67
+ soe_explore_docs(path="/", action="search", query="agent") # Find agent-related docs
68
+ """
69
+ # Normalize path
70
+ # Remove leading slash if present to match index keys (which are relative paths like 'soe/docs/...')
71
+ # But handle root '/' as well
72
+ clean_path = path.strip("/")
73
+
74
+ # Special case: "/" means the main docs directory
75
+ if clean_path == "" or path == "/":
76
+ clean_path = "soe/docs"
77
+
78
+ # Dispatch actions
79
+ if action == "list":
80
+ return _handle_list(clean_path)
81
+ elif action == "read":
82
+ return _handle_read(clean_path)
83
+ elif action == "tree":
84
+ return _handle_tree(clean_path)
85
+ elif action == "search":
86
+ return _handle_search(query, tag)
87
+ elif action == "get_tags":
88
+ return _handle_get_tags()
89
+ else:
90
+ return f"Error: Unknown action '{action}'"
91
+
92
+ def _handle_list(path: str) -> str:
93
+ # If path is empty/root, return root children
94
+ if path == "" or path == ".":
95
+ children = DOCS_INDEX.get("root_children", [])
96
+ return _format_list(children)
97
+
98
+ # Check if path exists in index
99
+ # Try exact match first
100
+ # Index keys usually end with / for dirs? My build script adds / for dirs.
101
+
102
+ # Try as directory
103
+ dir_key = path if path.endswith("/") else path + "/"
104
+ if dir_key in DOCS_INDEX["items"]:
105
+ item = DOCS_INDEX["items"][dir_key]
106
+ return _format_list(item.get("children", []))
107
+
108
+ # Try as file or section (no trailing slash)
109
+ file_key = path.rstrip("/")
110
+ if file_key in DOCS_INDEX["items"]:
111
+ item = DOCS_INDEX["items"][file_key]
112
+ return _format_list(item.get("children", []))
113
+
114
+ return f"Error: Path '{path}' not found in index."
115
+
116
+ def _format_list(children: List[str]) -> str:
117
+ if not children:
118
+ return "(empty)"
119
+
120
+ lines = []
121
+ for child in sorted(children):
122
+ item = DOCS_INDEX["items"][child] # Children are always valid in well-formed index
123
+ type_ = item.get("type", "unknown")
124
+
125
+ # Display name is the last part of the path
126
+ # For sections: docs/file.md/Section -> Section
127
+ # For files: docs/file.md -> file.md
128
+ display_name = child.rstrip("/").split("/")[-1]
129
+
130
+ if type_ == "dir":
131
+ lines.append(f"[DIR] {display_name}/")
132
+ elif type_ == "file":
133
+ lines.append(f"[FILE] {display_name}")
134
+ elif type_ == "section":
135
+ lines.append(f"[SEC] {display_name}")
136
+
137
+ return "\n".join(lines)
138
+
139
+ def _handle_read(path: str) -> str:
140
+ # Normalize
141
+ key = path.rstrip("/")
142
+
143
+ # Check if it exists as a file/section (exact match on stripped key)
144
+ if key in DOCS_INDEX["items"]:
145
+ item = DOCS_INDEX["items"][key]
146
+ type_ = item.get("type")
147
+
148
+ if type_ == "dir":
149
+ return f"Error: '{path}' is a directory. Use action='list' or 'tree'."
150
+
151
+ # Check if it exists as a directory (key + "/")
152
+ elif (key + "/") in DOCS_INDEX["items"]:
153
+ return f"Error: '{path}' is a directory. Use action='list' or 'tree'."
154
+
155
+ else:
156
+ return f"Error: Path '{path}' not found."
157
+
158
+ # Get file path
159
+ file_path_str = item.get("file_path") or item.get("path") # For files, path is file_path
160
+
161
+ # Read file relative to SOE package root
162
+ full_path = SOE_ROOT / file_path_str
163
+ try:
164
+ content = full_path.read_text()
165
+ except FileNotFoundError:
166
+ return f"Error: File not found at '{full_path}'. The docs index may be out of date."
167
+ lines = content.splitlines()
168
+
169
+ if type_ == "file":
170
+ return content
171
+
172
+ # type_ == "section"
173
+ start = item["start_line"] - 1
174
+ end = item["end_line"]
175
+ if end == -1:
176
+ end = len(lines)
177
+
178
+ section_lines = lines[start:end]
179
+ return "\n".join(section_lines)
180
+
181
+
182
+ def _handle_tree(path: str) -> str:
183
+ # Recursive listing
184
+ # We can use the index structure
185
+
186
+ # Normalize
187
+ if path == "" or path == ".":
188
+ roots = DOCS_INDEX.get("root_children", [])
189
+ return _build_tree_str(roots, indent=0)
190
+
191
+ key = path if path.endswith("/") else path + "/"
192
+ # Try dir
193
+ if key in DOCS_INDEX["items"]:
194
+ return _build_tree_str([key], indent=0)
195
+
196
+ # Try file
197
+ key = path.rstrip("/")
198
+ if key in DOCS_INDEX["items"]:
199
+ return _build_tree_str([key], indent=0)
200
+
201
+ return f"Error: Path '{path}' not found."
202
+
203
+ def _build_tree_str(paths: List[str], indent: int) -> str:
204
+ out = []
205
+ for p in sorted(paths):
206
+ item = DOCS_INDEX["items"].get(p)
207
+ if not item: continue
208
+
209
+ display_name = p.rstrip("/").split("/")[-1]
210
+ prefix = " " * indent
211
+
212
+ type_ = item.get("type")
213
+ marker = "[D]" if type_ == "dir" else "[F]" if type_ == "file" else "[S]"
214
+
215
+ out.append(f"{prefix}{marker} {display_name}")
216
+
217
+ # Recurse
218
+ children = item.get("children", [])
219
+ if children:
220
+ out.append(_build_tree_str(children, indent + 1))
221
+
222
+ return "\n".join(out)
223
+
224
+ def _handle_search(query: str, tag: str) -> str:
225
+ if not query and not tag:
226
+ return "Error: Provide 'query' or 'tag' for search."
227
+
228
+ # Tag filter
229
+ candidate_paths = set(DOCS_INDEX["items"].keys())
230
+ if tag:
231
+ if tag in DOCS_INDEX["tags"]:
232
+ candidate_paths = set(DOCS_INDEX["tags"][tag])
233
+ else:
234
+ return f"No results for tag '{tag}'"
235
+
236
+ # Text search - split query into words and match ANY word
237
+ # Also search in content_preview for better results
238
+ final_results = set()
239
+
240
+ if query:
241
+ # Split query into words, filter out common words
242
+ stop_words = {"and", "or", "the", "a", "an", "in", "on", "at", "to", "for", "of", "with", "how", "does", "do", "is", "are", "what"}
243
+ query_words = [w.lower() for w in query.split() if w.lower() not in stop_words and len(w) > 1]
244
+
245
+ if not query_words:
246
+ # Fall back to full query if all words were filtered
247
+ query_words = [query.lower()]
248
+
249
+ for p in candidate_paths:
250
+ item = DOCS_INDEX["items"].get(p, {})
251
+
252
+ # Search in path, title, and content_preview
253
+ searchable_text = p.lower()
254
+ if "title" in item:
255
+ searchable_text += " " + item["title"].lower()
256
+ if "content_preview" in item:
257
+ searchable_text += " " + item["content_preview"].lower()
258
+
259
+ # Match if ANY query word is found
260
+ for word in query_words:
261
+ if word in searchable_text:
262
+ final_results.add(p)
263
+ break
264
+ else:
265
+ # Tag only search
266
+ final_results = candidate_paths
267
+
268
+ if not final_results:
269
+ return f"No results found for: {query_words if query else tag}"
270
+
271
+ # Dedupe: skip sections if parent file is also in results
272
+ deduped = set()
273
+ for r in final_results:
274
+ parts = r.rsplit("/", 1)
275
+ if len(parts) == 2 and parts[0] in final_results:
276
+ continue # Skip section if parent file matches
277
+ deduped.add(r)
278
+
279
+ # Format results - limit to 20 most relevant
280
+ sorted_results = sorted(deduped)[:20]
281
+ result_text = "\n".join(sorted_results)
282
+
283
+ if len(deduped) > 20:
284
+ result_text += f"\n... and {len(deduped) - 20} more results"
285
+
286
+ return result_text
287
+
288
+ def _handle_get_tags() -> str:
289
+ tags = sorted(DOCS_INDEX.get("tags", {}).keys())
290
+ return f"Available tags: {tags}"
@@ -0,0 +1,42 @@
1
+ """Built-in tool to list available tools."""
2
+
3
+ from typing import Dict, Any, Callable, List
4
+
5
+
6
+ def create_soe_get_available_tools_tool(
7
+ execution_id: str,
8
+ backends,
9
+ tools_registry: Dict[str, Any] = None,
10
+ ) -> Callable:
11
+ """
12
+ Factory function to create soe_get_available_tools tool.
13
+
14
+ Args:
15
+ execution_id: ID for the execution
16
+ backends: Backend services
17
+ tools_registry: Registry of user-provided tools
18
+
19
+ Returns:
20
+ Configured soe_get_available_tools function
21
+ """
22
+ from . import BUILTIN_TOOLS
23
+
24
+ def soe_get_available_tools() -> Dict[str, List[str]]:
25
+ """
26
+ Get all available tools that can be used in workflows.
27
+
28
+ Returns:
29
+ {
30
+ "builtin_tools": [...list of builtin tool names...],
31
+ "user_tools": [...list of user-provided tool names...]
32
+ }
33
+ """
34
+ builtin_names = list(BUILTIN_TOOLS.keys())
35
+ user_names = list(tools_registry.keys()) if tools_registry else []
36
+
37
+ return {
38
+ "builtin_tools": builtin_names,
39
+ "user_tools": user_names,
40
+ }
41
+
42
+ return soe_get_available_tools
@@ -0,0 +1,50 @@
1
+ """
2
+ Built-in tool: get_context
3
+ Allows agents to read context fields dynamically.
4
+ """
5
+
6
+ from typing import Optional, List, Any, Dict
7
+
8
+
9
+ def create_soe_get_context_tool(backends, execution_id: str, tools_registry=None):
10
+ """
11
+ Factory that creates a get_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 get_context(
23
+ field: Optional[str] = None,
24
+ fields: Optional[List[str]] = None
25
+ ) -> Dict[str, Any]:
26
+ """
27
+ Read context fields from the current execution.
28
+
29
+ Args:
30
+ field: Single field name to read (returns just that value)
31
+ fields: List of field names to read (returns dict of values)
32
+
33
+ If neither field nor fields is provided, returns all context.
34
+
35
+ Returns:
36
+ Context data (single value, dict of values, or full context)
37
+ """
38
+ context = backends.context.get_context(execution_id)
39
+
40
+ # Filter out operational data
41
+ filtered = {k: v for k, v in context.items() if not k.startswith("__")}
42
+
43
+ if field:
44
+ return {field: filtered.get(field)}
45
+ elif fields:
46
+ return {f: filtered.get(f) for f in fields}
47
+ else:
48
+ return filtered
49
+
50
+ return get_context
@@ -0,0 +1,56 @@
1
+ """Built-in context schema retrieval tool."""
2
+
3
+ from typing import Dict, Any, Callable, Optional
4
+
5
+
6
+ def create_soe_get_context_schema_tool(
7
+ execution_id: str,
8
+ backends,
9
+ tools_registry: dict = None,
10
+ ) -> Callable:
11
+ """
12
+ Factory function to create soe_get_context_schema tool.
13
+
14
+ Args:
15
+ execution_id: ID to access context schema data via backends
16
+ backends: Backend services to fetch context schema
17
+ tools_registry: Optional registry of available tools (unused by this tool)
18
+
19
+ Returns:
20
+ Configured soe_get_context_schema function
21
+ """
22
+
23
+ def soe_get_context_schema(
24
+ field_name: Optional[str] = None,
25
+ ) -> Dict[str, Any]:
26
+ """
27
+ Get context schema information from the current execution.
28
+
29
+ Args:
30
+ field_name: If provided, get only this specific field's definition.
31
+ If None, returns the full schema.
32
+
33
+ Returns:
34
+ If field_name provided: {"field_name": "...", "definition": {...}}
35
+ Otherwise: Full schema dict of field_name -> field_definition
36
+ """
37
+ schema = backends.context_schema.get_context_schema(execution_id)
38
+
39
+ if schema is None:
40
+ schema = {}
41
+
42
+ if field_name:
43
+ if field_name in schema:
44
+ return {
45
+ "field_name": field_name,
46
+ "definition": schema[field_name],
47
+ }
48
+ else:
49
+ return {
50
+ "error": f"Field '{field_name}' not found in context schema",
51
+ "available": list(schema.keys()),
52
+ }
53
+
54
+ return schema
55
+
56
+ return soe_get_context_schema
@@ -0,0 +1,63 @@
1
+ """Built-in identity retrieval tool."""
2
+
3
+ from typing import Dict, Any, Callable, Optional
4
+
5
+
6
+ def create_soe_get_identities_tool(
7
+ execution_id: str,
8
+ backends,
9
+ tools_registry: dict = None,
10
+ ) -> Callable:
11
+ """
12
+ Factory function to create soe_get_identities tool.
13
+
14
+ Args:
15
+ execution_id: ID to access identity data via backends
16
+ backends: Backend services to fetch identities
17
+ tools_registry: Optional registry of available tools (unused by this tool)
18
+
19
+ Returns:
20
+ Configured soe_get_identities function
21
+ """
22
+
23
+ def soe_get_identities(
24
+ identity_name: Optional[str] = None,
25
+ list_only: bool = False,
26
+ ) -> Dict[str, Any]:
27
+ """
28
+ Get identity information from the current execution.
29
+
30
+ Args:
31
+ identity_name: If provided, get only this specific identity's system prompt.
32
+ If None, returns info about all identities.
33
+ list_only: If True, only return identity names (not full system prompts).
34
+ Default is False.
35
+
36
+ Returns:
37
+ If list_only=True: {"identity_names": ["assistant", "expert", ...]}
38
+ If identity_name provided: {"identity_name": "...", "system_prompt": "..."}
39
+ Otherwise: Full dict of identity_name -> system_prompt
40
+ """
41
+ identities = backends.identity.get_identities(execution_id)
42
+
43
+ if identities is None:
44
+ identities = {}
45
+
46
+ if list_only:
47
+ return {"identity_names": list(identities.keys())}
48
+
49
+ if identity_name:
50
+ if identity_name in identities:
51
+ return {
52
+ "identity_name": identity_name,
53
+ "system_prompt": identities[identity_name],
54
+ }
55
+ else:
56
+ return {
57
+ "error": f"Identity '{identity_name}' not found",
58
+ "available": list(identities.keys()),
59
+ }
60
+
61
+ return identities
62
+
63
+ return soe_get_identities
@@ -0,0 +1,63 @@
1
+ """Built-in workflow retrieval tool."""
2
+
3
+ from typing import Dict, Any, Callable, Optional, List
4
+
5
+
6
+ def create_soe_get_workflows_tool(
7
+ execution_id: str,
8
+ backends,
9
+ tools_registry: dict = None,
10
+ ) -> Callable:
11
+ """
12
+ Factory function to create soe_get_workflows tool.
13
+
14
+ Args:
15
+ execution_id: ID to access workflow data via backends
16
+ backends: Backend services to fetch workflows
17
+ tools_registry: Optional registry of available tools (unused by this tool)
18
+
19
+ Returns:
20
+ Configured soe_get_workflows function
21
+ """
22
+
23
+ def soe_get_workflows(
24
+ workflow_name: Optional[str] = None,
25
+ list_only: bool = False,
26
+ ) -> Dict[str, Any]:
27
+ """
28
+ Get workflow information from the current registry.
29
+
30
+ Args:
31
+ workflow_name: If provided, get only this specific workflow's nodes.
32
+ If None, returns info about all workflows.
33
+ list_only: If True, only return workflow names (not full node configs).
34
+ Default is False for backward compatibility.
35
+
36
+ Returns:
37
+ If list_only=True: {"workflow_names": ["orchestrator", "ecosystem", ...]}
38
+ If workflow_name provided: {"workflow_name": "...", "nodes": {...}}
39
+ Otherwise: Full dict of workflow_name -> node_configs
40
+
41
+ Example usage:
42
+ soe_get_workflows(list_only=True) # Just get workflow names
43
+ soe_get_workflows(workflow_name="orchestrator") # Get specific workflow
44
+ soe_get_workflows() # Get everything (legacy behavior)
45
+ """
46
+ registry = backends.workflow.get_workflows_registry(execution_id)
47
+
48
+ if list_only:
49
+ return {"workflow_names": list(registry.keys())}
50
+
51
+ if workflow_name:
52
+ if workflow_name in registry:
53
+ return {
54
+ "workflow_name": workflow_name,
55
+ "nodes": list(registry[workflow_name].keys()),
56
+ "node_configs": registry[workflow_name],
57
+ }
58
+ else:
59
+ return {"error": f"Workflow '{workflow_name}' not found", "available": list(registry.keys())}
60
+
61
+ return registry
62
+
63
+ return soe_get_workflows
@@ -0,0 +1,80 @@
1
+ """Built-in context schema field injection tool."""
2
+
3
+ import json
4
+ from typing import Dict, Any, Callable
5
+ from ..types import EventTypes
6
+ from ..lib.register_event import register_event
7
+ from ..lib.yaml_parser import parse_yaml
8
+
9
+
10
+ def create_soe_inject_context_schema_field_tool(
11
+ execution_id: str,
12
+ backends,
13
+ tools_registry: dict = None,
14
+ ) -> Callable:
15
+ """
16
+ Factory function to create soe_inject_context_schema_field tool.
17
+
18
+ Args:
19
+ execution_id: ID to access context schema data via backends
20
+ backends: Backend services to fetch/update context schema
21
+ tools_registry: Optional registry of available tools (unused by this tool)
22
+
23
+ Returns:
24
+ Configured soe_inject_context_schema_field function
25
+ """
26
+
27
+ def soe_inject_context_schema_field(
28
+ field_name: str,
29
+ field_definition: str,
30
+ ) -> Dict[str, Any]:
31
+ """
32
+ Add or update a field in the context schema.
33
+
34
+ Args:
35
+ field_name: Name of the field to add/update
36
+ field_definition: JSON or YAML string with field definition
37
+ (e.g., {"type": "string", "description": "..."})
38
+
39
+ Returns:
40
+ Success confirmation with field info and action taken
41
+ """
42
+ parsed_definition = None
43
+
44
+ try:
45
+ parsed_definition = json.loads(field_definition)
46
+ except json.JSONDecodeError:
47
+ parsed_definition = parse_yaml(field_definition)
48
+
49
+ if not isinstance(parsed_definition, dict):
50
+ raise ValueError("Field definition must be a dictionary/object")
51
+
52
+ schema = backends.context_schema.get_context_schema(execution_id)
53
+
54
+ if schema is None:
55
+ schema = {}
56
+
57
+ action = "updated" if field_name in schema else "created"
58
+
59
+ schema[field_name] = parsed_definition
60
+ backends.context_schema.save_context_schema(execution_id, schema)
61
+
62
+ register_event(
63
+ backends,
64
+ execution_id,
65
+ EventTypes.NODE_EXECUTION,
66
+ {
67
+ "tool": "soe_inject_context_schema_field",
68
+ "field_name": field_name,
69
+ "action": action,
70
+ },
71
+ )
72
+
73
+ return {
74
+ "success": True,
75
+ "field_name": field_name,
76
+ "action": action,
77
+ "message": f"Successfully {action} field '{field_name}' in context schema",
78
+ }
79
+
80
+ return soe_inject_context_schema_field