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.
- ouroboros/__init__.py +15 -0
- ouroboros/__main__.py +9 -0
- ouroboros/bigbang/__init__.py +39 -0
- ouroboros/bigbang/ambiguity.py +464 -0
- ouroboros/bigbang/interview.py +530 -0
- ouroboros/bigbang/seed_generator.py +610 -0
- ouroboros/cli/__init__.py +9 -0
- ouroboros/cli/commands/__init__.py +7 -0
- ouroboros/cli/commands/config.py +79 -0
- ouroboros/cli/commands/init.py +425 -0
- ouroboros/cli/commands/run.py +201 -0
- ouroboros/cli/commands/status.py +85 -0
- ouroboros/cli/formatters/__init__.py +31 -0
- ouroboros/cli/formatters/panels.py +157 -0
- ouroboros/cli/formatters/progress.py +112 -0
- ouroboros/cli/formatters/tables.py +166 -0
- ouroboros/cli/main.py +60 -0
- ouroboros/config/__init__.py +81 -0
- ouroboros/config/loader.py +292 -0
- ouroboros/config/models.py +332 -0
- ouroboros/core/__init__.py +62 -0
- ouroboros/core/ac_tree.py +401 -0
- ouroboros/core/context.py +472 -0
- ouroboros/core/errors.py +246 -0
- ouroboros/core/seed.py +212 -0
- ouroboros/core/types.py +205 -0
- ouroboros/evaluation/__init__.py +110 -0
- ouroboros/evaluation/consensus.py +350 -0
- ouroboros/evaluation/mechanical.py +351 -0
- ouroboros/evaluation/models.py +235 -0
- ouroboros/evaluation/pipeline.py +286 -0
- ouroboros/evaluation/semantic.py +302 -0
- ouroboros/evaluation/trigger.py +278 -0
- ouroboros/events/__init__.py +5 -0
- ouroboros/events/base.py +80 -0
- ouroboros/events/decomposition.py +153 -0
- ouroboros/events/evaluation.py +248 -0
- ouroboros/execution/__init__.py +44 -0
- ouroboros/execution/atomicity.py +451 -0
- ouroboros/execution/decomposition.py +481 -0
- ouroboros/execution/double_diamond.py +1386 -0
- ouroboros/execution/subagent.py +275 -0
- ouroboros/observability/__init__.py +63 -0
- ouroboros/observability/drift.py +383 -0
- ouroboros/observability/logging.py +504 -0
- ouroboros/observability/retrospective.py +338 -0
- ouroboros/orchestrator/__init__.py +78 -0
- ouroboros/orchestrator/adapter.py +391 -0
- ouroboros/orchestrator/events.py +278 -0
- ouroboros/orchestrator/runner.py +597 -0
- ouroboros/orchestrator/session.py +486 -0
- ouroboros/persistence/__init__.py +23 -0
- ouroboros/persistence/checkpoint.py +511 -0
- ouroboros/persistence/event_store.py +183 -0
- ouroboros/persistence/migrations/__init__.py +1 -0
- ouroboros/persistence/migrations/runner.py +100 -0
- ouroboros/persistence/migrations/scripts/001_initial.sql +20 -0
- ouroboros/persistence/schema.py +56 -0
- ouroboros/persistence/uow.py +230 -0
- ouroboros/providers/__init__.py +28 -0
- ouroboros/providers/base.py +133 -0
- ouroboros/providers/claude_code_adapter.py +212 -0
- ouroboros/providers/litellm_adapter.py +316 -0
- ouroboros/py.typed +0 -0
- ouroboros/resilience/__init__.py +67 -0
- ouroboros/resilience/lateral.py +595 -0
- ouroboros/resilience/stagnation.py +727 -0
- ouroboros/routing/__init__.py +60 -0
- ouroboros/routing/complexity.py +272 -0
- ouroboros/routing/downgrade.py +664 -0
- ouroboros/routing/escalation.py +340 -0
- ouroboros/routing/router.py +204 -0
- ouroboros/routing/tiers.py +247 -0
- ouroboros/secondary/__init__.py +40 -0
- ouroboros/secondary/scheduler.py +467 -0
- ouroboros/secondary/todo_registry.py +483 -0
- ouroboros_ai-0.1.0.dist-info/METADATA +607 -0
- ouroboros_ai-0.1.0.dist-info/RECORD +81 -0
- ouroboros_ai-0.1.0.dist-info/WHEEL +4 -0
- ouroboros_ai-0.1.0.dist-info/entry_points.txt +2 -0
- ouroboros_ai-0.1.0.dist-info/licenses/LICENSE +21 -0
ouroboros/core/errors.py
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"""Error hierarchy for Ouroboros.
|
|
2
|
+
|
|
3
|
+
This module defines the exception hierarchy for Ouroboros. These exceptions
|
|
4
|
+
are used for unexpected errors (programming bugs) and as error types in
|
|
5
|
+
Result for expected failures.
|
|
6
|
+
|
|
7
|
+
Exception Hierarchy:
|
|
8
|
+
OuroborosError (base)
|
|
9
|
+
├── ProviderError - LLM provider failures (rate limits, API errors)
|
|
10
|
+
├── ConfigError - Configuration and credentials issues
|
|
11
|
+
├── PersistenceError - Database and storage issues
|
|
12
|
+
└── ValidationError - Schema and data validation failures
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class OuroborosError(Exception):
|
|
19
|
+
"""Base exception for all Ouroboros errors.
|
|
20
|
+
|
|
21
|
+
All Ouroboros-specific exceptions inherit from this class.
|
|
22
|
+
This allows catching all Ouroboros errors with a single except clause.
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
message: Human-readable error description.
|
|
26
|
+
details: Optional dict with additional context.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, message: str, details: dict[str, Any] | None = None) -> None:
|
|
30
|
+
"""Initialize the error.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
message: Human-readable error description.
|
|
34
|
+
details: Optional dict with additional context about the error.
|
|
35
|
+
"""
|
|
36
|
+
super().__init__(message)
|
|
37
|
+
self.message = message
|
|
38
|
+
self.details = details or {}
|
|
39
|
+
|
|
40
|
+
def __str__(self) -> str:
|
|
41
|
+
"""Return string representation of the error."""
|
|
42
|
+
if self.details:
|
|
43
|
+
return f"{self.message} (details: {self.details})"
|
|
44
|
+
return self.message
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ProviderError(OuroborosError):
|
|
48
|
+
"""Error from LLM provider operations.
|
|
49
|
+
|
|
50
|
+
Raised when LLM provider calls fail (rate limits, API errors, timeouts).
|
|
51
|
+
Can be converted from provider-specific exceptions.
|
|
52
|
+
|
|
53
|
+
Attributes:
|
|
54
|
+
provider: Name of the provider (e.g., "openai", "anthropic").
|
|
55
|
+
status_code: HTTP status code if applicable.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
message: str,
|
|
61
|
+
*,
|
|
62
|
+
provider: str | None = None,
|
|
63
|
+
status_code: int | None = None,
|
|
64
|
+
details: dict[str, Any] | None = None,
|
|
65
|
+
) -> None:
|
|
66
|
+
"""Initialize provider error.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
message: Human-readable error description.
|
|
70
|
+
provider: Name of the LLM provider.
|
|
71
|
+
status_code: HTTP status code if applicable.
|
|
72
|
+
details: Optional dict with additional context.
|
|
73
|
+
"""
|
|
74
|
+
super().__init__(message, details)
|
|
75
|
+
self.provider = provider
|
|
76
|
+
self.status_code = status_code
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def from_exception(
|
|
80
|
+
cls, exc: Exception, *, provider: str | None = None
|
|
81
|
+
) -> ProviderError:
|
|
82
|
+
"""Create ProviderError from a provider exception.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
exc: The original exception from the provider.
|
|
86
|
+
provider: Name of the LLM provider.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
A ProviderError wrapping the original exception with __cause__ set
|
|
90
|
+
for proper traceback preservation.
|
|
91
|
+
"""
|
|
92
|
+
status_code = getattr(exc, "status_code", None)
|
|
93
|
+
error = cls(
|
|
94
|
+
str(exc),
|
|
95
|
+
provider=provider,
|
|
96
|
+
status_code=status_code,
|
|
97
|
+
details={"original_exception": type(exc).__name__},
|
|
98
|
+
)
|
|
99
|
+
error.__cause__ = exc # Preserve original traceback
|
|
100
|
+
return error
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class ConfigError(OuroborosError):
|
|
104
|
+
"""Error from configuration operations.
|
|
105
|
+
|
|
106
|
+
Raised when configuration loading, parsing, or validation fails.
|
|
107
|
+
|
|
108
|
+
Attributes:
|
|
109
|
+
config_key: The configuration key that caused the error.
|
|
110
|
+
config_file: Path to the config file if applicable.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def __init__(
|
|
114
|
+
self,
|
|
115
|
+
message: str,
|
|
116
|
+
*,
|
|
117
|
+
config_key: str | None = None,
|
|
118
|
+
config_file: str | None = None,
|
|
119
|
+
details: dict[str, Any] | None = None,
|
|
120
|
+
) -> None:
|
|
121
|
+
"""Initialize config error.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
message: Human-readable error description.
|
|
125
|
+
config_key: The configuration key that caused the error.
|
|
126
|
+
config_file: Path to the config file if applicable.
|
|
127
|
+
details: Optional dict with additional context.
|
|
128
|
+
"""
|
|
129
|
+
super().__init__(message, details)
|
|
130
|
+
self.config_key = config_key
|
|
131
|
+
self.config_file = config_file
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class PersistenceError(OuroborosError):
|
|
135
|
+
"""Error from database and storage operations.
|
|
136
|
+
|
|
137
|
+
Raised when database queries, event storage, or checkpoint operations fail.
|
|
138
|
+
|
|
139
|
+
Attributes:
|
|
140
|
+
operation: The operation that failed (e.g., "insert", "query").
|
|
141
|
+
table: The database table involved if applicable.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
def __init__(
|
|
145
|
+
self,
|
|
146
|
+
message: str,
|
|
147
|
+
*,
|
|
148
|
+
operation: str | None = None,
|
|
149
|
+
table: str | None = None,
|
|
150
|
+
details: dict[str, Any] | None = None,
|
|
151
|
+
) -> None:
|
|
152
|
+
"""Initialize persistence error.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
message: Human-readable error description.
|
|
156
|
+
operation: The operation that failed.
|
|
157
|
+
table: The database table involved.
|
|
158
|
+
details: Optional dict with additional context.
|
|
159
|
+
"""
|
|
160
|
+
super().__init__(message, details)
|
|
161
|
+
self.operation = operation
|
|
162
|
+
self.table = table
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class ValidationError(OuroborosError):
|
|
166
|
+
"""Error from data validation operations.
|
|
167
|
+
|
|
168
|
+
Raised when input data fails schema validation or business rule checks.
|
|
169
|
+
|
|
170
|
+
Attributes:
|
|
171
|
+
field: The field that failed validation.
|
|
172
|
+
value: The invalid value if safe to include.
|
|
173
|
+
|
|
174
|
+
Security Note:
|
|
175
|
+
Use safe_value property instead of value when logging to avoid
|
|
176
|
+
exposing sensitive data like API keys or credentials.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
# Fields that should never have their values exposed
|
|
180
|
+
_SENSITIVE_FIELDS = frozenset({
|
|
181
|
+
"password", "api_key", "secret", "token", "credential",
|
|
182
|
+
"auth", "key", "private", "apikey", "api-key",
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
def __init__(
|
|
186
|
+
self,
|
|
187
|
+
message: str,
|
|
188
|
+
*,
|
|
189
|
+
field: str | None = None,
|
|
190
|
+
value: Any | None = None,
|
|
191
|
+
details: dict[str, Any] | None = None,
|
|
192
|
+
) -> None:
|
|
193
|
+
"""Initialize validation error.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
message: Human-readable error description.
|
|
197
|
+
field: The field that failed validation.
|
|
198
|
+
value: The invalid value (only include if safe).
|
|
199
|
+
details: Optional dict with additional context.
|
|
200
|
+
"""
|
|
201
|
+
super().__init__(message, details)
|
|
202
|
+
self.field = field
|
|
203
|
+
self.value = value
|
|
204
|
+
|
|
205
|
+
@property
|
|
206
|
+
def safe_value(self) -> str:
|
|
207
|
+
"""Return a safe representation of the value for logging.
|
|
208
|
+
|
|
209
|
+
Masks potentially sensitive values based on field name or value type.
|
|
210
|
+
Always use this instead of value when logging or displaying errors.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
A safe string representation: masked for sensitive fields,
|
|
214
|
+
truncated for long values, or type info for complex objects.
|
|
215
|
+
"""
|
|
216
|
+
if self.value is None:
|
|
217
|
+
return "<None>"
|
|
218
|
+
|
|
219
|
+
# Check if field name suggests sensitive data
|
|
220
|
+
if self.field:
|
|
221
|
+
field_lower = self.field.lower()
|
|
222
|
+
if any(sensitive in field_lower for sensitive in self._SENSITIVE_FIELDS):
|
|
223
|
+
return "<REDACTED>"
|
|
224
|
+
|
|
225
|
+
# Check if value looks like a secret (starts with common prefixes)
|
|
226
|
+
if isinstance(self.value, str):
|
|
227
|
+
value_str = self.value
|
|
228
|
+
secret_prefixes = ("sk-", "pk-", "api-", "bearer ", "token ", "secret_")
|
|
229
|
+
if any(value_str.lower().startswith(p) for p in secret_prefixes):
|
|
230
|
+
return "<REDACTED>"
|
|
231
|
+
# Truncate long strings
|
|
232
|
+
if len(value_str) > 50:
|
|
233
|
+
return f"{value_str[:20]}...({len(value_str)} chars)"
|
|
234
|
+
return repr(value_str)
|
|
235
|
+
|
|
236
|
+
# For other types, show type info
|
|
237
|
+
return f"<{type(self.value).__name__}>"
|
|
238
|
+
|
|
239
|
+
def __str__(self) -> str:
|
|
240
|
+
"""Return string representation using safe_value."""
|
|
241
|
+
base = self.message
|
|
242
|
+
if self.field:
|
|
243
|
+
base = f"{base} (field: {self.field}, value: {self.safe_value})"
|
|
244
|
+
if self.details:
|
|
245
|
+
base = f"{base} (details: {self.details})"
|
|
246
|
+
return base
|
ouroboros/core/seed.py
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""Immutable Seed schema for workflow execution.
|
|
2
|
+
|
|
3
|
+
The Seed is the "constitution" of a workflow - an immutable specification
|
|
4
|
+
generated from the Big Bang interview phase when ambiguity score <= 0.2.
|
|
5
|
+
|
|
6
|
+
Key properties:
|
|
7
|
+
- Seed.direction (goal, constraints, acceptance_criteria) is IMMUTABLE
|
|
8
|
+
- Effective ontology can evolve with consensus during iterations
|
|
9
|
+
- Contains all information needed to execute and evaluate the workflow
|
|
10
|
+
|
|
11
|
+
This module defines:
|
|
12
|
+
- Seed: The immutable Pydantic model with frozen=True
|
|
13
|
+
- SeedMetadata: Version and creation metadata
|
|
14
|
+
- Supporting types for seed components
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from datetime import UTC, datetime
|
|
18
|
+
from typing import Any
|
|
19
|
+
from uuid import uuid4
|
|
20
|
+
|
|
21
|
+
from pydantic import BaseModel, Field
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ExitCondition(BaseModel, frozen=True):
|
|
25
|
+
"""Defines when the workflow should terminate.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
name: Short identifier for the condition.
|
|
29
|
+
description: Detailed explanation of the exit condition.
|
|
30
|
+
evaluation_criteria: How to determine if condition is met.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
name: str = Field(..., min_length=1)
|
|
34
|
+
description: str = Field(..., min_length=1)
|
|
35
|
+
evaluation_criteria: str = Field(..., min_length=1)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class EvaluationPrinciple(BaseModel, frozen=True):
|
|
39
|
+
"""A principle for evaluating workflow outputs.
|
|
40
|
+
|
|
41
|
+
Attributes:
|
|
42
|
+
name: Short identifier for the principle.
|
|
43
|
+
description: What this principle evaluates.
|
|
44
|
+
weight: Relative importance (0.0 to 1.0).
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
name: str = Field(..., min_length=1)
|
|
48
|
+
description: str = Field(..., min_length=1)
|
|
49
|
+
weight: float = Field(default=1.0, ge=0.0, le=1.0)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class OntologyField(BaseModel, frozen=True):
|
|
53
|
+
"""A field in the ontology schema.
|
|
54
|
+
|
|
55
|
+
Attributes:
|
|
56
|
+
name: Field identifier.
|
|
57
|
+
field_type: Type of the field (string, number, boolean, array, object).
|
|
58
|
+
description: Purpose of this field.
|
|
59
|
+
required: Whether this field is required.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
name: str = Field(..., min_length=1)
|
|
63
|
+
field_type: str = Field(..., min_length=1)
|
|
64
|
+
description: str = Field(..., min_length=1)
|
|
65
|
+
required: bool = Field(default=True)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class OntologySchema(BaseModel, frozen=True):
|
|
69
|
+
"""Schema defining the structure of workflow outputs.
|
|
70
|
+
|
|
71
|
+
The ontology schema defines what data structure the workflow should
|
|
72
|
+
produce and maintain throughout iterations.
|
|
73
|
+
|
|
74
|
+
Attributes:
|
|
75
|
+
name: Name of the ontology.
|
|
76
|
+
description: Purpose and scope of this ontology.
|
|
77
|
+
fields: List of fields in the ontology.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
name: str = Field(..., min_length=1)
|
|
81
|
+
description: str = Field(..., min_length=1)
|
|
82
|
+
fields: tuple[OntologyField, ...] = Field(default_factory=tuple)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class SeedMetadata(BaseModel, frozen=True):
|
|
86
|
+
"""Metadata about the Seed generation.
|
|
87
|
+
|
|
88
|
+
Attributes:
|
|
89
|
+
seed_id: Unique identifier for this seed.
|
|
90
|
+
version: Schema version for forward compatibility.
|
|
91
|
+
created_at: When this seed was generated.
|
|
92
|
+
ambiguity_score: The ambiguity score at generation time.
|
|
93
|
+
interview_id: Reference to the source interview.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
seed_id: str = Field(default_factory=lambda: f"seed_{uuid4().hex[:12]}")
|
|
97
|
+
version: str = Field(default="1.0.0")
|
|
98
|
+
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
|
99
|
+
ambiguity_score: float = Field(..., ge=0.0, le=1.0)
|
|
100
|
+
interview_id: str | None = Field(default=None)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class Seed(BaseModel, frozen=True):
|
|
104
|
+
"""Immutable specification for workflow execution.
|
|
105
|
+
|
|
106
|
+
The Seed is the "constitution" of the workflow - once generated, it cannot
|
|
107
|
+
be modified. This ensures consistency throughout the workflow lifecycle.
|
|
108
|
+
|
|
109
|
+
Direction (goal, constraints, acceptance_criteria) is IMMUTABLE:
|
|
110
|
+
- These define WHAT the workflow should achieve
|
|
111
|
+
- Cannot be changed after generation
|
|
112
|
+
- Serves as the ground truth for evaluation
|
|
113
|
+
|
|
114
|
+
Attributes:
|
|
115
|
+
goal: The primary objective of the workflow.
|
|
116
|
+
constraints: Hard constraints that must be satisfied.
|
|
117
|
+
acceptance_criteria: Specific criteria for success.
|
|
118
|
+
ontology_schema: Schema for workflow output structure.
|
|
119
|
+
evaluation_principles: Principles for evaluating outputs.
|
|
120
|
+
exit_conditions: Conditions for terminating the workflow.
|
|
121
|
+
metadata: Generation metadata (version, timestamp, etc.).
|
|
122
|
+
|
|
123
|
+
Example:
|
|
124
|
+
seed = Seed(
|
|
125
|
+
goal="Build a CLI task management tool",
|
|
126
|
+
constraints=("Python 3.14+", "No external database"),
|
|
127
|
+
acceptance_criteria=("Tasks can be created", "Tasks can be listed"),
|
|
128
|
+
ontology_schema=OntologySchema(
|
|
129
|
+
name="TaskManager",
|
|
130
|
+
description="Task management ontology",
|
|
131
|
+
fields=(
|
|
132
|
+
OntologyField(
|
|
133
|
+
name="tasks",
|
|
134
|
+
field_type="array",
|
|
135
|
+
description="List of tasks",
|
|
136
|
+
),
|
|
137
|
+
),
|
|
138
|
+
),
|
|
139
|
+
evaluation_principles=(
|
|
140
|
+
EvaluationPrinciple(
|
|
141
|
+
name="completeness",
|
|
142
|
+
description="All requirements are met",
|
|
143
|
+
),
|
|
144
|
+
),
|
|
145
|
+
exit_conditions=(
|
|
146
|
+
ExitCondition(
|
|
147
|
+
name="all_criteria_met",
|
|
148
|
+
description="All acceptance criteria satisfied",
|
|
149
|
+
evaluation_criteria="100% criteria pass",
|
|
150
|
+
),
|
|
151
|
+
),
|
|
152
|
+
metadata=SeedMetadata(ambiguity_score=0.15),
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# Attempting to modify raises an error:
|
|
156
|
+
seed.goal = "New goal" # Raises ValidationError (frozen)
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
# Direction - IMMUTABLE
|
|
160
|
+
goal: str = Field(..., min_length=1, description="Primary objective of the workflow")
|
|
161
|
+
constraints: tuple[str, ...] = Field(
|
|
162
|
+
default_factory=tuple,
|
|
163
|
+
description="Hard constraints that must be satisfied",
|
|
164
|
+
)
|
|
165
|
+
acceptance_criteria: tuple[str, ...] = Field(
|
|
166
|
+
default_factory=tuple,
|
|
167
|
+
description="Specific criteria for success evaluation",
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# Structure
|
|
171
|
+
ontology_schema: OntologySchema = Field(
|
|
172
|
+
...,
|
|
173
|
+
description="Schema defining the structure of workflow outputs",
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Evaluation
|
|
177
|
+
evaluation_principles: tuple[EvaluationPrinciple, ...] = Field(
|
|
178
|
+
default_factory=tuple,
|
|
179
|
+
description="Principles for evaluating workflow outputs",
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Termination
|
|
183
|
+
exit_conditions: tuple[ExitCondition, ...] = Field(
|
|
184
|
+
default_factory=tuple,
|
|
185
|
+
description="Conditions for terminating the workflow",
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Metadata
|
|
189
|
+
metadata: SeedMetadata = Field(
|
|
190
|
+
...,
|
|
191
|
+
description="Generation metadata (version, timestamp, etc.)",
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def to_dict(self) -> dict[str, Any]:
|
|
195
|
+
"""Convert seed to dictionary for serialization.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Dictionary representation of the seed.
|
|
199
|
+
"""
|
|
200
|
+
return self.model_dump(mode="json")
|
|
201
|
+
|
|
202
|
+
@classmethod
|
|
203
|
+
def from_dict(cls, data: dict[str, Any]) -> "Seed":
|
|
204
|
+
"""Create seed from dictionary.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
data: Dictionary representation of the seed.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Seed instance.
|
|
211
|
+
"""
|
|
212
|
+
return cls.model_validate(data)
|
ouroboros/core/types.py
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""Core types for Ouroboros - Result type and type aliases.
|
|
2
|
+
|
|
3
|
+
This module provides:
|
|
4
|
+
- Result[T, E]: A generic type for handling expected failures without exceptions
|
|
5
|
+
- Type aliases for common domain types
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from collections.abc import Callable
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Any, cast
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True, slots=True)
|
|
14
|
+
class Result[T, E]:
|
|
15
|
+
"""A type that represents either success (Ok) or failure (Err).
|
|
16
|
+
|
|
17
|
+
Result is used for expected failures (rate limits, API errors, validation failures)
|
|
18
|
+
instead of exceptions. Exceptions are reserved for programming errors (bugs).
|
|
19
|
+
|
|
20
|
+
Usage:
|
|
21
|
+
# Construction
|
|
22
|
+
ok_result: Result[int, str] = Result.ok(42)
|
|
23
|
+
err_result: Result[int, str] = Result.err("something went wrong")
|
|
24
|
+
|
|
25
|
+
# Pattern matching
|
|
26
|
+
if result.is_ok:
|
|
27
|
+
process(result.value)
|
|
28
|
+
else:
|
|
29
|
+
handle_error(result.error)
|
|
30
|
+
|
|
31
|
+
# Transform values
|
|
32
|
+
mapped = result.map(lambda x: x * 2)
|
|
33
|
+
mapped_err = result.map_err(lambda e: CustomError(e))
|
|
34
|
+
|
|
35
|
+
# Extract values
|
|
36
|
+
value = result.unwrap() # Raises if Err
|
|
37
|
+
value = result.unwrap_or(default) # Returns default if Err
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
_value: T | None
|
|
41
|
+
_error: E | None
|
|
42
|
+
_is_ok: bool
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def ok(cls, value: T) -> Result[T, E]:
|
|
46
|
+
"""Create a successful Result containing the given value.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
value: The success value to wrap.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
A Result in the Ok state.
|
|
53
|
+
"""
|
|
54
|
+
return cls(_value=value, _error=None, _is_ok=True)
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def err(cls, error: E) -> Result[T, E]:
|
|
58
|
+
"""Create a failed Result containing the given error.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
error: The error value to wrap.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
A Result in the Err state.
|
|
65
|
+
"""
|
|
66
|
+
return cls(_value=None, _error=error, _is_ok=False)
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def is_ok(self) -> bool:
|
|
70
|
+
"""Return True if this Result is Ok (success)."""
|
|
71
|
+
return self._is_ok
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def is_err(self) -> bool:
|
|
75
|
+
"""Return True if this Result is Err (failure)."""
|
|
76
|
+
return not self._is_ok
|
|
77
|
+
|
|
78
|
+
def __repr__(self) -> str:
|
|
79
|
+
"""Return a semantic string representation of the Result.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
'Ok(value)' for success, 'Err(error)' for failure.
|
|
83
|
+
"""
|
|
84
|
+
if self._is_ok:
|
|
85
|
+
return f"Ok({self._value!r})"
|
|
86
|
+
return f"Err({self._error!r})"
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def value(self) -> T:
|
|
90
|
+
"""Return the Ok value.
|
|
91
|
+
|
|
92
|
+
Note: Only access this when is_ok is True.
|
|
93
|
+
For safe access, use unwrap() or unwrap_or().
|
|
94
|
+
"""
|
|
95
|
+
if not self._is_ok:
|
|
96
|
+
msg = "Cannot access value on Err result"
|
|
97
|
+
raise ValueError(msg)
|
|
98
|
+
return cast(T, self._value)
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def error(self) -> E:
|
|
102
|
+
"""Return the Err value.
|
|
103
|
+
|
|
104
|
+
Note: Only access this when is_err is True.
|
|
105
|
+
"""
|
|
106
|
+
if self._is_ok:
|
|
107
|
+
msg = "Cannot access error on Ok result"
|
|
108
|
+
raise ValueError(msg)
|
|
109
|
+
return cast(E, self._error)
|
|
110
|
+
|
|
111
|
+
def unwrap(self) -> T:
|
|
112
|
+
"""Return the Ok value or raise ValueError if Err.
|
|
113
|
+
|
|
114
|
+
Raises:
|
|
115
|
+
ValueError: If this Result is Err, with the error as the message.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
The success value.
|
|
119
|
+
"""
|
|
120
|
+
if self._is_ok:
|
|
121
|
+
return cast(T, self._value)
|
|
122
|
+
raise ValueError(str(self._error))
|
|
123
|
+
|
|
124
|
+
def unwrap_or(self, default: T) -> T:
|
|
125
|
+
"""Return the Ok value or the provided default if Err.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
default: Value to return if this Result is Err.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
The success value if Ok, otherwise the default.
|
|
132
|
+
"""
|
|
133
|
+
if self._is_ok:
|
|
134
|
+
return cast(T, self._value)
|
|
135
|
+
return default
|
|
136
|
+
|
|
137
|
+
def map[U](self, fn: Callable[[T], U]) -> Result[U, E]:
|
|
138
|
+
"""Transform the Ok value using the given function.
|
|
139
|
+
|
|
140
|
+
If this Result is Ok, apply fn to the value and return Ok(fn(value)).
|
|
141
|
+
If this Result is Err, return Err unchanged.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
fn: Function to apply to the Ok value.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
A new Result with the transformed value or the original error.
|
|
148
|
+
"""
|
|
149
|
+
if self._is_ok:
|
|
150
|
+
return Result.ok(fn(cast(T, self._value)))
|
|
151
|
+
return Result.err(cast(E, self._error))
|
|
152
|
+
|
|
153
|
+
def map_err[F](self, fn: Callable[[E], F]) -> Result[T, F]:
|
|
154
|
+
"""Transform the Err value using the given function.
|
|
155
|
+
|
|
156
|
+
If this Result is Err, apply fn to the error and return Err(fn(error)).
|
|
157
|
+
If this Result is Ok, return Ok unchanged.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
fn: Function to apply to the Err value.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
A new Result with the original value or the transformed error.
|
|
164
|
+
"""
|
|
165
|
+
if self._is_ok:
|
|
166
|
+
return Result.ok(cast(T, self._value))
|
|
167
|
+
return Result.err(fn(cast(E, self._error)))
|
|
168
|
+
|
|
169
|
+
def and_then[U](self, fn: Callable[[T], "Result[U, E]"]) -> "Result[U, E]":
|
|
170
|
+
"""Chain Result-producing operations (flatMap/bind).
|
|
171
|
+
|
|
172
|
+
If this Result is Ok, apply fn to the value and return the result.
|
|
173
|
+
If this Result is Err, return Err unchanged.
|
|
174
|
+
|
|
175
|
+
This is useful for chaining operations that may fail.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
fn: Function that takes the Ok value and returns a new Result.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
The result of fn if Ok, or the original Err.
|
|
182
|
+
|
|
183
|
+
Example:
|
|
184
|
+
def divide(a: int, b: int) -> Result[int, str]:
|
|
185
|
+
if b == 0:
|
|
186
|
+
return Result.err("division by zero")
|
|
187
|
+
return Result.ok(a // b)
|
|
188
|
+
|
|
189
|
+
result = Result.ok(10).and_then(lambda x: divide(x, 2))
|
|
190
|
+
# Returns Ok(5)
|
|
191
|
+
"""
|
|
192
|
+
if self._is_ok:
|
|
193
|
+
return fn(cast(T, self._value))
|
|
194
|
+
return Result.err(cast(E, self._error))
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# Type aliases for common domain types
|
|
198
|
+
EventPayload = dict[str, Any]
|
|
199
|
+
"""Type alias for event payload data - arbitrary JSON-serializable dict."""
|
|
200
|
+
|
|
201
|
+
CostUnits = int
|
|
202
|
+
"""Type alias for cost tracking - integer units (e.g., token counts)."""
|
|
203
|
+
|
|
204
|
+
DriftScore = float
|
|
205
|
+
"""Type alias for drift measurement - float between 0.0 and 1.0."""
|