ouroboros-ai 0.1.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 (81) hide show
  1. ouroboros/__init__.py +15 -0
  2. ouroboros/__main__.py +9 -0
  3. ouroboros/bigbang/__init__.py +39 -0
  4. ouroboros/bigbang/ambiguity.py +464 -0
  5. ouroboros/bigbang/interview.py +530 -0
  6. ouroboros/bigbang/seed_generator.py +610 -0
  7. ouroboros/cli/__init__.py +9 -0
  8. ouroboros/cli/commands/__init__.py +7 -0
  9. ouroboros/cli/commands/config.py +79 -0
  10. ouroboros/cli/commands/init.py +425 -0
  11. ouroboros/cli/commands/run.py +201 -0
  12. ouroboros/cli/commands/status.py +85 -0
  13. ouroboros/cli/formatters/__init__.py +31 -0
  14. ouroboros/cli/formatters/panels.py +157 -0
  15. ouroboros/cli/formatters/progress.py +112 -0
  16. ouroboros/cli/formatters/tables.py +166 -0
  17. ouroboros/cli/main.py +60 -0
  18. ouroboros/config/__init__.py +81 -0
  19. ouroboros/config/loader.py +292 -0
  20. ouroboros/config/models.py +332 -0
  21. ouroboros/core/__init__.py +62 -0
  22. ouroboros/core/ac_tree.py +401 -0
  23. ouroboros/core/context.py +472 -0
  24. ouroboros/core/errors.py +246 -0
  25. ouroboros/core/seed.py +212 -0
  26. ouroboros/core/types.py +205 -0
  27. ouroboros/evaluation/__init__.py +110 -0
  28. ouroboros/evaluation/consensus.py +350 -0
  29. ouroboros/evaluation/mechanical.py +351 -0
  30. ouroboros/evaluation/models.py +235 -0
  31. ouroboros/evaluation/pipeline.py +286 -0
  32. ouroboros/evaluation/semantic.py +302 -0
  33. ouroboros/evaluation/trigger.py +278 -0
  34. ouroboros/events/__init__.py +5 -0
  35. ouroboros/events/base.py +80 -0
  36. ouroboros/events/decomposition.py +153 -0
  37. ouroboros/events/evaluation.py +248 -0
  38. ouroboros/execution/__init__.py +44 -0
  39. ouroboros/execution/atomicity.py +451 -0
  40. ouroboros/execution/decomposition.py +481 -0
  41. ouroboros/execution/double_diamond.py +1386 -0
  42. ouroboros/execution/subagent.py +275 -0
  43. ouroboros/observability/__init__.py +63 -0
  44. ouroboros/observability/drift.py +383 -0
  45. ouroboros/observability/logging.py +504 -0
  46. ouroboros/observability/retrospective.py +338 -0
  47. ouroboros/orchestrator/__init__.py +78 -0
  48. ouroboros/orchestrator/adapter.py +391 -0
  49. ouroboros/orchestrator/events.py +278 -0
  50. ouroboros/orchestrator/runner.py +597 -0
  51. ouroboros/orchestrator/session.py +486 -0
  52. ouroboros/persistence/__init__.py +23 -0
  53. ouroboros/persistence/checkpoint.py +511 -0
  54. ouroboros/persistence/event_store.py +183 -0
  55. ouroboros/persistence/migrations/__init__.py +1 -0
  56. ouroboros/persistence/migrations/runner.py +100 -0
  57. ouroboros/persistence/migrations/scripts/001_initial.sql +20 -0
  58. ouroboros/persistence/schema.py +56 -0
  59. ouroboros/persistence/uow.py +230 -0
  60. ouroboros/providers/__init__.py +28 -0
  61. ouroboros/providers/base.py +133 -0
  62. ouroboros/providers/claude_code_adapter.py +212 -0
  63. ouroboros/providers/litellm_adapter.py +316 -0
  64. ouroboros/py.typed +0 -0
  65. ouroboros/resilience/__init__.py +67 -0
  66. ouroboros/resilience/lateral.py +595 -0
  67. ouroboros/resilience/stagnation.py +727 -0
  68. ouroboros/routing/__init__.py +60 -0
  69. ouroboros/routing/complexity.py +272 -0
  70. ouroboros/routing/downgrade.py +664 -0
  71. ouroboros/routing/escalation.py +340 -0
  72. ouroboros/routing/router.py +204 -0
  73. ouroboros/routing/tiers.py +247 -0
  74. ouroboros/secondary/__init__.py +40 -0
  75. ouroboros/secondary/scheduler.py +467 -0
  76. ouroboros/secondary/todo_registry.py +483 -0
  77. ouroboros_ai-0.1.0.dist-info/METADATA +607 -0
  78. ouroboros_ai-0.1.0.dist-info/RECORD +81 -0
  79. ouroboros_ai-0.1.0.dist-info/WHEEL +4 -0
  80. ouroboros_ai-0.1.0.dist-info/entry_points.txt +2 -0
  81. ouroboros_ai-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,486 @@
1
+ """Session tracking for orchestrator execution.
2
+
3
+ This module provides session management through event sourcing:
4
+ - SessionTracker: Immutable session state (frozen dataclass)
5
+ - SessionRepository: Event-based persistence and reconstruction
6
+
7
+ Sessions are tracked entirely through events in the EventStore,
8
+ following the principle that events are the single source of truth.
9
+
10
+ Usage:
11
+ repo = SessionRepository(event_store)
12
+
13
+ # Create and track session
14
+ tracker = SessionTracker.create(execution_id, seed_id)
15
+ await repo.track_progress(tracker.session_id, {"step": 1})
16
+
17
+ # Reconstruct session from events
18
+ result = await repo.reconstruct_session(session_id)
19
+ if result.is_ok:
20
+ tracker = result.value
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ from dataclasses import dataclass, field, replace
26
+ from datetime import UTC, datetime
27
+ from enum import StrEnum
28
+ from typing import TYPE_CHECKING, Any
29
+ from uuid import uuid4
30
+
31
+ from ouroboros.core.errors import PersistenceError
32
+ from ouroboros.core.types import Result
33
+ from ouroboros.events.base import BaseEvent
34
+ from ouroboros.observability.logging import get_logger
35
+
36
+ if TYPE_CHECKING:
37
+ from ouroboros.persistence.event_store import EventStore
38
+
39
+ log = get_logger(__name__)
40
+
41
+
42
+ # =============================================================================
43
+ # Session Status
44
+ # =============================================================================
45
+
46
+
47
+ class SessionStatus(StrEnum):
48
+ """Status of an orchestrator session."""
49
+
50
+ RUNNING = "running"
51
+ PAUSED = "paused"
52
+ COMPLETED = "completed"
53
+ FAILED = "failed"
54
+
55
+
56
+ # =============================================================================
57
+ # Session Tracker (Immutable)
58
+ # =============================================================================
59
+
60
+
61
+ @dataclass(frozen=True, slots=True)
62
+ class SessionTracker:
63
+ """Immutable session state for orchestrator execution.
64
+
65
+ This dataclass tracks the current state of an orchestrator session.
66
+ Updates create new instances via with_* methods (immutable pattern).
67
+
68
+ Attributes:
69
+ session_id: Unique identifier for this session.
70
+ execution_id: Associated workflow execution ID.
71
+ seed_id: ID of the seed being executed.
72
+ status: Current session status.
73
+ start_time: When the session started.
74
+ progress: Progress data (message count, current step, etc.).
75
+ messages_processed: Number of messages processed so far.
76
+ last_message_time: Timestamp of last processed message.
77
+ """
78
+
79
+ session_id: str
80
+ execution_id: str
81
+ seed_id: str
82
+ status: SessionStatus
83
+ start_time: datetime
84
+ progress: dict[str, Any] = field(default_factory=dict)
85
+ messages_processed: int = 0
86
+ last_message_time: datetime | None = None
87
+
88
+ @classmethod
89
+ def create(
90
+ cls,
91
+ execution_id: str,
92
+ seed_id: str,
93
+ session_id: str | None = None,
94
+ ) -> SessionTracker:
95
+ """Create a new session tracker.
96
+
97
+ Args:
98
+ execution_id: Workflow execution ID.
99
+ seed_id: Seed ID being executed.
100
+ session_id: Optional custom session ID.
101
+
102
+ Returns:
103
+ New SessionTracker instance.
104
+ """
105
+ return cls(
106
+ session_id=session_id or f"orch_{uuid4().hex[:12]}",
107
+ execution_id=execution_id,
108
+ seed_id=seed_id,
109
+ status=SessionStatus.RUNNING,
110
+ start_time=datetime.now(UTC),
111
+ )
112
+
113
+ def with_progress(self, update: dict[str, Any]) -> SessionTracker:
114
+ """Return new tracker with updated progress.
115
+
116
+ Args:
117
+ update: Progress data to merge.
118
+
119
+ Returns:
120
+ New SessionTracker with merged progress.
121
+ """
122
+ merged_progress = {**self.progress, **update}
123
+ return replace(
124
+ self,
125
+ progress=merged_progress,
126
+ messages_processed=self.messages_processed + 1,
127
+ last_message_time=datetime.now(UTC),
128
+ )
129
+
130
+ def with_status(self, status: SessionStatus) -> SessionTracker:
131
+ """Return new tracker with updated status.
132
+
133
+ Args:
134
+ status: New session status.
135
+
136
+ Returns:
137
+ New SessionTracker with updated status.
138
+ """
139
+ return replace(self, status=status)
140
+
141
+ @property
142
+ def is_active(self) -> bool:
143
+ """Return True if session is still active (running or paused)."""
144
+ return self.status in (SessionStatus.RUNNING, SessionStatus.PAUSED)
145
+
146
+ @property
147
+ def is_completed(self) -> bool:
148
+ """Return True if session completed successfully."""
149
+ return self.status == SessionStatus.COMPLETED
150
+
151
+ @property
152
+ def is_failed(self) -> bool:
153
+ """Return True if session failed."""
154
+ return self.status == SessionStatus.FAILED
155
+
156
+ def to_dict(self) -> dict[str, Any]:
157
+ """Convert to dictionary for serialization.
158
+
159
+ Returns:
160
+ Dictionary representation.
161
+ """
162
+ return {
163
+ "session_id": self.session_id,
164
+ "execution_id": self.execution_id,
165
+ "seed_id": self.seed_id,
166
+ "status": self.status.value,
167
+ "start_time": self.start_time.isoformat(),
168
+ "progress": self.progress,
169
+ "messages_processed": self.messages_processed,
170
+ "last_message_time": self.last_message_time.isoformat()
171
+ if self.last_message_time
172
+ else None,
173
+ }
174
+
175
+
176
+ # =============================================================================
177
+ # Session Repository (Event-based)
178
+ # =============================================================================
179
+
180
+
181
+ class SessionRepository:
182
+ """Manages sessions via event store.
183
+
184
+ Sessions are persisted entirely through events, following the
185
+ event sourcing pattern. This avoids dual-write problems and
186
+ keeps events as the single source of truth.
187
+
188
+ Event Types:
189
+ - orchestrator.session.started: Session created
190
+ - orchestrator.progress.updated: Progress update
191
+ - orchestrator.session.completed: Session finished successfully
192
+ - orchestrator.session.failed: Session failed
193
+ - orchestrator.session.paused: Session paused for resumption
194
+ """
195
+
196
+ def __init__(self, event_store: EventStore) -> None:
197
+ """Initialize repository with event store.
198
+
199
+ Args:
200
+ event_store: Event store for persistence.
201
+ """
202
+ self._event_store = event_store
203
+
204
+ async def create_session(
205
+ self,
206
+ execution_id: str,
207
+ seed_id: str,
208
+ session_id: str | None = None,
209
+ ) -> Result[SessionTracker, PersistenceError]:
210
+ """Create a new session and persist start event.
211
+
212
+ Args:
213
+ execution_id: Workflow execution ID.
214
+ seed_id: Seed ID being executed.
215
+ session_id: Optional custom session ID.
216
+
217
+ Returns:
218
+ Result containing new SessionTracker.
219
+ """
220
+ tracker = SessionTracker.create(execution_id, seed_id, session_id)
221
+
222
+ event = BaseEvent(
223
+ type="orchestrator.session.started",
224
+ aggregate_type="session",
225
+ aggregate_id=tracker.session_id,
226
+ data={
227
+ "execution_id": execution_id,
228
+ "seed_id": seed_id,
229
+ "start_time": tracker.start_time.isoformat(),
230
+ },
231
+ )
232
+
233
+ try:
234
+ await self._event_store.append(event)
235
+ log.info(
236
+ "orchestrator.session.created",
237
+ session_id=tracker.session_id,
238
+ execution_id=execution_id,
239
+ )
240
+ return Result.ok(tracker)
241
+ except Exception as e:
242
+ log.exception(
243
+ "orchestrator.session.create_failed",
244
+ session_id=tracker.session_id,
245
+ error=str(e),
246
+ )
247
+ return Result.err(
248
+ PersistenceError(
249
+ message=f"Failed to create session: {e}",
250
+ details={"session_id": tracker.session_id},
251
+ )
252
+ )
253
+
254
+ async def track_progress(
255
+ self,
256
+ session_id: str,
257
+ progress: dict[str, Any],
258
+ ) -> Result[None, PersistenceError]:
259
+ """Emit progress event for session.
260
+
261
+ Args:
262
+ session_id: Session to update.
263
+ progress: Progress data to record.
264
+
265
+ Returns:
266
+ Result indicating success or failure.
267
+ """
268
+ event = BaseEvent(
269
+ type="orchestrator.progress.updated",
270
+ aggregate_type="session",
271
+ aggregate_id=session_id,
272
+ data={
273
+ "progress": progress,
274
+ "timestamp": datetime.now(UTC).isoformat(),
275
+ },
276
+ )
277
+
278
+ try:
279
+ await self._event_store.append(event)
280
+ return Result.ok(None)
281
+ except Exception as e:
282
+ log.warning(
283
+ "orchestrator.progress.track_failed",
284
+ session_id=session_id,
285
+ error=str(e),
286
+ )
287
+ return Result.err(
288
+ PersistenceError(
289
+ message=f"Failed to track progress: {e}",
290
+ details={"session_id": session_id},
291
+ )
292
+ )
293
+
294
+ async def mark_completed(
295
+ self,
296
+ session_id: str,
297
+ summary: dict[str, Any] | None = None,
298
+ ) -> Result[None, PersistenceError]:
299
+ """Mark session as completed.
300
+
301
+ Args:
302
+ session_id: Session to complete.
303
+ summary: Optional completion summary.
304
+
305
+ Returns:
306
+ Result indicating success or failure.
307
+ """
308
+ event = BaseEvent(
309
+ type="orchestrator.session.completed",
310
+ aggregate_type="session",
311
+ aggregate_id=session_id,
312
+ data={
313
+ "summary": summary or {},
314
+ "completed_at": datetime.now(UTC).isoformat(),
315
+ },
316
+ )
317
+
318
+ try:
319
+ await self._event_store.append(event)
320
+ log.info(
321
+ "orchestrator.session.completed",
322
+ session_id=session_id,
323
+ )
324
+ return Result.ok(None)
325
+ except Exception as e:
326
+ log.exception(
327
+ "orchestrator.session.complete_failed",
328
+ session_id=session_id,
329
+ error=str(e),
330
+ )
331
+ return Result.err(
332
+ PersistenceError(
333
+ message=f"Failed to mark session completed: {e}",
334
+ details={"session_id": session_id},
335
+ )
336
+ )
337
+
338
+ async def mark_failed(
339
+ self,
340
+ session_id: str,
341
+ error_message: str,
342
+ error_details: dict[str, Any] | None = None,
343
+ ) -> Result[None, PersistenceError]:
344
+ """Mark session as failed.
345
+
346
+ Args:
347
+ session_id: Session that failed.
348
+ error_message: Error description.
349
+ error_details: Optional error details.
350
+
351
+ Returns:
352
+ Result indicating success or failure.
353
+ """
354
+ event = BaseEvent(
355
+ type="orchestrator.session.failed",
356
+ aggregate_type="session",
357
+ aggregate_id=session_id,
358
+ data={
359
+ "error": error_message,
360
+ "error_details": error_details or {},
361
+ "failed_at": datetime.now(UTC).isoformat(),
362
+ },
363
+ )
364
+
365
+ try:
366
+ await self._event_store.append(event)
367
+ log.error(
368
+ "orchestrator.session.failed",
369
+ session_id=session_id,
370
+ error=error_message,
371
+ )
372
+ return Result.ok(None)
373
+ except Exception as e:
374
+ log.exception(
375
+ "orchestrator.session.fail_failed",
376
+ session_id=session_id,
377
+ error=str(e),
378
+ )
379
+ return Result.err(
380
+ PersistenceError(
381
+ message=f"Failed to mark session failed: {e}",
382
+ details={"session_id": session_id},
383
+ )
384
+ )
385
+
386
+ async def reconstruct_session(
387
+ self,
388
+ session_id: str,
389
+ ) -> Result[SessionTracker, PersistenceError]:
390
+ """Reconstruct session state from events.
391
+
392
+ Replays all events for the session to rebuild the current state.
393
+ This is used for session resumption.
394
+
395
+ Args:
396
+ session_id: Session to reconstruct.
397
+
398
+ Returns:
399
+ Result containing reconstructed SessionTracker.
400
+ """
401
+ try:
402
+ events = await self._event_store.replay("session", session_id)
403
+
404
+ if not events:
405
+ return Result.err(
406
+ PersistenceError(
407
+ message=f"No events found for session: {session_id}",
408
+ details={"session_id": session_id},
409
+ )
410
+ )
411
+
412
+ # Find the start event to get initial state
413
+ start_event = next(
414
+ (e for e in events if e.type == "orchestrator.session.started"),
415
+ None,
416
+ )
417
+
418
+ if not start_event:
419
+ return Result.err(
420
+ PersistenceError(
421
+ message=f"No start event found for session: {session_id}",
422
+ details={"session_id": session_id},
423
+ )
424
+ )
425
+
426
+ # Create initial tracker from start event
427
+ tracker = SessionTracker(
428
+ session_id=session_id,
429
+ execution_id=start_event.data.get("execution_id", ""),
430
+ seed_id=start_event.data.get("seed_id", ""),
431
+ status=SessionStatus.RUNNING,
432
+ start_time=datetime.fromisoformat(
433
+ start_event.data.get("start_time", datetime.now(UTC).isoformat())
434
+ ),
435
+ )
436
+
437
+ # Replay subsequent events
438
+ messages_processed = 0
439
+ last_progress: dict[str, Any] = {}
440
+
441
+ for event in events:
442
+ if event.type == "orchestrator.progress.updated":
443
+ messages_processed += 1
444
+ last_progress = event.data.get("progress", {})
445
+ elif event.type == "orchestrator.session.completed":
446
+ tracker = tracker.with_status(SessionStatus.COMPLETED)
447
+ elif event.type == "orchestrator.session.failed":
448
+ tracker = tracker.with_status(SessionStatus.FAILED)
449
+ elif event.type == "orchestrator.session.paused":
450
+ tracker = tracker.with_status(SessionStatus.PAUSED)
451
+
452
+ # Apply accumulated progress
453
+ tracker = replace(
454
+ tracker,
455
+ progress=last_progress,
456
+ messages_processed=messages_processed,
457
+ )
458
+
459
+ log.info(
460
+ "orchestrator.session.reconstructed",
461
+ session_id=session_id,
462
+ status=tracker.status.value,
463
+ messages_processed=messages_processed,
464
+ )
465
+
466
+ return Result.ok(tracker)
467
+
468
+ except Exception as e:
469
+ log.exception(
470
+ "orchestrator.session.reconstruct_failed",
471
+ session_id=session_id,
472
+ error=str(e),
473
+ )
474
+ return Result.err(
475
+ PersistenceError(
476
+ message=f"Failed to reconstruct session: {e}",
477
+ details={"session_id": session_id},
478
+ )
479
+ )
480
+
481
+
482
+ __all__ = [
483
+ "SessionRepository",
484
+ "SessionStatus",
485
+ "SessionTracker",
486
+ ]
@@ -0,0 +1,23 @@
1
+ """Ouroboros persistence module - event sourcing infrastructure."""
2
+
3
+ from ouroboros.persistence.checkpoint import (
4
+ CheckpointData,
5
+ CheckpointStore,
6
+ PeriodicCheckpointer,
7
+ RecoveryManager,
8
+ )
9
+ from ouroboros.persistence.event_store import EventStore
10
+ from ouroboros.persistence.schema import events_table, metadata
11
+ from ouroboros.persistence.uow import PhaseTransaction, UnitOfWork
12
+
13
+ __all__ = [
14
+ "CheckpointData",
15
+ "CheckpointStore",
16
+ "EventStore",
17
+ "PeriodicCheckpointer",
18
+ "PhaseTransaction",
19
+ "RecoveryManager",
20
+ "UnitOfWork",
21
+ "events_table",
22
+ "metadata",
23
+ ]