atomicguard 0.1.0__py3-none-any.whl → 1.2.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.
Files changed (37) hide show
  1. atomicguard/__init__.py +8 -3
  2. atomicguard/application/action_pair.py +7 -1
  3. atomicguard/application/agent.py +46 -6
  4. atomicguard/application/workflow.py +494 -11
  5. atomicguard/domain/__init__.py +4 -1
  6. atomicguard/domain/exceptions.py +19 -0
  7. atomicguard/domain/interfaces.py +137 -6
  8. atomicguard/domain/models.py +120 -6
  9. atomicguard/guards/__init__.py +16 -5
  10. atomicguard/guards/composite/__init__.py +11 -0
  11. atomicguard/guards/dynamic/__init__.py +13 -0
  12. atomicguard/guards/dynamic/test_runner.py +207 -0
  13. atomicguard/guards/interactive/__init__.py +11 -0
  14. atomicguard/guards/static/__init__.py +13 -0
  15. atomicguard/guards/static/imports.py +177 -0
  16. atomicguard/infrastructure/__init__.py +4 -1
  17. atomicguard/infrastructure/llm/__init__.py +3 -1
  18. atomicguard/infrastructure/llm/huggingface.py +180 -0
  19. atomicguard/infrastructure/llm/mock.py +32 -6
  20. atomicguard/infrastructure/llm/ollama.py +40 -17
  21. atomicguard/infrastructure/persistence/__init__.py +7 -1
  22. atomicguard/infrastructure/persistence/checkpoint.py +361 -0
  23. atomicguard/infrastructure/persistence/filesystem.py +69 -5
  24. atomicguard/infrastructure/persistence/memory.py +25 -3
  25. atomicguard/infrastructure/registry.py +126 -0
  26. atomicguard/schemas/__init__.py +142 -0
  27. {atomicguard-0.1.0.dist-info → atomicguard-1.2.0.dist-info}/METADATA +75 -13
  28. atomicguard-1.2.0.dist-info/RECORD +37 -0
  29. {atomicguard-0.1.0.dist-info → atomicguard-1.2.0.dist-info}/WHEEL +1 -1
  30. atomicguard-1.2.0.dist-info/entry_points.txt +4 -0
  31. atomicguard/guards/test_runner.py +0 -176
  32. atomicguard-0.1.0.dist-info/RECORD +0 -27
  33. /atomicguard/guards/{base.py → composite/base.py} +0 -0
  34. /atomicguard/guards/{human.py → interactive/human.py} +0 -0
  35. /atomicguard/guards/{syntax.py → static/syntax.py} +0 -0
  36. {atomicguard-0.1.0.dist-info → atomicguard-1.2.0.dist-info}/licenses/LICENSE +0 -0
  37. {atomicguard-0.1.0.dist-info → atomicguard-1.2.0.dist-info}/top_level.txt +0 -0
atomicguard/__init__.py CHANGED
@@ -25,7 +25,7 @@ from atomicguard.application.agent import DualStateAgent
25
25
  from atomicguard.application.workflow import Workflow, WorkflowStep
26
26
 
27
27
  # Domain exceptions
28
- from atomicguard.domain.exceptions import RmaxExhausted
28
+ from atomicguard.domain.exceptions import EscalationRequired, RmaxExhausted
29
29
 
30
30
  # Domain interfaces (for type hints and custom implementations)
31
31
  from atomicguard.domain.interfaces import (
@@ -43,6 +43,7 @@ from atomicguard.domain.models import (
43
43
  GuardResult,
44
44
  WorkflowResult,
45
45
  WorkflowState,
46
+ WorkflowStatus,
46
47
  )
47
48
 
48
49
  # Prompts and tasks (structures only - content defined by calling applications)
@@ -57,6 +58,7 @@ from atomicguard.guards import (
57
58
  CompositeGuard,
58
59
  DynamicTestGuard,
59
60
  HumanReviewGuard,
61
+ ImportGuard,
60
62
  SyntaxGuard,
61
63
  TestGuard,
62
64
  )
@@ -71,7 +73,7 @@ from atomicguard.infrastructure.persistence import (
71
73
  InMemoryArtifactDAG,
72
74
  )
73
75
 
74
- __version__ = "0.1.0"
76
+ __version__ = "1.2.0"
75
77
 
76
78
  __all__ = [
77
79
  # Version
@@ -86,6 +88,7 @@ __all__ = [
86
88
  "GuardResult",
87
89
  "WorkflowState",
88
90
  "WorkflowResult",
91
+ "WorkflowStatus",
89
92
  # Prompts and tasks (structures only)
90
93
  "PromptTemplate",
91
94
  "StepDefinition",
@@ -96,6 +99,7 @@ __all__ = [
96
99
  "ArtifactDAGInterface",
97
100
  # Domain exceptions
98
101
  "RmaxExhausted",
102
+ "EscalationRequired",
99
103
  # Application layer
100
104
  "ActionPair",
101
105
  "DualStateAgent",
@@ -105,11 +109,12 @@ __all__ = [
105
109
  "InMemoryArtifactDAG",
106
110
  "FilesystemArtifactDAG",
107
111
  # Infrastructure - LLM
108
- "OllamaGenerator",
109
112
  "MockGenerator",
113
+ "OllamaGenerator",
110
114
  # Guards
111
115
  "CompositeGuard",
112
116
  "SyntaxGuard",
117
+ "ImportGuard",
113
118
  "TestGuard",
114
119
  "DynamicTestGuard",
115
120
  "HumanReviewGuard",
@@ -48,6 +48,8 @@ class ActionPair:
48
48
  self,
49
49
  context: Context,
50
50
  dependencies: dict[str, Artifact] | None = None,
51
+ action_pair_id: str = "unknown",
52
+ workflow_id: str = "unknown",
51
53
  ) -> tuple[Artifact, GuardResult]:
52
54
  """
53
55
  Execute the atomic generate-then-validate transaction.
@@ -55,11 +57,15 @@ class ActionPair:
55
57
  Args:
56
58
  context: Generation context
57
59
  dependencies: Artifacts from prior workflow steps
60
+ action_pair_id: Identifier for this action pair
61
+ workflow_id: UUID of the workflow execution instance
58
62
 
59
63
  Returns:
60
64
  Tuple of (generated artifact, guard result)
61
65
  """
62
66
  dependencies = dependencies or {}
63
- artifact = self._generator.generate(context, self._prompt_template)
67
+ artifact = self._generator.generate(
68
+ context, self._prompt_template, action_pair_id, workflow_id
69
+ )
64
70
  result = self._guard.validate(artifact, **dependencies)
65
71
  return artifact, result
@@ -5,13 +5,17 @@ Manages only EnvironmentState (the retry loop).
5
5
  WorkflowState is managed by Workflow.
6
6
  """
7
7
 
8
+ from dataclasses import replace
9
+
8
10
  from atomicguard.application.action_pair import ActionPair
9
- from atomicguard.domain.exceptions import RmaxExhausted
11
+ from atomicguard.domain.exceptions import EscalationRequired, RmaxExhausted
10
12
  from atomicguard.domain.interfaces import ArtifactDAGInterface
11
13
  from atomicguard.domain.models import (
12
14
  AmbientEnvironment,
13
15
  Artifact,
16
+ ArtifactStatus,
14
17
  Context,
18
+ FeedbackEntry,
15
19
  )
16
20
 
17
21
 
@@ -33,6 +37,8 @@ class DualStateAgent:
33
37
  artifact_dag: ArtifactDAGInterface,
34
38
  rmax: int = 3,
35
39
  constraints: str = "",
40
+ action_pair_id: str = "unknown",
41
+ workflow_id: str = "unknown",
36
42
  ):
37
43
  """
38
44
  Args:
@@ -40,11 +46,15 @@ class DualStateAgent:
40
46
  artifact_dag: Repository for storing artifacts
41
47
  rmax: Maximum retry attempts (default: 3)
42
48
  constraints: Global constraints for the ambient environment
49
+ action_pair_id: Identifier for this action pair (e.g., 'g_test')
50
+ workflow_id: UUID of the workflow execution instance
43
51
  """
44
52
  self._action_pair = action_pair
45
53
  self._artifact_dag = artifact_dag
46
54
  self._rmax = rmax
47
55
  self._constraints = constraints
56
+ self._action_pair_id = action_pair_id
57
+ self._workflow_id = workflow_id
48
58
 
49
59
  def execute(
50
60
  self,
@@ -69,18 +79,44 @@ class DualStateAgent:
69
79
  context = self._compose_context(specification, dependencies)
70
80
  feedback_history: list[tuple[Artifact, str]] = []
71
81
  retry_count = 0
82
+ previous_id: str | None = None # Track chain linkage
72
83
 
73
84
  while retry_count <= self._rmax:
74
- artifact, result = self._action_pair.execute(context, dependencies)
85
+ artifact, result = self._action_pair.execute(
86
+ context, dependencies, self._action_pair_id, self._workflow_id
87
+ )
88
+
89
+ # Build feedback history for context snapshot
90
+ fb_entries = tuple(
91
+ FeedbackEntry(artifact_id=a.artifact_id, feedback=f)
92
+ for a, f in feedback_history
93
+ )
75
94
 
76
- self._artifact_dag.store(
77
- artifact, metadata="" if result.passed else result.feedback
95
+ # Update artifact with guard result AND provenance metadata
96
+ artifact = replace(
97
+ artifact,
98
+ previous_attempt_id=previous_id,
99
+ status=ArtifactStatus.ACCEPTED
100
+ if result.passed
101
+ else ArtifactStatus.REJECTED,
102
+ guard_result=result.passed,
103
+ feedback=result.feedback,
104
+ context=replace(
105
+ artifact.context,
106
+ feedback_history=fb_entries,
107
+ ),
78
108
  )
109
+ self._artifact_dag.store(artifact)
79
110
 
80
111
  if result.passed:
81
112
  return artifact
113
+ elif result.fatal:
114
+ # Non-recoverable failure - escalate immediately
115
+ raise EscalationRequired(artifact, result.feedback)
82
116
  else:
117
+ # Recoverable failure - retry
83
118
  feedback_history.append((artifact, result.feedback))
119
+ previous_id = artifact.artifact_id # Track for next iteration
84
120
  retry_count += 1
85
121
  context = self._refine_context(
86
122
  specification, artifact, feedback_history, dependencies
@@ -105,7 +141,9 @@ class DualStateAgent:
105
141
  specification=specification,
106
142
  current_artifact=None,
107
143
  feedback_history=(),
108
- dependencies=tuple(dependencies.items()), # Pass to generator
144
+ dependency_artifacts=tuple(
145
+ (k, v.artifact_id) for k, v in dependencies.items()
146
+ ), # Store IDs, not full artifacts
109
147
  )
110
148
 
111
149
  def _refine_context(
@@ -125,5 +163,7 @@ class DualStateAgent:
125
163
  specification=specification,
126
164
  current_artifact=artifact.content,
127
165
  feedback_history=tuple((a.content, f) for a, f in feedback_history),
128
- dependencies=tuple(dependencies.items()), # Preserve dependencies on retry
166
+ dependency_artifacts=tuple(
167
+ (k, v.artifact_id) for k, v in dependencies.items()
168
+ ), # Preserve dependencies on retry
129
169
  )