atomicguard 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.
- atomicguard/__init__.py +116 -0
- atomicguard/application/__init__.py +16 -0
- atomicguard/application/action_pair.py +65 -0
- atomicguard/application/agent.py +129 -0
- atomicguard/application/workflow.py +149 -0
- atomicguard/domain/__init__.py +51 -0
- atomicguard/domain/exceptions.py +28 -0
- atomicguard/domain/interfaces.py +119 -0
- atomicguard/domain/models.py +145 -0
- atomicguard/domain/prompts.py +85 -0
- atomicguard/guards/__init__.py +19 -0
- atomicguard/guards/base.py +41 -0
- atomicguard/guards/human.py +85 -0
- atomicguard/guards/syntax.py +33 -0
- atomicguard/guards/test_runner.py +176 -0
- atomicguard/infrastructure/__init__.py +23 -0
- atomicguard/infrastructure/llm/__init__.py +11 -0
- atomicguard/infrastructure/llm/mock.py +61 -0
- atomicguard/infrastructure/llm/ollama.py +132 -0
- atomicguard/infrastructure/persistence/__init__.py +11 -0
- atomicguard/infrastructure/persistence/filesystem.py +232 -0
- atomicguard/infrastructure/persistence/memory.py +39 -0
- atomicguard-0.1.0.dist-info/METADATA +137 -0
- atomicguard-0.1.0.dist-info/RECORD +27 -0
- atomicguard-0.1.0.dist-info/WHEEL +5 -0
- atomicguard-0.1.0.dist-info/licenses/LICENSE +21 -0
- atomicguard-0.1.0.dist-info/top_level.txt +1 -0
atomicguard/__init__.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AtomicGuard: Dual-State Framework for LLM Output Management.
|
|
3
|
+
|
|
4
|
+
A framework for managing stochastic LLM outputs through deterministic
|
|
5
|
+
control flow, implementing the formal model from Thompson (2025).
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
from atomicguard import Workflow, ActionPair
|
|
9
|
+
from atomicguard.guards import SyntaxGuard
|
|
10
|
+
from atomicguard.infrastructure import OllamaGenerator
|
|
11
|
+
|
|
12
|
+
generator = OllamaGenerator(model="qwen2.5-coder:7b")
|
|
13
|
+
guard = SyntaxGuard()
|
|
14
|
+
action_pair = ActionPair(generator=generator, guard=guard)
|
|
15
|
+
|
|
16
|
+
workflow = Workflow(rmax=3)
|
|
17
|
+
workflow.add_step('g_code', action_pair=action_pair)
|
|
18
|
+
result = workflow.execute("Write a function that adds two numbers")
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
# Domain models (most commonly used)
|
|
22
|
+
# Application layer (orchestration)
|
|
23
|
+
from atomicguard.application.action_pair import ActionPair
|
|
24
|
+
from atomicguard.application.agent import DualStateAgent
|
|
25
|
+
from atomicguard.application.workflow import Workflow, WorkflowStep
|
|
26
|
+
|
|
27
|
+
# Domain exceptions
|
|
28
|
+
from atomicguard.domain.exceptions import RmaxExhausted
|
|
29
|
+
|
|
30
|
+
# Domain interfaces (for type hints and custom implementations)
|
|
31
|
+
from atomicguard.domain.interfaces import (
|
|
32
|
+
ArtifactDAGInterface,
|
|
33
|
+
GeneratorInterface,
|
|
34
|
+
GuardInterface,
|
|
35
|
+
)
|
|
36
|
+
from atomicguard.domain.models import (
|
|
37
|
+
AmbientEnvironment,
|
|
38
|
+
Artifact,
|
|
39
|
+
ArtifactStatus,
|
|
40
|
+
Context,
|
|
41
|
+
ContextSnapshot,
|
|
42
|
+
FeedbackEntry,
|
|
43
|
+
GuardResult,
|
|
44
|
+
WorkflowResult,
|
|
45
|
+
WorkflowState,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Prompts and tasks (structures only - content defined by calling applications)
|
|
49
|
+
from atomicguard.domain.prompts import (
|
|
50
|
+
PromptTemplate,
|
|
51
|
+
StepDefinition,
|
|
52
|
+
TaskDefinition,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Guards (commonly composed)
|
|
56
|
+
from atomicguard.guards import (
|
|
57
|
+
CompositeGuard,
|
|
58
|
+
DynamicTestGuard,
|
|
59
|
+
HumanReviewGuard,
|
|
60
|
+
SyntaxGuard,
|
|
61
|
+
TestGuard,
|
|
62
|
+
)
|
|
63
|
+
from atomicguard.infrastructure.llm import (
|
|
64
|
+
MockGenerator,
|
|
65
|
+
OllamaGenerator,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Infrastructure (explicit import encouraged for dependency injection)
|
|
69
|
+
from atomicguard.infrastructure.persistence import (
|
|
70
|
+
FilesystemArtifactDAG,
|
|
71
|
+
InMemoryArtifactDAG,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
__version__ = "0.1.0"
|
|
75
|
+
|
|
76
|
+
__all__ = [
|
|
77
|
+
# Version
|
|
78
|
+
"__version__",
|
|
79
|
+
# Domain models
|
|
80
|
+
"Artifact",
|
|
81
|
+
"ArtifactStatus",
|
|
82
|
+
"ContextSnapshot",
|
|
83
|
+
"FeedbackEntry",
|
|
84
|
+
"Context",
|
|
85
|
+
"AmbientEnvironment",
|
|
86
|
+
"GuardResult",
|
|
87
|
+
"WorkflowState",
|
|
88
|
+
"WorkflowResult",
|
|
89
|
+
# Prompts and tasks (structures only)
|
|
90
|
+
"PromptTemplate",
|
|
91
|
+
"StepDefinition",
|
|
92
|
+
"TaskDefinition",
|
|
93
|
+
# Domain interfaces
|
|
94
|
+
"GeneratorInterface",
|
|
95
|
+
"GuardInterface",
|
|
96
|
+
"ArtifactDAGInterface",
|
|
97
|
+
# Domain exceptions
|
|
98
|
+
"RmaxExhausted",
|
|
99
|
+
# Application layer
|
|
100
|
+
"ActionPair",
|
|
101
|
+
"DualStateAgent",
|
|
102
|
+
"Workflow",
|
|
103
|
+
"WorkflowStep",
|
|
104
|
+
# Infrastructure - Persistence
|
|
105
|
+
"InMemoryArtifactDAG",
|
|
106
|
+
"FilesystemArtifactDAG",
|
|
107
|
+
# Infrastructure - LLM
|
|
108
|
+
"OllamaGenerator",
|
|
109
|
+
"MockGenerator",
|
|
110
|
+
# Guards
|
|
111
|
+
"CompositeGuard",
|
|
112
|
+
"SyntaxGuard",
|
|
113
|
+
"TestGuard",
|
|
114
|
+
"DynamicTestGuard",
|
|
115
|
+
"HumanReviewGuard",
|
|
116
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Application layer for the Dual-State Framework.
|
|
3
|
+
|
|
4
|
+
Contains use cases and orchestration logic that coordinates domain objects.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from atomicguard.application.action_pair import ActionPair
|
|
8
|
+
from atomicguard.application.agent import DualStateAgent
|
|
9
|
+
from atomicguard.application.workflow import Workflow, WorkflowStep
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"ActionPair",
|
|
13
|
+
"DualStateAgent",
|
|
14
|
+
"Workflow",
|
|
15
|
+
"WorkflowStep",
|
|
16
|
+
]
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ActionPair: Atomic generation-verification transaction.
|
|
3
|
+
|
|
4
|
+
Paper Definition 6: A = ⟨a_gen, G⟩
|
|
5
|
+
Precondition ρ is handled at the Workflow level.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from atomicguard.domain.interfaces import GeneratorInterface, GuardInterface
|
|
9
|
+
from atomicguard.domain.models import Artifact, Context, GuardResult
|
|
10
|
+
from atomicguard.domain.prompts import PromptTemplate
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ActionPair:
|
|
14
|
+
"""
|
|
15
|
+
Atomic generation-verification transaction.
|
|
16
|
+
|
|
17
|
+
Couples a generator with a guard to form an indivisible unit.
|
|
18
|
+
The precondition ρ is handled at the Workflow level.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
generator: GeneratorInterface,
|
|
24
|
+
guard: GuardInterface,
|
|
25
|
+
prompt_template: PromptTemplate | None = None,
|
|
26
|
+
):
|
|
27
|
+
"""
|
|
28
|
+
Args:
|
|
29
|
+
generator: The artifact generator
|
|
30
|
+
guard: The validator for generated artifacts
|
|
31
|
+
prompt_template: Optional structured prompt template
|
|
32
|
+
"""
|
|
33
|
+
self._generator = generator
|
|
34
|
+
self._guard = guard
|
|
35
|
+
self._prompt_template = prompt_template
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def generator(self) -> GeneratorInterface:
|
|
39
|
+
"""Access the generator (read-only)."""
|
|
40
|
+
return self._generator
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def guard(self) -> GuardInterface:
|
|
44
|
+
"""Access the guard (read-only)."""
|
|
45
|
+
return self._guard
|
|
46
|
+
|
|
47
|
+
def execute(
|
|
48
|
+
self,
|
|
49
|
+
context: Context,
|
|
50
|
+
dependencies: dict[str, Artifact] | None = None,
|
|
51
|
+
) -> tuple[Artifact, GuardResult]:
|
|
52
|
+
"""
|
|
53
|
+
Execute the atomic generate-then-validate transaction.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
context: Generation context
|
|
57
|
+
dependencies: Artifacts from prior workflow steps
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Tuple of (generated artifact, guard result)
|
|
61
|
+
"""
|
|
62
|
+
dependencies = dependencies or {}
|
|
63
|
+
artifact = self._generator.generate(context, self._prompt_template)
|
|
64
|
+
result = self._guard.validate(artifact, **dependencies)
|
|
65
|
+
return artifact, result
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DualStateAgent: Stateless executor for a single ActionPair.
|
|
3
|
+
|
|
4
|
+
Manages only EnvironmentState (the retry loop).
|
|
5
|
+
WorkflowState is managed by Workflow.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from atomicguard.application.action_pair import ActionPair
|
|
9
|
+
from atomicguard.domain.exceptions import RmaxExhausted
|
|
10
|
+
from atomicguard.domain.interfaces import ArtifactDAGInterface
|
|
11
|
+
from atomicguard.domain.models import (
|
|
12
|
+
AmbientEnvironment,
|
|
13
|
+
Artifact,
|
|
14
|
+
Context,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DualStateAgent:
|
|
19
|
+
"""
|
|
20
|
+
Stateless executor for a single ActionPair.
|
|
21
|
+
|
|
22
|
+
Manages only EnvironmentState (retry loop).
|
|
23
|
+
WorkflowState is managed by Workflow.
|
|
24
|
+
|
|
25
|
+
Dependencies from prior workflow steps are passed to both:
|
|
26
|
+
- Generator (via Context.dependencies) - so it can see prior artifacts
|
|
27
|
+
- Guard (via validate(**deps)) - for validation against prior artifacts
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
action_pair: ActionPair,
|
|
33
|
+
artifact_dag: ArtifactDAGInterface,
|
|
34
|
+
rmax: int = 3,
|
|
35
|
+
constraints: str = "",
|
|
36
|
+
):
|
|
37
|
+
"""
|
|
38
|
+
Args:
|
|
39
|
+
action_pair: The generator-guard pair to execute
|
|
40
|
+
artifact_dag: Repository for storing artifacts
|
|
41
|
+
rmax: Maximum retry attempts (default: 3)
|
|
42
|
+
constraints: Global constraints for the ambient environment
|
|
43
|
+
"""
|
|
44
|
+
self._action_pair = action_pair
|
|
45
|
+
self._artifact_dag = artifact_dag
|
|
46
|
+
self._rmax = rmax
|
|
47
|
+
self._constraints = constraints
|
|
48
|
+
|
|
49
|
+
def execute(
|
|
50
|
+
self,
|
|
51
|
+
specification: str,
|
|
52
|
+
dependencies: dict[str, Artifact] | None = None,
|
|
53
|
+
) -> Artifact:
|
|
54
|
+
"""
|
|
55
|
+
Execute the action pair with retry logic.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
specification: The task specification
|
|
59
|
+
dependencies: Artifacts from prior workflow steps (key -> Artifact)
|
|
60
|
+
Passed to both generator (via Context) and guard (via validate)
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
The accepted artifact
|
|
64
|
+
|
|
65
|
+
Raises:
|
|
66
|
+
RmaxExhausted: If all retries fail
|
|
67
|
+
"""
|
|
68
|
+
dependencies = dependencies or {}
|
|
69
|
+
context = self._compose_context(specification, dependencies)
|
|
70
|
+
feedback_history: list[tuple[Artifact, str]] = []
|
|
71
|
+
retry_count = 0
|
|
72
|
+
|
|
73
|
+
while retry_count <= self._rmax:
|
|
74
|
+
artifact, result = self._action_pair.execute(context, dependencies)
|
|
75
|
+
|
|
76
|
+
self._artifact_dag.store(
|
|
77
|
+
artifact, metadata="" if result.passed else result.feedback
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
if result.passed:
|
|
81
|
+
return artifact
|
|
82
|
+
else:
|
|
83
|
+
feedback_history.append((artifact, result.feedback))
|
|
84
|
+
retry_count += 1
|
|
85
|
+
context = self._refine_context(
|
|
86
|
+
specification, artifact, feedback_history, dependencies
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
raise RmaxExhausted(
|
|
90
|
+
f"Failed after {self._rmax} retries", provenance=feedback_history
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def _compose_context(
|
|
94
|
+
self,
|
|
95
|
+
specification: str,
|
|
96
|
+
dependencies: dict[str, Artifact] | None = None,
|
|
97
|
+
) -> Context:
|
|
98
|
+
"""Compose initial context with dependencies."""
|
|
99
|
+
dependencies = dependencies or {}
|
|
100
|
+
ambient = AmbientEnvironment(
|
|
101
|
+
repository=self._artifact_dag, constraints=self._constraints
|
|
102
|
+
)
|
|
103
|
+
return Context(
|
|
104
|
+
ambient=ambient,
|
|
105
|
+
specification=specification,
|
|
106
|
+
current_artifact=None,
|
|
107
|
+
feedback_history=(),
|
|
108
|
+
dependencies=tuple(dependencies.items()), # Pass to generator
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def _refine_context(
|
|
112
|
+
self,
|
|
113
|
+
specification: str,
|
|
114
|
+
artifact: Artifact,
|
|
115
|
+
feedback_history: list[tuple[Artifact, str]],
|
|
116
|
+
dependencies: dict[str, Artifact] | None = None,
|
|
117
|
+
) -> Context:
|
|
118
|
+
"""Refine context with feedback from failed attempt."""
|
|
119
|
+
dependencies = dependencies or {}
|
|
120
|
+
ambient = AmbientEnvironment(
|
|
121
|
+
repository=self._artifact_dag, constraints=self._constraints
|
|
122
|
+
)
|
|
123
|
+
return Context(
|
|
124
|
+
ambient=ambient,
|
|
125
|
+
specification=specification,
|
|
126
|
+
current_artifact=artifact.content,
|
|
127
|
+
feedback_history=tuple((a.content, f) for a, f in feedback_history),
|
|
128
|
+
dependencies=tuple(dependencies.items()), # Preserve dependencies on retry
|
|
129
|
+
)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Workflow: Orchestrates ActionPair execution across multiple steps.
|
|
3
|
+
|
|
4
|
+
Owns WorkflowState and infers preconditions from step dependencies.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
from atomicguard.application.action_pair import ActionPair
|
|
10
|
+
from atomicguard.application.agent import DualStateAgent
|
|
11
|
+
from atomicguard.domain.exceptions import RmaxExhausted
|
|
12
|
+
from atomicguard.domain.interfaces import ArtifactDAGInterface
|
|
13
|
+
from atomicguard.domain.models import Artifact, WorkflowResult, WorkflowState
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class WorkflowStep:
|
|
18
|
+
"""Internal step representation."""
|
|
19
|
+
|
|
20
|
+
guard_id: str
|
|
21
|
+
action_pair: ActionPair
|
|
22
|
+
requires: tuple[str, ...]
|
|
23
|
+
deps: tuple[str, ...] # Artifacts to pass to guard
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Workflow:
|
|
27
|
+
"""
|
|
28
|
+
Orchestrates ActionPair execution.
|
|
29
|
+
|
|
30
|
+
Owns WorkflowState and infers preconditions from requires.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
artifact_dag: ArtifactDAGInterface | None = None,
|
|
36
|
+
rmax: int = 3,
|
|
37
|
+
constraints: str = "",
|
|
38
|
+
):
|
|
39
|
+
"""
|
|
40
|
+
Args:
|
|
41
|
+
artifact_dag: Repository for storing artifacts (creates InMemory if None)
|
|
42
|
+
rmax: Maximum retries per step
|
|
43
|
+
constraints: Global constraints for the ambient environment
|
|
44
|
+
"""
|
|
45
|
+
# Lazy import to avoid circular dependency
|
|
46
|
+
if artifact_dag is None:
|
|
47
|
+
from atomicguard.infrastructure.persistence import InMemoryArtifactDAG
|
|
48
|
+
|
|
49
|
+
artifact_dag = InMemoryArtifactDAG()
|
|
50
|
+
|
|
51
|
+
self._dag = artifact_dag
|
|
52
|
+
self._rmax = rmax
|
|
53
|
+
self._constraints = constraints
|
|
54
|
+
self._steps: list[WorkflowStep] = []
|
|
55
|
+
self._workflow_state = WorkflowState()
|
|
56
|
+
self._artifacts: dict[str, Artifact] = {}
|
|
57
|
+
|
|
58
|
+
def add_step(
|
|
59
|
+
self,
|
|
60
|
+
guard_id: str,
|
|
61
|
+
action_pair: ActionPair,
|
|
62
|
+
requires: tuple[str, ...] = (),
|
|
63
|
+
deps: tuple[str, ...] | None = None,
|
|
64
|
+
) -> "Workflow":
|
|
65
|
+
"""
|
|
66
|
+
Register a step. Precondition inferred from requires.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
guard_id: Unique identifier for this step (e.g., 'g_test', 'g_impl')
|
|
70
|
+
action_pair: The generator-guard pair for this step
|
|
71
|
+
requires: Guard IDs that must be satisfied before this step
|
|
72
|
+
deps: Artifact dependencies to pass to guard (defaults to requires)
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Self for fluent chaining
|
|
76
|
+
"""
|
|
77
|
+
if deps is None:
|
|
78
|
+
deps = requires
|
|
79
|
+
self._steps.append(WorkflowStep(guard_id, action_pair, requires, deps))
|
|
80
|
+
return self # Fluent
|
|
81
|
+
|
|
82
|
+
def execute(self, specification: str) -> WorkflowResult:
|
|
83
|
+
"""
|
|
84
|
+
Execute the workflow until completion or failure.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
specification: The task specification
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
WorkflowResult with success status and artifacts
|
|
91
|
+
"""
|
|
92
|
+
while not self._is_goal_state():
|
|
93
|
+
step = self._find_applicable()
|
|
94
|
+
|
|
95
|
+
if step is None:
|
|
96
|
+
return WorkflowResult(
|
|
97
|
+
success=False,
|
|
98
|
+
artifacts=self._artifacts,
|
|
99
|
+
failed_step="No applicable step",
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Extract dependencies
|
|
103
|
+
dependencies = {
|
|
104
|
+
gid.replace("g_", ""): self._artifacts[gid]
|
|
105
|
+
for gid in step.deps
|
|
106
|
+
if gid in self._artifacts
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# Execute via stateless agent
|
|
110
|
+
agent = DualStateAgent(
|
|
111
|
+
action_pair=step.action_pair,
|
|
112
|
+
artifact_dag=self._dag,
|
|
113
|
+
rmax=self._rmax,
|
|
114
|
+
constraints=self._constraints,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
artifact = agent.execute(specification, dependencies)
|
|
119
|
+
self._artifacts[step.guard_id] = artifact
|
|
120
|
+
self._workflow_state.satisfy(step.guard_id, artifact.artifact_id)
|
|
121
|
+
|
|
122
|
+
except RmaxExhausted as e:
|
|
123
|
+
return WorkflowResult(
|
|
124
|
+
success=False,
|
|
125
|
+
artifacts=self._artifacts,
|
|
126
|
+
failed_step=step.guard_id,
|
|
127
|
+
provenance=tuple(e.provenance),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
return WorkflowResult(success=True, artifacts=self._artifacts)
|
|
131
|
+
|
|
132
|
+
def _precondition_met(self, step: WorkflowStep) -> bool:
|
|
133
|
+
"""Precondition: all required guards satisfied."""
|
|
134
|
+
return all(self._workflow_state.is_satisfied(req) for req in step.requires)
|
|
135
|
+
|
|
136
|
+
def _find_applicable(self) -> WorkflowStep | None:
|
|
137
|
+
"""Find first step not done and with precondition met."""
|
|
138
|
+
for step in self._steps:
|
|
139
|
+
if not self._workflow_state.is_satisfied(
|
|
140
|
+
step.guard_id
|
|
141
|
+
) and self._precondition_met(step):
|
|
142
|
+
return step
|
|
143
|
+
return None
|
|
144
|
+
|
|
145
|
+
def _is_goal_state(self) -> bool:
|
|
146
|
+
"""Check if all steps are satisfied."""
|
|
147
|
+
return all(
|
|
148
|
+
self._workflow_state.is_satisfied(step.guard_id) for step in self._steps
|
|
149
|
+
)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Domain layer for the Dual-State Framework.
|
|
3
|
+
|
|
4
|
+
Contains core business logic with no external dependencies.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from atomicguard.domain.exceptions import RmaxExhausted
|
|
8
|
+
from atomicguard.domain.interfaces import (
|
|
9
|
+
ArtifactDAGInterface,
|
|
10
|
+
GeneratorInterface,
|
|
11
|
+
GuardInterface,
|
|
12
|
+
)
|
|
13
|
+
from atomicguard.domain.models import (
|
|
14
|
+
AmbientEnvironment,
|
|
15
|
+
Artifact,
|
|
16
|
+
ArtifactStatus,
|
|
17
|
+
Context,
|
|
18
|
+
ContextSnapshot,
|
|
19
|
+
FeedbackEntry,
|
|
20
|
+
GuardResult,
|
|
21
|
+
WorkflowResult,
|
|
22
|
+
WorkflowState,
|
|
23
|
+
)
|
|
24
|
+
from atomicguard.domain.prompts import (
|
|
25
|
+
PromptTemplate,
|
|
26
|
+
StepDefinition,
|
|
27
|
+
TaskDefinition,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
# Models
|
|
32
|
+
"Artifact",
|
|
33
|
+
"ArtifactStatus",
|
|
34
|
+
"ContextSnapshot",
|
|
35
|
+
"FeedbackEntry",
|
|
36
|
+
"Context",
|
|
37
|
+
"AmbientEnvironment",
|
|
38
|
+
"GuardResult",
|
|
39
|
+
"WorkflowState",
|
|
40
|
+
"WorkflowResult",
|
|
41
|
+
# Prompts and Tasks (structures only, no content)
|
|
42
|
+
"PromptTemplate",
|
|
43
|
+
"StepDefinition",
|
|
44
|
+
"TaskDefinition",
|
|
45
|
+
# Interfaces
|
|
46
|
+
"GeneratorInterface",
|
|
47
|
+
"GuardInterface",
|
|
48
|
+
"ArtifactDAGInterface",
|
|
49
|
+
# Exceptions
|
|
50
|
+
"RmaxExhausted",
|
|
51
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Domain exceptions for the Dual-State Framework.
|
|
3
|
+
|
|
4
|
+
These represent business rule violations in the domain layer.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from atomicguard.domain.models import Artifact
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class RmaxExhausted(Exception):
|
|
14
|
+
"""
|
|
15
|
+
Raised when maximum retry attempts are exhausted.
|
|
16
|
+
|
|
17
|
+
This is a domain exception representing the business rule that
|
|
18
|
+
generation must succeed within r_max attempts.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, message: str, provenance: list[tuple["Artifact", str]]):
|
|
22
|
+
"""
|
|
23
|
+
Args:
|
|
24
|
+
message: Human-readable error message
|
|
25
|
+
provenance: List of (artifact, feedback) tuples for each failed attempt
|
|
26
|
+
"""
|
|
27
|
+
super().__init__(message)
|
|
28
|
+
self.provenance = provenance
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Domain interfaces (Ports) for the Dual-State Framework.
|
|
3
|
+
|
|
4
|
+
These abstract base classes define the contracts that implementations must satisfy.
|
|
5
|
+
They have no external dependencies and represent the core domain boundaries.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from typing import TYPE_CHECKING, Optional
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from atomicguard.domain.models import (
|
|
13
|
+
Artifact,
|
|
14
|
+
Context,
|
|
15
|
+
GuardResult,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from atomicguard.domain.models import Artifact, Context, GuardResult
|
|
20
|
+
from atomicguard.domain.prompts import PromptTemplate
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class GeneratorInterface(ABC):
|
|
24
|
+
"""
|
|
25
|
+
Port for artifact generation.
|
|
26
|
+
|
|
27
|
+
Implementations connect to LLMs or other generation sources.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def generate(
|
|
32
|
+
self, context: "Context", template: Optional["PromptTemplate"] = None
|
|
33
|
+
) -> "Artifact":
|
|
34
|
+
"""
|
|
35
|
+
Generate an artifact based on context.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
context: The generation context including specification and feedback
|
|
39
|
+
template: Optional prompt template for structured generation
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
A new Artifact containing the generated content
|
|
43
|
+
"""
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class GuardInterface(ABC):
|
|
48
|
+
"""
|
|
49
|
+
Port for artifact validation.
|
|
50
|
+
|
|
51
|
+
Guards are deterministic validators that return ⊤ (pass) or ⊥ (fail with feedback).
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
@abstractmethod
|
|
55
|
+
def validate(
|
|
56
|
+
self, artifact: "Artifact", **dependencies: "Artifact"
|
|
57
|
+
) -> "GuardResult":
|
|
58
|
+
"""
|
|
59
|
+
Validate an artifact.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
artifact: The artifact to validate
|
|
63
|
+
**dependencies: Artifacts from prior workflow steps (key -> Artifact)
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
GuardResult with passed=True/False and optional feedback
|
|
67
|
+
"""
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class ArtifactDAGInterface(ABC):
|
|
72
|
+
"""
|
|
73
|
+
Port for artifact persistence.
|
|
74
|
+
|
|
75
|
+
Implementations provide append-only storage for the Versioned Repository (Definition 4).
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
@abstractmethod
|
|
79
|
+
def store(self, artifact: "Artifact", metadata: str = "") -> str:
|
|
80
|
+
"""
|
|
81
|
+
Store an artifact in the DAG.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
artifact: The artifact to store
|
|
85
|
+
metadata: Optional metadata string
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
The artifact_id
|
|
89
|
+
"""
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
@abstractmethod
|
|
93
|
+
def get_artifact(self, artifact_id: str) -> "Artifact":
|
|
94
|
+
"""
|
|
95
|
+
Retrieve an artifact by ID.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
artifact_id: The unique identifier
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
The artifact
|
|
102
|
+
|
|
103
|
+
Raises:
|
|
104
|
+
KeyError: If artifact not found
|
|
105
|
+
"""
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
@abstractmethod
|
|
109
|
+
def get_provenance(self, artifact_id: str) -> list["Artifact"]:
|
|
110
|
+
"""
|
|
111
|
+
Trace the retry chain via previous_attempt_id.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
artifact_id: Starting artifact
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
List of artifacts from oldest to newest in the chain
|
|
118
|
+
"""
|
|
119
|
+
pass
|