ouroboros-ai 0.3.0__py3-none-any.whl → 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ouroboros-ai might be problematic. Click here for more details.

Files changed (42) hide show
  1. ouroboros/__init__.py +1 -1
  2. ouroboros/bigbang/__init__.py +9 -0
  3. ouroboros/bigbang/ontology.py +180 -0
  4. ouroboros/cli/commands/__init__.py +2 -0
  5. ouroboros/cli/commands/mcp.py +161 -0
  6. ouroboros/cli/commands/run.py +165 -27
  7. ouroboros/cli/main.py +2 -1
  8. ouroboros/core/ontology_aspect.py +455 -0
  9. ouroboros/core/ontology_questions.py +462 -0
  10. ouroboros/evaluation/__init__.py +16 -1
  11. ouroboros/evaluation/consensus.py +569 -11
  12. ouroboros/evaluation/models.py +81 -0
  13. ouroboros/events/ontology.py +135 -0
  14. ouroboros/mcp/__init__.py +83 -0
  15. ouroboros/mcp/client/__init__.py +20 -0
  16. ouroboros/mcp/client/adapter.py +632 -0
  17. ouroboros/mcp/client/manager.py +600 -0
  18. ouroboros/mcp/client/protocol.py +161 -0
  19. ouroboros/mcp/errors.py +377 -0
  20. ouroboros/mcp/resources/__init__.py +22 -0
  21. ouroboros/mcp/resources/handlers.py +328 -0
  22. ouroboros/mcp/server/__init__.py +21 -0
  23. ouroboros/mcp/server/adapter.py +408 -0
  24. ouroboros/mcp/server/protocol.py +291 -0
  25. ouroboros/mcp/server/security.py +636 -0
  26. ouroboros/mcp/tools/__init__.py +24 -0
  27. ouroboros/mcp/tools/definitions.py +351 -0
  28. ouroboros/mcp/tools/registry.py +269 -0
  29. ouroboros/mcp/types.py +333 -0
  30. ouroboros/orchestrator/__init__.py +31 -0
  31. ouroboros/orchestrator/events.py +40 -0
  32. ouroboros/orchestrator/mcp_config.py +419 -0
  33. ouroboros/orchestrator/mcp_tools.py +483 -0
  34. ouroboros/orchestrator/runner.py +119 -2
  35. ouroboros/providers/claude_code_adapter.py +75 -0
  36. ouroboros/strategies/__init__.py +23 -0
  37. ouroboros/strategies/devil_advocate.py +197 -0
  38. {ouroboros_ai-0.3.0.dist-info → ouroboros_ai-0.4.0.dist-info}/METADATA +10 -5
  39. {ouroboros_ai-0.3.0.dist-info → ouroboros_ai-0.4.0.dist-info}/RECORD +42 -17
  40. {ouroboros_ai-0.3.0.dist-info → ouroboros_ai-0.4.0.dist-info}/WHEEL +0 -0
  41. {ouroboros_ai-0.3.0.dist-info → ouroboros_ai-0.4.0.dist-info}/entry_points.txt +0 -0
  42. {ouroboros_ai-0.3.0.dist-info → ouroboros_ai-0.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,351 @@
1
+ """Ouroboros tool definitions for MCP server.
2
+
3
+ This module defines the standard Ouroboros tools that are exposed
4
+ via the MCP server:
5
+ - execute_seed: Execute a seed (task specification)
6
+ - session_status: Get current session status
7
+ - query_events: Query event history
8
+ """
9
+
10
+ from dataclasses import dataclass
11
+ from typing import Any
12
+
13
+ import structlog
14
+
15
+ from ouroboros.core.types import Result
16
+ from ouroboros.mcp.errors import MCPServerError, MCPToolError
17
+ from ouroboros.mcp.types import (
18
+ ContentType,
19
+ MCPContentItem,
20
+ MCPToolDefinition,
21
+ MCPToolParameter,
22
+ MCPToolResult,
23
+ ToolInputType,
24
+ )
25
+
26
+ log = structlog.get_logger(__name__)
27
+
28
+
29
+ @dataclass
30
+ class ExecuteSeedHandler:
31
+ """Handler for the execute_seed tool.
32
+
33
+ Executes a seed (task specification) in the Ouroboros system.
34
+ This is the primary entry point for running tasks.
35
+ """
36
+
37
+ @property
38
+ def definition(self) -> MCPToolDefinition:
39
+ """Return the tool definition."""
40
+ return MCPToolDefinition(
41
+ name="ouroboros_execute_seed",
42
+ description=(
43
+ "Execute a seed (task specification) in Ouroboros. "
44
+ "A seed defines a task to be executed with acceptance criteria."
45
+ ),
46
+ parameters=(
47
+ MCPToolParameter(
48
+ name="seed_content",
49
+ type=ToolInputType.STRING,
50
+ description="The seed content describing the task to execute",
51
+ required=True,
52
+ ),
53
+ MCPToolParameter(
54
+ name="session_id",
55
+ type=ToolInputType.STRING,
56
+ description="Optional session ID to resume. If not provided, a new session is created.",
57
+ required=False,
58
+ ),
59
+ MCPToolParameter(
60
+ name="model_tier",
61
+ type=ToolInputType.STRING,
62
+ description="Model tier to use (small, medium, large). Default: medium",
63
+ required=False,
64
+ default="medium",
65
+ enum=("small", "medium", "large"),
66
+ ),
67
+ MCPToolParameter(
68
+ name="max_iterations",
69
+ type=ToolInputType.INTEGER,
70
+ description="Maximum number of execution iterations. Default: 10",
71
+ required=False,
72
+ default=10,
73
+ ),
74
+ ),
75
+ )
76
+
77
+ async def handle(
78
+ self,
79
+ arguments: dict[str, Any],
80
+ ) -> Result[MCPToolResult, MCPServerError]:
81
+ """Handle a seed execution request.
82
+
83
+ Args:
84
+ arguments: Tool arguments including seed_content.
85
+
86
+ Returns:
87
+ Result containing execution result or error.
88
+ """
89
+ seed_content = arguments.get("seed_content")
90
+ if not seed_content:
91
+ return Result.err(
92
+ MCPToolError(
93
+ "seed_content is required",
94
+ tool_name="ouroboros_execute_seed",
95
+ )
96
+ )
97
+
98
+ session_id = arguments.get("session_id")
99
+ model_tier = arguments.get("model_tier", "medium")
100
+ max_iterations = arguments.get("max_iterations", 10)
101
+
102
+ log.info(
103
+ "mcp.tool.execute_seed",
104
+ session_id=session_id,
105
+ model_tier=model_tier,
106
+ max_iterations=max_iterations,
107
+ )
108
+
109
+ # This is a placeholder - actual implementation would integrate
110
+ # with the Ouroboros execution engine
111
+ try:
112
+ # TODO: Integrate with actual execution engine
113
+ result_text = (
114
+ f"Seed execution initiated.\n"
115
+ f"Session ID: {session_id or 'new'}\n"
116
+ f"Model tier: {model_tier}\n"
117
+ f"Max iterations: {max_iterations}\n"
118
+ f"Seed content: {seed_content[:100]}..."
119
+ )
120
+
121
+ return Result.ok(
122
+ MCPToolResult(
123
+ content=(
124
+ MCPContentItem(type=ContentType.TEXT, text=result_text),
125
+ ),
126
+ is_error=False,
127
+ meta={"session_id": session_id or "new-session-id"},
128
+ )
129
+ )
130
+ except Exception as e:
131
+ log.error("mcp.tool.execute_seed.error", error=str(e))
132
+ return Result.err(
133
+ MCPToolError(
134
+ f"Seed execution failed: {e}",
135
+ tool_name="ouroboros_execute_seed",
136
+ )
137
+ )
138
+
139
+
140
+ @dataclass
141
+ class SessionStatusHandler:
142
+ """Handler for the session_status tool.
143
+
144
+ Returns the current status of an Ouroboros session.
145
+ """
146
+
147
+ @property
148
+ def definition(self) -> MCPToolDefinition:
149
+ """Return the tool definition."""
150
+ return MCPToolDefinition(
151
+ name="ouroboros_session_status",
152
+ description=(
153
+ "Get the status of an Ouroboros session. "
154
+ "Returns information about the current phase, progress, and any errors."
155
+ ),
156
+ parameters=(
157
+ MCPToolParameter(
158
+ name="session_id",
159
+ type=ToolInputType.STRING,
160
+ description="The session ID to query",
161
+ required=True,
162
+ ),
163
+ ),
164
+ )
165
+
166
+ async def handle(
167
+ self,
168
+ arguments: dict[str, Any],
169
+ ) -> Result[MCPToolResult, MCPServerError]:
170
+ """Handle a session status request.
171
+
172
+ Args:
173
+ arguments: Tool arguments including session_id.
174
+
175
+ Returns:
176
+ Result containing session status or error.
177
+ """
178
+ session_id = arguments.get("session_id")
179
+ if not session_id:
180
+ return Result.err(
181
+ MCPToolError(
182
+ "session_id is required",
183
+ tool_name="ouroboros_session_status",
184
+ )
185
+ )
186
+
187
+ log.info("mcp.tool.session_status", session_id=session_id)
188
+
189
+ try:
190
+ # TODO: Integrate with actual session management
191
+ status_text = (
192
+ f"Session: {session_id}\n"
193
+ f"Status: active\n"
194
+ f"Phase: execution\n"
195
+ f"Progress: 60%\n"
196
+ f"Current iteration: 3/10\n"
197
+ )
198
+
199
+ return Result.ok(
200
+ MCPToolResult(
201
+ content=(
202
+ MCPContentItem(type=ContentType.TEXT, text=status_text),
203
+ ),
204
+ is_error=False,
205
+ meta={
206
+ "session_id": session_id,
207
+ "status": "active",
208
+ "phase": "execution",
209
+ "progress": 0.6,
210
+ },
211
+ )
212
+ )
213
+ except Exception as e:
214
+ log.error("mcp.tool.session_status.error", error=str(e))
215
+ return Result.err(
216
+ MCPToolError(
217
+ f"Failed to get session status: {e}",
218
+ tool_name="ouroboros_session_status",
219
+ )
220
+ )
221
+
222
+
223
+ @dataclass
224
+ class QueryEventsHandler:
225
+ """Handler for the query_events tool.
226
+
227
+ Queries the event history for a session or across sessions.
228
+ """
229
+
230
+ @property
231
+ def definition(self) -> MCPToolDefinition:
232
+ """Return the tool definition."""
233
+ return MCPToolDefinition(
234
+ name="ouroboros_query_events",
235
+ description=(
236
+ "Query the event history for an Ouroboros session. "
237
+ "Returns a list of events matching the specified criteria."
238
+ ),
239
+ parameters=(
240
+ MCPToolParameter(
241
+ name="session_id",
242
+ type=ToolInputType.STRING,
243
+ description="Filter events by session ID. If not provided, returns events across all sessions.",
244
+ required=False,
245
+ ),
246
+ MCPToolParameter(
247
+ name="event_type",
248
+ type=ToolInputType.STRING,
249
+ description="Filter by event type (e.g., 'execution', 'evaluation', 'error')",
250
+ required=False,
251
+ ),
252
+ MCPToolParameter(
253
+ name="limit",
254
+ type=ToolInputType.INTEGER,
255
+ description="Maximum number of events to return. Default: 50",
256
+ required=False,
257
+ default=50,
258
+ ),
259
+ MCPToolParameter(
260
+ name="offset",
261
+ type=ToolInputType.INTEGER,
262
+ description="Number of events to skip for pagination. Default: 0",
263
+ required=False,
264
+ default=0,
265
+ ),
266
+ ),
267
+ )
268
+
269
+ async def handle(
270
+ self,
271
+ arguments: dict[str, Any],
272
+ ) -> Result[MCPToolResult, MCPServerError]:
273
+ """Handle an event query request.
274
+
275
+ Args:
276
+ arguments: Tool arguments for filtering events.
277
+
278
+ Returns:
279
+ Result containing matching events or error.
280
+ """
281
+ session_id = arguments.get("session_id")
282
+ event_type = arguments.get("event_type")
283
+ limit = arguments.get("limit", 50)
284
+ offset = arguments.get("offset", 0)
285
+
286
+ log.info(
287
+ "mcp.tool.query_events",
288
+ session_id=session_id,
289
+ event_type=event_type,
290
+ limit=limit,
291
+ offset=offset,
292
+ )
293
+
294
+ try:
295
+ # TODO: Integrate with actual event store
296
+ events_text = (
297
+ f"Event Query Results\n"
298
+ f"==================\n"
299
+ f"Session: {session_id or 'all'}\n"
300
+ f"Type filter: {event_type or 'all'}\n"
301
+ f"Showing {offset} to {offset + limit}\n\n"
302
+ f"1. [execution] Started seed execution\n"
303
+ f"2. [evaluation] Mechanical check passed\n"
304
+ f"3. [execution] Iteration 2 completed\n"
305
+ )
306
+
307
+ return Result.ok(
308
+ MCPToolResult(
309
+ content=(
310
+ MCPContentItem(type=ContentType.TEXT, text=events_text),
311
+ ),
312
+ is_error=False,
313
+ meta={
314
+ "total_events": 3,
315
+ "offset": offset,
316
+ "limit": limit,
317
+ },
318
+ )
319
+ )
320
+ except Exception as e:
321
+ log.error("mcp.tool.query_events.error", error=str(e))
322
+ return Result.err(
323
+ MCPToolError(
324
+ f"Failed to query events: {e}",
325
+ tool_name="ouroboros_query_events",
326
+ )
327
+ )
328
+
329
+
330
+ # Convenience functions for handler access
331
+ def execute_seed_handler() -> ExecuteSeedHandler:
332
+ """Create an ExecuteSeedHandler instance."""
333
+ return ExecuteSeedHandler()
334
+
335
+
336
+ def session_status_handler() -> SessionStatusHandler:
337
+ """Create a SessionStatusHandler instance."""
338
+ return SessionStatusHandler()
339
+
340
+
341
+ def query_events_handler() -> QueryEventsHandler:
342
+ """Create a QueryEventsHandler instance."""
343
+ return QueryEventsHandler()
344
+
345
+
346
+ # List of all Ouroboros tools for registration
347
+ OUROBOROS_TOOLS: tuple[ExecuteSeedHandler | SessionStatusHandler | QueryEventsHandler, ...] = (
348
+ ExecuteSeedHandler(),
349
+ SessionStatusHandler(),
350
+ QueryEventsHandler(),
351
+ )
@@ -0,0 +1,269 @@
1
+ """Tool registry for managing MCP tool handlers.
2
+
3
+ This module provides a registry for managing tool handlers, supporting
4
+ dynamic registration, discovery, and invocation of tools.
5
+ """
6
+
7
+ import threading
8
+ from collections.abc import Sequence
9
+ from typing import Any
10
+
11
+ import structlog
12
+
13
+ from ouroboros.core.types import Result
14
+ from ouroboros.mcp.errors import MCPResourceNotFoundError, MCPServerError, MCPToolError
15
+ from ouroboros.mcp.server.protocol import ToolHandler
16
+ from ouroboros.mcp.types import MCPToolDefinition, MCPToolResult
17
+
18
+ log = structlog.get_logger(__name__)
19
+
20
+
21
+ class ToolRegistry:
22
+ """Registry for managing MCP tool handlers.
23
+
24
+ Provides a centralized place to register, discover, and invoke tools.
25
+ Supports grouping tools by category and provides metadata for discovery.
26
+
27
+ Example:
28
+ registry = ToolRegistry()
29
+
30
+ # Register individual handlers
31
+ registry.register(ExecuteSeedHandler())
32
+ registry.register(SessionStatusHandler())
33
+
34
+ # Or register multiple at once
35
+ registry.register_all([
36
+ ExecuteSeedHandler(),
37
+ SessionStatusHandler(),
38
+ ])
39
+
40
+ # List tools
41
+ tools = registry.list_tools()
42
+
43
+ # Call a tool
44
+ result = await registry.call("execute_seed", {"seed_id": "123"})
45
+ """
46
+
47
+ def __init__(self) -> None:
48
+ """Initialize the registry."""
49
+ self._handlers: dict[str, ToolHandler] = {}
50
+ self._categories: dict[str, set[str]] = {}
51
+
52
+ @property
53
+ def tool_count(self) -> int:
54
+ """Return the number of registered tools."""
55
+ return len(self._handlers)
56
+
57
+ def register(
58
+ self,
59
+ handler: ToolHandler,
60
+ *,
61
+ category: str = "default",
62
+ ) -> None:
63
+ """Register a tool handler.
64
+
65
+ Args:
66
+ handler: The tool handler to register.
67
+ category: Optional category for grouping tools.
68
+
69
+ Raises:
70
+ ValueError: If a tool with the same name is already registered.
71
+ """
72
+ name = handler.definition.name
73
+
74
+ if name in self._handlers:
75
+ msg = f"Tool already registered: {name}"
76
+ raise ValueError(msg)
77
+
78
+ self._handlers[name] = handler
79
+
80
+ # Track category
81
+ if category not in self._categories:
82
+ self._categories[category] = set()
83
+ self._categories[category].add(name)
84
+
85
+ log.info("mcp.registry.tool_registered", tool=name, category=category)
86
+
87
+ def register_all(
88
+ self,
89
+ handlers: Sequence[ToolHandler],
90
+ *,
91
+ category: str = "default",
92
+ ) -> None:
93
+ """Register multiple tool handlers.
94
+
95
+ Args:
96
+ handlers: The tool handlers to register.
97
+ category: Optional category for grouping tools.
98
+ """
99
+ for handler in handlers:
100
+ self.register(handler, category=category)
101
+
102
+ def unregister(self, name: str) -> bool:
103
+ """Unregister a tool handler.
104
+
105
+ Args:
106
+ name: Name of the tool to unregister.
107
+
108
+ Returns:
109
+ True if the tool was unregistered, False if not found.
110
+ """
111
+ if name not in self._handlers:
112
+ return False
113
+
114
+ del self._handlers[name]
115
+
116
+ # Remove from categories
117
+ for category_tools in self._categories.values():
118
+ category_tools.discard(name)
119
+
120
+ log.info("mcp.registry.tool_unregistered", tool=name)
121
+ return True
122
+
123
+ def get(self, name: str) -> ToolHandler | None:
124
+ """Get a tool handler by name.
125
+
126
+ Args:
127
+ name: Name of the tool.
128
+
129
+ Returns:
130
+ The tool handler or None if not found.
131
+ """
132
+ return self._handlers.get(name)
133
+
134
+ def list_tools(self, category: str | None = None) -> Sequence[MCPToolDefinition]:
135
+ """List all registered tools.
136
+
137
+ Args:
138
+ category: Optional category to filter by.
139
+
140
+ Returns:
141
+ Sequence of tool definitions.
142
+ """
143
+ if category is not None:
144
+ tool_names = self._categories.get(category, set())
145
+ return tuple(
146
+ self._handlers[name].definition
147
+ for name in tool_names
148
+ if name in self._handlers
149
+ )
150
+
151
+ return tuple(h.definition for h in self._handlers.values())
152
+
153
+ def list_categories(self) -> Sequence[str]:
154
+ """List all tool categories.
155
+
156
+ Returns:
157
+ Sequence of category names.
158
+ """
159
+ return tuple(self._categories.keys())
160
+
161
+ def tools_in_category(self, category: str) -> Sequence[str]:
162
+ """List tool names in a category.
163
+
164
+ Args:
165
+ category: Category name.
166
+
167
+ Returns:
168
+ Sequence of tool names in the category.
169
+ """
170
+ return tuple(self._categories.get(category, set()))
171
+
172
+ async def call(
173
+ self,
174
+ name: str,
175
+ arguments: dict[str, Any],
176
+ ) -> Result[MCPToolResult, MCPServerError]:
177
+ """Call a registered tool.
178
+
179
+ Args:
180
+ name: Name of the tool to call.
181
+ arguments: Arguments for the tool.
182
+
183
+ Returns:
184
+ Result containing the tool result or an error.
185
+ """
186
+ handler = self._handlers.get(name)
187
+ if not handler:
188
+ return Result.err(
189
+ MCPResourceNotFoundError(
190
+ f"Tool not found: {name}",
191
+ resource_type="tool",
192
+ resource_id=name,
193
+ )
194
+ )
195
+
196
+ try:
197
+ log.debug("mcp.registry.calling_tool", tool=name)
198
+ result = await handler.handle(arguments)
199
+ return result
200
+ except Exception as e:
201
+ log.error("mcp.registry.tool_error", tool=name, error=str(e))
202
+ return Result.err(
203
+ MCPToolError(
204
+ f"Tool execution failed: {e}",
205
+ tool_name=name,
206
+ )
207
+ )
208
+
209
+ def get_definition(self, name: str) -> MCPToolDefinition | None:
210
+ """Get the definition for a tool.
211
+
212
+ Args:
213
+ name: Name of the tool.
214
+
215
+ Returns:
216
+ The tool definition or None if not found.
217
+ """
218
+ handler = self._handlers.get(name)
219
+ return handler.definition if handler else None
220
+
221
+ def has_tool(self, name: str) -> bool:
222
+ """Check if a tool is registered.
223
+
224
+ Args:
225
+ name: Name of the tool.
226
+
227
+ Returns:
228
+ True if the tool is registered.
229
+ """
230
+ return name in self._handlers
231
+
232
+ def clear(self) -> None:
233
+ """Clear all registered tools."""
234
+ self._handlers.clear()
235
+ self._categories.clear()
236
+ log.info("mcp.registry.cleared")
237
+
238
+
239
+ # Global registry instance for convenience
240
+ _global_registry: ToolRegistry | None = None
241
+ _global_registry_lock = threading.Lock()
242
+
243
+
244
+ def get_global_registry() -> ToolRegistry:
245
+ """Get the global tool registry.
246
+
247
+ Returns:
248
+ The global ToolRegistry instance.
249
+ """
250
+ global _global_registry
251
+ if _global_registry is None:
252
+ with _global_registry_lock:
253
+ if _global_registry is None:
254
+ _global_registry = ToolRegistry()
255
+ return _global_registry
256
+
257
+
258
+ def register_tool(
259
+ handler: ToolHandler,
260
+ *,
261
+ category: str = "default",
262
+ ) -> None:
263
+ """Register a tool handler with the global registry.
264
+
265
+ Args:
266
+ handler: The tool handler to register.
267
+ category: Optional category for grouping tools.
268
+ """
269
+ get_global_registry().register(handler, category=category)