ouroboros-ai 0.2.3__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 (44) hide show
  1. ouroboros/__init__.py +1 -1
  2. ouroboros/bigbang/__init__.py +9 -0
  3. ouroboros/bigbang/interview.py +16 -18
  4. ouroboros/bigbang/ontology.py +180 -0
  5. ouroboros/cli/commands/__init__.py +2 -0
  6. ouroboros/cli/commands/init.py +162 -97
  7. ouroboros/cli/commands/mcp.py +161 -0
  8. ouroboros/cli/commands/run.py +165 -27
  9. ouroboros/cli/main.py +2 -1
  10. ouroboros/core/ontology_aspect.py +455 -0
  11. ouroboros/core/ontology_questions.py +462 -0
  12. ouroboros/evaluation/__init__.py +16 -1
  13. ouroboros/evaluation/consensus.py +569 -11
  14. ouroboros/evaluation/models.py +81 -0
  15. ouroboros/events/ontology.py +135 -0
  16. ouroboros/mcp/__init__.py +83 -0
  17. ouroboros/mcp/client/__init__.py +20 -0
  18. ouroboros/mcp/client/adapter.py +632 -0
  19. ouroboros/mcp/client/manager.py +600 -0
  20. ouroboros/mcp/client/protocol.py +161 -0
  21. ouroboros/mcp/errors.py +377 -0
  22. ouroboros/mcp/resources/__init__.py +22 -0
  23. ouroboros/mcp/resources/handlers.py +328 -0
  24. ouroboros/mcp/server/__init__.py +21 -0
  25. ouroboros/mcp/server/adapter.py +408 -0
  26. ouroboros/mcp/server/protocol.py +291 -0
  27. ouroboros/mcp/server/security.py +636 -0
  28. ouroboros/mcp/tools/__init__.py +24 -0
  29. ouroboros/mcp/tools/definitions.py +351 -0
  30. ouroboros/mcp/tools/registry.py +269 -0
  31. ouroboros/mcp/types.py +333 -0
  32. ouroboros/orchestrator/__init__.py +31 -0
  33. ouroboros/orchestrator/events.py +40 -0
  34. ouroboros/orchestrator/mcp_config.py +419 -0
  35. ouroboros/orchestrator/mcp_tools.py +483 -0
  36. ouroboros/orchestrator/runner.py +119 -2
  37. ouroboros/providers/claude_code_adapter.py +75 -0
  38. ouroboros/strategies/__init__.py +23 -0
  39. ouroboros/strategies/devil_advocate.py +197 -0
  40. {ouroboros_ai-0.2.3.dist-info → ouroboros_ai-0.4.0.dist-info}/METADATA +73 -17
  41. {ouroboros_ai-0.2.3.dist-info → ouroboros_ai-0.4.0.dist-info}/RECORD +44 -19
  42. {ouroboros_ai-0.2.3.dist-info → ouroboros_ai-0.4.0.dist-info}/WHEEL +0 -0
  43. {ouroboros_ai-0.2.3.dist-info → ouroboros_ai-0.4.0.dist-info}/entry_points.txt +0 -0
  44. {ouroboros_ai-0.2.3.dist-info → ouroboros_ai-0.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,455 @@
1
+ """AOP Framework for Ontological Analysis.
2
+
3
+ This module provides Aspect-Oriented Programming capabilities for
4
+ applying ontological analysis as a cross-cutting concern across
5
+ Interview, Consensus, and Resilience phases.
6
+
7
+ Pattern: Protocol + Strategy + Dependency Injection
8
+ - OntologicalAspect: Central weaver (Around Advice)
9
+ - OntologyStrategy: Protocol for join-point-specific analysis
10
+ - TTLCache: Performance optimization for LLM calls
11
+
12
+ Usage:
13
+ # Create aspect with strategy
14
+ aspect = OntologicalAspect(
15
+ strategy=DevilAdvocateStrategy(llm_adapter),
16
+ )
17
+
18
+ # Execute with ontological analysis
19
+ result = await aspect.execute(
20
+ context=consensus_context,
21
+ core_operation=lambda ctx: consensus.deliberate(ctx),
22
+ )
23
+
24
+ Design Reference: docs/ontological-framework/aop-design.md
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ from collections.abc import Awaitable, Callable
30
+ from dataclasses import dataclass, field
31
+ from enum import StrEnum
32
+ import logging
33
+ from typing import TYPE_CHECKING, Any, Generic, Protocol, TypeVar
34
+
35
+ from cachetools import TTLCache
36
+
37
+ from ouroboros.core.errors import OuroborosError
38
+ from ouroboros.core.types import Result
39
+
40
+ if TYPE_CHECKING:
41
+ from ouroboros.events.base import BaseEvent
42
+
43
+ log = logging.getLogger(__name__)
44
+
45
+
46
+ # =============================================================================
47
+ # Core Types
48
+ # =============================================================================
49
+
50
+
51
+ class OntologicalJoinPoint(StrEnum):
52
+ """Join points where ontological analysis is applied.
53
+
54
+ These correspond to Ouroboros phases where cross-cutting
55
+ ontological concerns are relevant.
56
+ """
57
+
58
+ INTERVIEW = "interview"
59
+ """Phase 0: Requirement clarification - ensures user asks about root problem."""
60
+
61
+ RESILIENCE = "resilience"
62
+ """Phase 3: Stagnation recovery - CONTRARIAN challenges assumptions."""
63
+
64
+ CONSENSUS = "consensus"
65
+ """Phase 4: Result evaluation - Devil's Advocate checks root vs symptom."""
66
+
67
+
68
+ @dataclass(frozen=True, slots=True)
69
+ class AnalysisResult:
70
+ """Result of ontological analysis by a Strategy.
71
+
72
+ This represents the AOP verdict (valid/invalid) rather than
73
+ the full ontological insight content.
74
+
75
+ Attributes:
76
+ is_valid: Whether the subject passes ontological scrutiny.
77
+ confidence: Analysis confidence (0.0-1.0).
78
+ reasoning: Explanation of the verdict.
79
+ suggestions: Refinement suggestions if invalid.
80
+ """
81
+
82
+ is_valid: bool
83
+ confidence: float
84
+ reasoning: tuple[str, ...]
85
+ suggestions: tuple[str, ...]
86
+
87
+ @property
88
+ def needs_refinement(self) -> bool:
89
+ """True if invalid and has actionable suggestions."""
90
+ return not self.is_valid and len(self.suggestions) > 0
91
+
92
+ @classmethod
93
+ def valid(
94
+ cls,
95
+ confidence: float = 1.0,
96
+ reasoning: tuple[str, ...] | list[str] = (),
97
+ ) -> AnalysisResult:
98
+ """Create a passing result."""
99
+ return cls(
100
+ is_valid=True,
101
+ confidence=confidence,
102
+ reasoning=tuple(reasoning) if isinstance(reasoning, list) else reasoning,
103
+ suggestions=(),
104
+ )
105
+
106
+ @classmethod
107
+ def invalid(
108
+ cls,
109
+ reasoning: tuple[str, ...] | list[str],
110
+ suggestions: tuple[str, ...] | list[str] = (),
111
+ confidence: float = 0.8,
112
+ ) -> AnalysisResult:
113
+ """Create a failing result."""
114
+ return cls(
115
+ is_valid=False,
116
+ confidence=confidence,
117
+ reasoning=tuple(reasoning) if isinstance(reasoning, list) else reasoning,
118
+ suggestions=tuple(suggestions) if isinstance(suggestions, list) else suggestions,
119
+ )
120
+
121
+
122
+ class OntologicalViolationError(OuroborosError):
123
+ """Error raised when ontological analysis blocks execution.
124
+
125
+ This error contains the analysis result explaining why
126
+ the operation was halted.
127
+
128
+ Attributes:
129
+ result: The AnalysisResult that caused the violation.
130
+ join_point: Which phase triggered the violation.
131
+ """
132
+
133
+ def __init__(
134
+ self,
135
+ result: AnalysisResult,
136
+ *,
137
+ join_point: OntologicalJoinPoint | None = None,
138
+ ) -> None:
139
+ """Initialize violation error.
140
+
141
+ Args:
142
+ result: The analysis result that caused the violation.
143
+ join_point: Which phase triggered this error.
144
+ """
145
+ self.result = result
146
+ self.join_point = join_point
147
+ super().__init__(
148
+ message="Ontological violation: analysis blocked execution",
149
+ details={
150
+ "is_valid": result.is_valid,
151
+ "confidence": result.confidence,
152
+ "reasoning": result.reasoning,
153
+ "suggestions": result.suggestions,
154
+ "join_point": join_point.value if join_point else None,
155
+ },
156
+ )
157
+
158
+
159
+ # =============================================================================
160
+ # Strategy Protocol
161
+ # =============================================================================
162
+
163
+ C = TypeVar("C", contravariant=True) # Context type (input)
164
+
165
+
166
+ class OntologyStrategy(Protocol[C]):
167
+ """Protocol for join-point-specific ontological analysis.
168
+
169
+ Each Strategy implements phase-specific logic:
170
+ - InterviewOntologyStrategy: Checks if user asks about root problem
171
+ - DevilAdvocateStrategy: Validates solution addresses root cause
172
+ - ContrarianStrategy: Challenges assumptions when stuck
173
+
174
+ Key Design Decision:
175
+ Strategy provides get_cache_key(), not Aspect.
176
+ This allows fine-grained control over what matters for caching.
177
+ """
178
+
179
+ @property
180
+ def join_point(self) -> OntologicalJoinPoint:
181
+ """Which phase this strategy is for."""
182
+ ...
183
+
184
+ def get_cache_key(self, context: C) -> str:
185
+ """Return cache key for this context.
186
+
187
+ Strategy decides which parts of context are relevant for caching.
188
+ Example: Consensus only cares about artifact hash, not full state.
189
+
190
+ Args:
191
+ context: The analysis context.
192
+
193
+ Returns:
194
+ A string cache key.
195
+ """
196
+ ...
197
+
198
+ async def analyze(self, context: C) -> AnalysisResult:
199
+ """Perform ontological analysis on the given context.
200
+
201
+ Args:
202
+ context: Phase-specific context.
203
+
204
+ Returns:
205
+ AnalysisResult with validity, confidence, and reasoning.
206
+ """
207
+ ...
208
+
209
+
210
+ # =============================================================================
211
+ # Ontological Aspect (Weaver)
212
+ # =============================================================================
213
+
214
+ T = TypeVar("T") # Result type (output)
215
+ E = TypeVar("E", bound=OuroborosError) # Error type
216
+
217
+
218
+ @dataclass
219
+ class OntologicalAspect(Generic[C, T, E]):
220
+ """Central AOP Weaver for Ontological Analysis.
221
+
222
+ Implements the "Around Advice" pattern:
223
+ 1. Pre-execution: Run ontological analysis
224
+ 2. Decision: Proceed or halt based on analysis
225
+ 3. Execution: Run core operation if valid
226
+ 4. Post-execution: Emit events
227
+
228
+ Type Parameters:
229
+ C: Context type passed to strategy
230
+ T: Success type from core operation
231
+ E: Error type from core operation (must extend OuroborosError)
232
+
233
+ Configuration:
234
+ halt_on_violation: Return error on ontological failure (default: True)
235
+ strict_mode: Fail closed on LLM errors (default: True)
236
+ cache_ttl: Cache TTL in seconds (default: 300)
237
+ cache_maxsize: Max cached entries (default: 100)
238
+
239
+ Example:
240
+ aspect = OntologicalAspect(
241
+ strategy=DevilAdvocateStrategy(llm),
242
+ event_emitter=event_store.emit,
243
+ )
244
+
245
+ result = await aspect.execute(
246
+ context=consensus_context,
247
+ core_operation=lambda ctx: consensus.deliberate(ctx),
248
+ )
249
+
250
+ if result.is_err:
251
+ if isinstance(result.error, OntologicalViolationError):
252
+ # Handle ontological violation
253
+ print(result.error.result.suggestions)
254
+ """
255
+
256
+ strategy: OntologyStrategy[C]
257
+ event_emitter: Callable[[BaseEvent], Awaitable[None]] | None = None
258
+ halt_on_violation: bool = True
259
+ strict_mode: bool = True # fail_closed by default
260
+ cache_ttl: int = 300 # 5 minutes
261
+ cache_maxsize: int = 100
262
+ _cache: TTLCache[str, AnalysisResult] = field(
263
+ default_factory=lambda: TTLCache(maxsize=100, ttl=300),
264
+ repr=False,
265
+ )
266
+
267
+ def __post_init__(self) -> None:
268
+ """Initialize cache with configured TTL and maxsize."""
269
+ # Recreate cache with actual config values if they differ from defaults
270
+ if self.cache_ttl != 300 or self.cache_maxsize != 100:
271
+ object.__setattr__(
272
+ self,
273
+ "_cache",
274
+ TTLCache(maxsize=self.cache_maxsize, ttl=self.cache_ttl),
275
+ )
276
+
277
+ async def execute(
278
+ self,
279
+ context: C,
280
+ core_operation: Callable[[C], Awaitable[Result[T, E]]],
281
+ *,
282
+ skip_analysis: bool = False,
283
+ ) -> Result[T, OntologicalViolationError | E]:
284
+ """Execute with ontological analysis (Around Advice).
285
+
286
+ Args:
287
+ context: Phase-specific context passed to strategy.
288
+ core_operation: The operation returning Result[T, E].
289
+ skip_analysis: Skip ontological check (for known-safe hot paths).
290
+
291
+ Returns:
292
+ Result with union error type:
293
+ - Ok(T) if analysis passes and operation succeeds
294
+ - Err(OntologicalViolationError) if analysis fails and halt_on_violation
295
+ - Err(E) if core operation fails
296
+ """
297
+ # Escape hatch for hot paths
298
+ if skip_analysis:
299
+ return await core_operation(context)
300
+
301
+ # Get cache key from Strategy (not self-computed)
302
+ cache_key = self.strategy.get_cache_key(context)
303
+
304
+ # Check cache
305
+ if cache_key in self._cache:
306
+ analysis = self._cache[cache_key]
307
+ log.debug(
308
+ "ontology.analysis.cache_hit",
309
+ extra={
310
+ "join_point": self.strategy.join_point,
311
+ "cache_key": cache_key[:16],
312
+ },
313
+ )
314
+ else:
315
+ try:
316
+ analysis = await self.strategy.analyze(context)
317
+ self._cache[cache_key] = analysis
318
+ log.debug(
319
+ "ontology.analysis.completed",
320
+ extra={
321
+ "join_point": self.strategy.join_point,
322
+ "is_valid": analysis.is_valid,
323
+ "confidence": analysis.confidence,
324
+ },
325
+ )
326
+ except Exception as e:
327
+ # LLM provider failure
328
+ if self.strict_mode:
329
+ # fail_closed: propagate error
330
+ log.error(
331
+ "ontology.analysis.failed_closed",
332
+ extra={
333
+ "join_point": self.strategy.join_point,
334
+ "error": str(e),
335
+ },
336
+ )
337
+ raise
338
+ else:
339
+ # fail_open: log warning, proceed without analysis
340
+ log.warning(
341
+ "ontology.analysis.failed_open",
342
+ extra={
343
+ "join_point": self.strategy.join_point,
344
+ "error": str(e),
345
+ },
346
+ )
347
+ return await core_operation(context)
348
+
349
+ # Handle violation
350
+ if not analysis.is_valid:
351
+ await self._emit_violation_event(analysis)
352
+
353
+ if self.halt_on_violation:
354
+ return Result.err(
355
+ OntologicalViolationError(
356
+ analysis,
357
+ join_point=self.strategy.join_point,
358
+ )
359
+ )
360
+ # else: log and continue (non-halting mode)
361
+ log.warning(
362
+ "ontology.analysis.violation_ignored",
363
+ extra={
364
+ "join_point": self.strategy.join_point,
365
+ "reasoning": analysis.reasoning,
366
+ },
367
+ )
368
+
369
+ # Handle valid
370
+ if analysis.is_valid:
371
+ await self._emit_passed_event(analysis)
372
+
373
+ # Execute core operation (returns Result[T, E])
374
+ return await core_operation(context)
375
+
376
+ async def _emit_violation_event(self, analysis: AnalysisResult) -> None:
377
+ """Emit ontological violation event."""
378
+ if self.event_emitter:
379
+ from ouroboros.events.ontology import OntologicalViolationEvent
380
+
381
+ event = OntologicalViolationEvent(
382
+ join_point=self.strategy.join_point,
383
+ confidence=analysis.confidence,
384
+ reasoning=analysis.reasoning,
385
+ suggestions=analysis.suggestions,
386
+ )
387
+ await self.event_emitter(event)
388
+
389
+ async def _emit_passed_event(self, analysis: AnalysisResult) -> None:
390
+ """Emit ontological passed event."""
391
+ if self.event_emitter:
392
+ from ouroboros.events.ontology import OntologicalPassedEvent
393
+
394
+ event = OntologicalPassedEvent(
395
+ join_point=self.strategy.join_point,
396
+ confidence=analysis.confidence,
397
+ )
398
+ await self.event_emitter(event)
399
+
400
+
401
+ # =============================================================================
402
+ # Factory Function
403
+ # =============================================================================
404
+
405
+
406
+ def create_ontology_aspect(
407
+ strategy: OntologyStrategy[Any],
408
+ event_emitter: Callable[[BaseEvent], Awaitable[None]] | None = None,
409
+ *,
410
+ halt_on_violation: bool = True,
411
+ strict_mode: bool = True,
412
+ cache_ttl: int = 300,
413
+ cache_maxsize: int = 100,
414
+ ) -> OntologicalAspect[Any, Any, Any]:
415
+ """Factory to create configured ontological aspect.
416
+
417
+ Args:
418
+ strategy: The OntologyStrategy to use.
419
+ event_emitter: Optional event emission callback.
420
+ halt_on_violation: Return error on violation (default: True).
421
+ strict_mode: Fail closed on LLM errors (default: True).
422
+ cache_ttl: Cache TTL in seconds (default: 300).
423
+ cache_maxsize: Max cached entries (default: 100).
424
+
425
+ Returns:
426
+ Configured OntologicalAspect instance.
427
+
428
+ Example:
429
+ aspect = create_ontology_aspect(
430
+ strategy=DevilAdvocateStrategy(llm_adapter),
431
+ event_emitter=event_store.emit,
432
+ )
433
+ """
434
+ return OntologicalAspect(
435
+ strategy=strategy,
436
+ event_emitter=event_emitter,
437
+ halt_on_violation=halt_on_violation,
438
+ strict_mode=strict_mode,
439
+ cache_ttl=cache_ttl,
440
+ cache_maxsize=cache_maxsize,
441
+ )
442
+
443
+
444
+ __all__ = [
445
+ # Types
446
+ "OntologicalJoinPoint",
447
+ "AnalysisResult",
448
+ "OntologicalViolationError",
449
+ # Protocol
450
+ "OntologyStrategy",
451
+ # Weaver
452
+ "OntologicalAspect",
453
+ # Factory
454
+ "create_ontology_aspect",
455
+ ]