atomicguard 1.2.0__tar.gz → 2.1.0__tar.gz

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.
Files changed (48) hide show
  1. {atomicguard-1.2.0/src/atomicguard.egg-info → atomicguard-2.1.0}/PKG-INFO +6 -1
  2. {atomicguard-1.2.0 → atomicguard-2.1.0}/pyproject.toml +9 -2
  3. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/__init__.py +4 -3
  4. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/application/__init__.py +5 -0
  5. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/application/action_pair.py +2 -2
  6. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/application/agent.py +2 -3
  7. atomicguard-2.1.0/src/atomicguard/application/checkpoint_service.py +130 -0
  8. atomicguard-2.1.0/src/atomicguard/application/resume_service.py +242 -0
  9. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/application/workflow.py +53 -3
  10. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/domain/__init__.py +2 -0
  11. atomicguard-2.1.0/src/atomicguard/domain/extraction.py +257 -0
  12. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/domain/interfaces.py +17 -2
  13. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/domain/models.py +91 -13
  14. atomicguard-2.1.0/src/atomicguard/domain/multiagent.py +279 -0
  15. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/domain/prompts.py +12 -1
  16. atomicguard-2.1.0/src/atomicguard/domain/workflow.py +642 -0
  17. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/infrastructure/llm/huggingface.py +4 -22
  18. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/infrastructure/llm/mock.py +5 -4
  19. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/infrastructure/llm/ollama.py +5 -23
  20. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/infrastructure/persistence/filesystem.py +133 -46
  21. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/infrastructure/persistence/memory.py +10 -0
  22. {atomicguard-1.2.0 → atomicguard-2.1.0/src/atomicguard.egg-info}/PKG-INFO +6 -1
  23. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard.egg-info/SOURCES.txt +5 -0
  24. atomicguard-2.1.0/src/atomicguard.egg-info/requires.txt +10 -0
  25. atomicguard-1.2.0/src/atomicguard.egg-info/requires.txt +0 -4
  26. {atomicguard-1.2.0 → atomicguard-2.1.0}/LICENSE +0 -0
  27. {atomicguard-1.2.0 → atomicguard-2.1.0}/README.md +0 -0
  28. {atomicguard-1.2.0 → atomicguard-2.1.0}/setup.cfg +0 -0
  29. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/domain/exceptions.py +0 -0
  30. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/guards/__init__.py +0 -0
  31. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/guards/composite/__init__.py +0 -0
  32. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/guards/composite/base.py +0 -0
  33. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/guards/dynamic/__init__.py +0 -0
  34. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/guards/dynamic/test_runner.py +0 -0
  35. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/guards/interactive/__init__.py +0 -0
  36. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/guards/interactive/human.py +0 -0
  37. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/guards/static/__init__.py +0 -0
  38. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/guards/static/imports.py +0 -0
  39. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/guards/static/syntax.py +0 -0
  40. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/infrastructure/__init__.py +0 -0
  41. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/infrastructure/llm/__init__.py +0 -0
  42. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/infrastructure/persistence/__init__.py +0 -0
  43. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/infrastructure/persistence/checkpoint.py +0 -0
  44. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/infrastructure/registry.py +0 -0
  45. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard/schemas/__init__.py +0 -0
  46. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard.egg-info/dependency_links.txt +0 -0
  47. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard.egg-info/entry_points.txt +0 -0
  48. {atomicguard-1.2.0 → atomicguard-2.1.0}/src/atomicguard.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: atomicguard
3
- Version: 1.2.0
3
+ Version: 2.1.0
4
4
  Summary: A Dual-State Agent Framework for reliable LLM code generation with guard-validated loops
5
5
  Author-email: Matthew Thompson <thompsonson@gmail.com>
6
6
  Maintainer-email: Matthew Thompson <thompsonson@gmail.com>
@@ -30,6 +30,11 @@ Requires-Dist: matplotlib>=3.10.0
30
30
  Requires-Dist: openhands-ai>=0.27.0
31
31
  Requires-Dist: pydantic-ai>=1.0.0
32
32
  Requires-Dist: pytestarch>=4.0.1
33
+ Provides-Extra: experiment
34
+ Requires-Dist: datasets>=2.0.0; extra == "experiment"
35
+ Requires-Dist: huggingface_hub>=0.20; extra == "experiment"
36
+ Requires-Dist: swebench>=2.0.0; extra == "experiment"
37
+ Requires-Dist: docker>=7.0.0; extra == "experiment"
33
38
  Dynamic: license-file
34
39
 
35
40
  # AtomicGuard
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "atomicguard"
3
- version = "1.2.0"
3
+ version = "2.1.0"
4
4
  description = "A Dual-State Agent Framework for reliable LLM code generation with guard-validated loops"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -54,8 +54,15 @@ OllamaGenerator = "atomicguard.infrastructure.llm:OllamaGenerator"
54
54
  HuggingFaceGenerator = "atomicguard.infrastructure.llm:HuggingFaceGenerator"
55
55
  MockGenerator = "atomicguard.infrastructure.llm:MockGenerator"
56
56
 
57
+ [project.optional-dependencies]
58
+ experiment = [
59
+ "datasets>=2.0.0",
60
+ "huggingface_hub>=0.20",
61
+ "swebench>=2.0.0",
62
+ "docker>=7.0.0",
63
+ ]
64
+
57
65
  # Note: Dev dependencies are in [dependency-groups] below, not here.
58
- # [project.optional-dependencies] is for end-user extras only.
59
66
 
60
67
  [build-system]
61
68
  requires = ["setuptools>=75.0.0", "wheel"]
@@ -5,13 +5,14 @@ A framework for managing stochastic LLM outputs through deterministic
5
5
  control flow, implementing the formal model from Thompson (2025).
6
6
 
7
7
  Example:
8
- from atomicguard import Workflow, ActionPair
8
+ from atomicguard import Workflow, ActionPair, PromptTemplate
9
9
  from atomicguard.guards import SyntaxGuard
10
10
  from atomicguard.infrastructure import OllamaGenerator
11
11
 
12
12
  generator = OllamaGenerator(model="qwen2.5-coder:7b")
13
13
  guard = SyntaxGuard()
14
- action_pair = ActionPair(generator=generator, guard=guard)
14
+ template = PromptTemplate(role="code generator", constraints="write clean Python code", task="generate code")
15
+ action_pair = ActionPair(generator=generator, guard=guard, prompt_template=template)
15
16
 
16
17
  workflow = Workflow(rmax=3)
17
18
  workflow.add_step('g_code', action_pair=action_pair)
@@ -73,7 +74,7 @@ from atomicguard.infrastructure.persistence import (
73
74
  InMemoryArtifactDAG,
74
75
  )
75
76
 
76
- __version__ = "1.2.0"
77
+ __version__ = "2.1.0"
77
78
 
78
79
  __all__ = [
79
80
  # Version
@@ -6,11 +6,16 @@ Contains use cases and orchestration logic that coordinates domain objects.
6
6
 
7
7
  from atomicguard.application.action_pair import ActionPair
8
8
  from atomicguard.application.agent import DualStateAgent
9
+ from atomicguard.application.checkpoint_service import CheckpointService
10
+ from atomicguard.application.resume_service import ResumeResult, WorkflowResumeService
9
11
  from atomicguard.application.workflow import Workflow, WorkflowStep
10
12
 
11
13
  __all__ = [
12
14
  "ActionPair",
15
+ "CheckpointService",
13
16
  "DualStateAgent",
17
+ "ResumeResult",
14
18
  "Workflow",
19
+ "WorkflowResumeService",
15
20
  "WorkflowStep",
16
21
  ]
@@ -22,13 +22,13 @@ class ActionPair:
22
22
  self,
23
23
  generator: GeneratorInterface,
24
24
  guard: GuardInterface,
25
- prompt_template: PromptTemplate | None = None,
25
+ prompt_template: PromptTemplate,
26
26
  ):
27
27
  """
28
28
  Args:
29
29
  generator: The artifact generator
30
30
  guard: The validator for generated artifacts
31
- prompt_template: Optional structured prompt template
31
+ prompt_template: Structured prompt template for generation
32
32
  """
33
33
  self._generator = generator
34
34
  self._guard = guard
@@ -92,15 +92,14 @@ class DualStateAgent:
92
92
  for a, f in feedback_history
93
93
  )
94
94
 
95
- # Update artifact with guard result AND provenance metadata
95
+ # Update artifact with full GuardResult (Extension 08: Composite Guards)
96
96
  artifact = replace(
97
97
  artifact,
98
98
  previous_attempt_id=previous_id,
99
99
  status=ArtifactStatus.ACCEPTED
100
100
  if result.passed
101
101
  else ArtifactStatus.REJECTED,
102
- guard_result=result.passed,
103
- feedback=result.feedback,
102
+ guard_result=result, # Store full GuardResult, not just bool
104
103
  context=replace(
105
104
  artifact.context,
106
105
  feedback_history=fb_entries,
@@ -0,0 +1,130 @@
1
+ """Application service for checkpoint operations.
2
+
3
+ Provides clean separation of checkpoint creation from workflow execution,
4
+ with Extension 01 W_ref support for workflow integrity verification.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import uuid
10
+ from datetime import UTC, datetime
11
+ from typing import TYPE_CHECKING
12
+
13
+ from atomicguard.domain.models import FailureType, WorkflowCheckpoint
14
+ from atomicguard.domain.workflow import compute_workflow_ref
15
+
16
+ if TYPE_CHECKING:
17
+ from atomicguard.domain.interfaces import (
18
+ ArtifactDAGInterface,
19
+ CheckpointDAGInterface,
20
+ )
21
+
22
+
23
+ class CheckpointService:
24
+ """Application service for creating and managing checkpoints.
25
+
26
+ Separates checkpoint creation concerns from Workflow execution,
27
+ enabling cleaner DDD architecture with proper layering.
28
+ """
29
+
30
+ def __init__(
31
+ self,
32
+ checkpoint_dag: CheckpointDAGInterface,
33
+ artifact_dag: ArtifactDAGInterface | None = None,
34
+ ) -> None:
35
+ """Initialize checkpoint service.
36
+
37
+ Args:
38
+ checkpoint_dag: DAG for storing checkpoints.
39
+ artifact_dag: DAG for storing artifacts (optional).
40
+ """
41
+ self._checkpoint_dag = checkpoint_dag
42
+ self._artifact_dag = artifact_dag
43
+
44
+ def create_checkpoint(
45
+ self,
46
+ workflow_definition: dict,
47
+ workflow_id: str,
48
+ specification: str,
49
+ constraints: str,
50
+ rmax: int,
51
+ completed_steps: tuple[str, ...],
52
+ artifact_ids: tuple[tuple[str, str], ...],
53
+ failure_type: FailureType,
54
+ failed_step: str,
55
+ failed_artifact_id: str | None,
56
+ failure_feedback: str,
57
+ provenance_ids: tuple[str, ...],
58
+ ) -> WorkflowCheckpoint:
59
+ """Create checkpoint with W_ref for integrity verification.
60
+
61
+ Computes the W_ref (content-addressed workflow hash) from the
62
+ workflow definition and stores it with the checkpoint. This
63
+ enables resume-time verification that the workflow hasn't changed.
64
+
65
+ Args:
66
+ workflow_definition: Dict representing workflow structure for W_ref computation.
67
+ workflow_id: UUID of the workflow execution instance.
68
+ specification: Original task specification (Ψ).
69
+ constraints: Global constraints (Ω).
70
+ rmax: Original retry budget.
71
+ completed_steps: Guard IDs that completed successfully.
72
+ artifact_ids: (guard_id, artifact_id) pairs for completed artifacts.
73
+ failure_type: Type of failure (ESCALATION or RMAX_EXHAUSTED).
74
+ failed_step: Guard ID where failure occurred.
75
+ failed_artifact_id: ID of the last artifact before failure.
76
+ failure_feedback: Error/feedback message from the guard.
77
+ provenance_ids: Artifact IDs of all failed attempts.
78
+
79
+ Returns:
80
+ WorkflowCheckpoint with W_ref for integrity verification.
81
+ """
82
+ # Compute W_ref using domain layer (stores in registry for later resolution)
83
+ w_ref = compute_workflow_ref(workflow_definition, store=True)
84
+
85
+ checkpoint = WorkflowCheckpoint(
86
+ checkpoint_id=str(uuid.uuid4()),
87
+ workflow_id=workflow_id,
88
+ created_at=datetime.now(UTC).isoformat(),
89
+ specification=specification,
90
+ constraints=constraints,
91
+ rmax=rmax,
92
+ completed_steps=completed_steps,
93
+ artifact_ids=artifact_ids,
94
+ failure_type=failure_type,
95
+ failed_step=failed_step,
96
+ failed_artifact_id=failed_artifact_id,
97
+ failure_feedback=failure_feedback,
98
+ provenance_ids=provenance_ids,
99
+ workflow_ref=w_ref, # Extension 01: W_ref for integrity
100
+ )
101
+
102
+ self._checkpoint_dag.store_checkpoint(checkpoint)
103
+ return checkpoint
104
+
105
+ def get_checkpoint(self, checkpoint_id: str) -> WorkflowCheckpoint:
106
+ """Retrieve a checkpoint by ID.
107
+
108
+ Args:
109
+ checkpoint_id: The checkpoint identifier.
110
+
111
+ Returns:
112
+ The WorkflowCheckpoint.
113
+
114
+ Raises:
115
+ KeyError: If checkpoint not found.
116
+ """
117
+ return self._checkpoint_dag.get_checkpoint(checkpoint_id)
118
+
119
+ def list_checkpoints(
120
+ self, workflow_id: str | None = None
121
+ ) -> list[WorkflowCheckpoint]:
122
+ """List checkpoints, optionally filtered by workflow_id.
123
+
124
+ Args:
125
+ workflow_id: Optional filter by workflow.
126
+
127
+ Returns:
128
+ List of matching checkpoints, newest first.
129
+ """
130
+ return self._checkpoint_dag.list_checkpoints(workflow_id)
@@ -0,0 +1,242 @@
1
+ """Application service for workflow resume operations.
2
+
3
+ Provides clean separation of resume logic from workflow execution,
4
+ with Extension 01 W_ref support for workflow integrity verification.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import replace
10
+ from typing import TYPE_CHECKING
11
+
12
+ from atomicguard.domain.models import (
13
+ AmendmentType,
14
+ Artifact,
15
+ ArtifactStatus,
16
+ GuardResult,
17
+ HumanAmendment,
18
+ )
19
+ from atomicguard.domain.workflow import (
20
+ HumanAmendmentProcessor,
21
+ WorkflowResumer,
22
+ compute_workflow_ref,
23
+ )
24
+
25
+ if TYPE_CHECKING:
26
+ from atomicguard.domain.interfaces import (
27
+ ArtifactDAGInterface,
28
+ CheckpointDAGInterface,
29
+ GuardInterface,
30
+ )
31
+ from atomicguard.domain.models import Context
32
+ from atomicguard.domain.workflow import RestoredWorkflowState
33
+
34
+
35
+ class ResumeResult:
36
+ """Result of resume operation.
37
+
38
+ Attributes:
39
+ success: Whether the resume completed without errors.
40
+ artifact: The artifact created from the amendment (if any).
41
+ guard_result: The guard validation result (if validation occurred).
42
+ needs_retry: Whether the workflow needs a retry (feedback amendment or guard failure).
43
+ error: Error message if success is False.
44
+ amended_context: The amended context for retry (feedback amendment only).
45
+ """
46
+
47
+ def __init__(
48
+ self,
49
+ success: bool,
50
+ artifact: Artifact | None = None,
51
+ guard_result: GuardResult | None = None,
52
+ needs_retry: bool = False,
53
+ error: str | None = None,
54
+ amended_context: object | None = None,
55
+ ) -> None:
56
+ self.success = success
57
+ self.artifact = artifact
58
+ self.guard_result = guard_result
59
+ self.needs_retry = needs_retry
60
+ self.error = error
61
+ self.amended_context = amended_context
62
+
63
+
64
+ class WorkflowResumeService:
65
+ """Application service for resuming workflows from checkpoints.
66
+
67
+ Delegates to domain layer components (WorkflowResumer, HumanAmendmentProcessor)
68
+ while providing a clean interface for the presentation layer.
69
+
70
+ Extension 01 compliant: Verifies W_ref integrity on resume.
71
+ """
72
+
73
+ def __init__(
74
+ self,
75
+ checkpoint_dag: CheckpointDAGInterface,
76
+ artifact_dag: ArtifactDAGInterface,
77
+ ) -> None:
78
+ """Initialize resume service.
79
+
80
+ Args:
81
+ checkpoint_dag: DAG for storing checkpoints and amendments.
82
+ artifact_dag: DAG for storing artifacts.
83
+ """
84
+ self._checkpoint_dag = checkpoint_dag
85
+ self._artifact_dag = artifact_dag
86
+
87
+ # Initialize domain layer components
88
+ self._resumer = WorkflowResumer(checkpoint_dag, artifact_dag)
89
+ self._processor = HumanAmendmentProcessor(checkpoint_dag, artifact_dag)
90
+
91
+ def resume(
92
+ self,
93
+ checkpoint_id: str,
94
+ amendment: HumanAmendment,
95
+ current_workflow_definition: dict,
96
+ guard: GuardInterface,
97
+ dependencies: dict[str, Artifact] | None = None,
98
+ ) -> ResumeResult:
99
+ """Resume workflow from checkpoint with W_ref verification.
100
+
101
+ Args:
102
+ checkpoint_id: ID of checkpoint to resume from.
103
+ amendment: Human-provided amendment.
104
+ current_workflow_definition: Current workflow dict for W_ref verification.
105
+ guard: Guard to validate the human artifact.
106
+ dependencies: Artifact dependencies for guard validation.
107
+
108
+ Returns:
109
+ ResumeResult with success status and artifact.
110
+
111
+ Raises:
112
+ WorkflowIntegrityError: If workflow changed since checkpoint.
113
+ """
114
+ dependencies = dependencies or {}
115
+
116
+ # 1. Store amendment (required by HumanAmendmentProcessor)
117
+ self._checkpoint_dag.store_amendment(amendment)
118
+
119
+ # 2. Verify W_ref integrity using domain layer
120
+ current_w_ref = compute_workflow_ref(current_workflow_definition, store=False)
121
+ resume_result = self._resumer.resume(checkpoint_id, current_w_ref)
122
+
123
+ if not resume_result.success:
124
+ return ResumeResult(success=False, error=resume_result.error)
125
+
126
+ # 3. Process amendment based on type
127
+ if amendment.amendment_type == AmendmentType.ARTIFACT:
128
+ return self._handle_artifact_amendment(amendment, guard, dependencies)
129
+
130
+ elif amendment.amendment_type == AmendmentType.FEEDBACK:
131
+ # Return amended context for agent retry
132
+ amended_context = self._processor.apply_amendment_to_context(
133
+ amendment.amendment_id
134
+ )
135
+ return ResumeResult(
136
+ success=True,
137
+ needs_retry=True,
138
+ amended_context=amended_context,
139
+ )
140
+
141
+ return ResumeResult(
142
+ success=False, error=f"Unknown amendment type: {amendment.amendment_type}"
143
+ )
144
+
145
+ def _handle_artifact_amendment(
146
+ self,
147
+ amendment: HumanAmendment,
148
+ guard: GuardInterface,
149
+ dependencies: dict[str, Artifact],
150
+ ) -> ResumeResult:
151
+ """Process artifact amendment using domain layer.
152
+
153
+ Args:
154
+ amendment: The human amendment with artifact content.
155
+ guard: Guard to validate the artifact.
156
+ dependencies: Artifact dependencies for guard validation.
157
+
158
+ Returns:
159
+ ResumeResult with artifact and guard result.
160
+ """
161
+ try:
162
+ # Create artifact from amendment using domain layer
163
+ human_artifact = self._processor.create_artifact_from_amendment(
164
+ amendment.amendment_id
165
+ )
166
+ except KeyError as e:
167
+ return ResumeResult(
168
+ success=False,
169
+ error=f"Failed to create artifact from amendment: {e}",
170
+ )
171
+ except ValueError as e:
172
+ return ResumeResult(
173
+ success=False,
174
+ error=f"Invalid amendment: {e}",
175
+ )
176
+
177
+ # Validate with guard
178
+ guard_result = guard.validate(human_artifact, **dependencies)
179
+
180
+ if guard_result.passed:
181
+ # Update artifact status
182
+ accepted_artifact = replace(
183
+ human_artifact,
184
+ status=ArtifactStatus.ACCEPTED,
185
+ guard_result=guard_result, # Store full GuardResult
186
+ )
187
+ self._artifact_dag.store(accepted_artifact)
188
+
189
+ return ResumeResult(
190
+ success=True,
191
+ artifact=accepted_artifact,
192
+ guard_result=guard_result,
193
+ )
194
+ else:
195
+ # Guard failed - return for new checkpoint
196
+ return ResumeResult(
197
+ success=True,
198
+ artifact=human_artifact,
199
+ guard_result=guard_result,
200
+ needs_retry=True,
201
+ )
202
+
203
+ def get_restored_state(self, checkpoint_id: str) -> RestoredWorkflowState:
204
+ """Get restored workflow state from checkpoint.
205
+
206
+ Args:
207
+ checkpoint_id: ID of checkpoint to restore from.
208
+
209
+ Returns:
210
+ RestoredWorkflowState with completed steps and artifact IDs.
211
+ """
212
+ return self._resumer.restore_state(checkpoint_id)
213
+
214
+ def get_reconstructed_context(self, checkpoint_id: str) -> Context:
215
+ """Get reconstructed context from checkpoint.
216
+
217
+ Args:
218
+ checkpoint_id: ID of checkpoint.
219
+
220
+ Returns:
221
+ Reconstructed Context.
222
+ """
223
+ return self._resumer.reconstruct_context(checkpoint_id)
224
+
225
+ def verify_workflow_integrity(
226
+ self, checkpoint_id: str, current_workflow_definition: dict
227
+ ) -> bool:
228
+ """Verify workflow integrity without resuming.
229
+
230
+ Args:
231
+ checkpoint_id: ID of checkpoint to verify against.
232
+ current_workflow_definition: Current workflow dict.
233
+
234
+ Returns:
235
+ True if workflow matches checkpoint, False otherwise.
236
+
237
+ Raises:
238
+ WorkflowIntegrityError: If workflow changed since checkpoint.
239
+ """
240
+ current_w_ref = compute_workflow_ref(current_workflow_definition, store=False)
241
+ result = self._resumer.resume(checkpoint_id, current_w_ref)
242
+ return result.success
@@ -5,6 +5,7 @@ Owns WorkflowState and infers preconditions from step dependencies.
5
5
  """
6
6
 
7
7
  import uuid
8
+ import warnings
8
9
  from dataclasses import dataclass, replace
9
10
  from datetime import UTC, datetime
10
11
 
@@ -175,11 +176,55 @@ class Workflow:
175
176
  self._workflow_state.is_satisfied(step.guard_id) for step in self._steps
176
177
  )
177
178
 
179
+ def get_workflow_definition(self) -> dict:
180
+ """Build workflow definition dict for W_ref computation.
181
+
182
+ Returns a serializable dict representing the workflow structure.
183
+ Used by CheckpointService for W_ref computation.
184
+
185
+ Returns:
186
+ Dict with steps, rmax, and constraints for hashing.
187
+ """
188
+ return {
189
+ "steps": [
190
+ {
191
+ "guard_id": step.guard_id,
192
+ "requires": list(step.requires),
193
+ "deps": list(step.deps),
194
+ }
195
+ for step in self._steps
196
+ ],
197
+ "rmax": self._rmax,
198
+ "constraints": self._constraints,
199
+ }
200
+
201
+ def get_step(self, guard_id: str) -> WorkflowStep:
202
+ """Get a workflow step by guard_id.
203
+
204
+ Args:
205
+ guard_id: The identifier of the step to retrieve.
206
+
207
+ Returns:
208
+ The WorkflowStep with the given guard_id.
209
+
210
+ Raises:
211
+ KeyError: If no step with the given guard_id exists.
212
+ """
213
+ for step in self._steps:
214
+ if step.guard_id == guard_id:
215
+ return step
216
+ raise KeyError(f"Step not found: {guard_id}")
217
+
178
218
 
179
219
  class ResumableWorkflow(Workflow):
180
220
  """
181
221
  Workflow with checkpoint and resume support.
182
222
 
223
+ .. deprecated::
224
+ Use Workflow + CheckpointService + WorkflowResumeService instead.
225
+ This class is maintained for backwards compatibility but will be
226
+ removed in a future version.
227
+
183
228
  Extends Workflow with:
184
229
  - Automatic checkpoint creation on failure
185
230
  - Resume from checkpoint with human amendment
@@ -202,6 +247,12 @@ class ResumableWorkflow(Workflow):
202
247
  constraints: Global constraints for the ambient environment
203
248
  auto_checkpoint: Create checkpoint on failure (default True)
204
249
  """
250
+ warnings.warn(
251
+ "ResumableWorkflow is deprecated. Use Workflow + CheckpointService + "
252
+ "WorkflowResumeService instead.",
253
+ DeprecationWarning,
254
+ stacklevel=2,
255
+ )
205
256
  super().__init__(artifact_dag, rmax, constraints)
206
257
 
207
258
  if checkpoint_dag is None:
@@ -357,7 +408,7 @@ class ResumableWorkflow(Workflow):
357
408
  updated_artifact = replace(
358
409
  human_artifact,
359
410
  status=ArtifactStatus.ACCEPTED,
360
- guard_result=True,
411
+ guard_result=result, # Store full GuardResult
361
412
  )
362
413
  self._dag.store(updated_artifact)
363
414
  self._artifacts[step.guard_id] = updated_artifact
@@ -546,8 +597,7 @@ class ResumableWorkflow(Workflow):
546
597
  created_at=datetime.now(UTC).isoformat(),
547
598
  attempt_number=attempt_number,
548
599
  status=ArtifactStatus.PENDING,
549
- guard_result=None,
550
- feedback="",
600
+ guard_result=None, # Guard result set after validation
551
601
  context=context,
552
602
  source=ArtifactSource.HUMAN,
553
603
  )
@@ -18,6 +18,7 @@ from atomicguard.domain.models import (
18
18
  ContextSnapshot,
19
19
  FeedbackEntry,
20
20
  GuardResult,
21
+ SubGuardOutcome,
21
22
  WorkflowResult,
22
23
  WorkflowState,
23
24
  WorkflowStatus,
@@ -37,6 +38,7 @@ __all__ = [
37
38
  "Context",
38
39
  "AmbientEnvironment",
39
40
  "GuardResult",
41
+ "SubGuardOutcome",
40
42
  "WorkflowState",
41
43
  "WorkflowResult",
42
44
  "WorkflowStatus",