atomicguard 0.1.0__tar.gz → 1.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 (47) hide show
  1. {atomicguard-0.1.0/src/atomicguard.egg-info → atomicguard-1.1.0}/PKG-INFO +13 -13
  2. {atomicguard-0.1.0 → atomicguard-1.1.0}/README.md +9 -2
  3. {atomicguard-0.1.0 → atomicguard-1.1.0}/pyproject.toml +31 -15
  4. {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard/__init__.py +8 -3
  5. {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard/application/action_pair.py +7 -1
  6. {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard/application/agent.py +46 -6
  7. atomicguard-1.1.0/src/atomicguard/application/workflow.py +632 -0
  8. {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard/domain/__init__.py +4 -1
  9. {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard/domain/exceptions.py +19 -0
  10. atomicguard-1.1.0/src/atomicguard/domain/interfaces.py +250 -0
  11. atomicguard-1.1.0/src/atomicguard/domain/models.py +259 -0
  12. atomicguard-1.1.0/src/atomicguard/guards/__init__.py +30 -0
  13. atomicguard-1.1.0/src/atomicguard/guards/composite/__init__.py +11 -0
  14. atomicguard-1.1.0/src/atomicguard/guards/dynamic/__init__.py +13 -0
  15. atomicguard-1.1.0/src/atomicguard/guards/dynamic/test_runner.py +207 -0
  16. atomicguard-1.1.0/src/atomicguard/guards/interactive/__init__.py +11 -0
  17. atomicguard-1.1.0/src/atomicguard/guards/static/__init__.py +13 -0
  18. atomicguard-1.1.0/src/atomicguard/guards/static/imports.py +177 -0
  19. {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard/infrastructure/__init__.py +4 -1
  20. {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard/infrastructure/llm/mock.py +32 -6
  21. {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard/infrastructure/llm/ollama.py +40 -17
  22. {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard/infrastructure/persistence/__init__.py +7 -1
  23. atomicguard-1.1.0/src/atomicguard/infrastructure/persistence/checkpoint.py +361 -0
  24. {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard/infrastructure/persistence/filesystem.py +69 -5
  25. {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard/infrastructure/persistence/memory.py +25 -3
  26. atomicguard-1.1.0/src/atomicguard/infrastructure/registry.py +126 -0
  27. atomicguard-1.1.0/src/atomicguard/schemas/__init__.py +142 -0
  28. {atomicguard-0.1.0 → atomicguard-1.1.0/src/atomicguard.egg-info}/PKG-INFO +13 -13
  29. {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard.egg-info/SOURCES.txt +14 -5
  30. atomicguard-1.1.0/src/atomicguard.egg-info/entry_points.txt +3 -0
  31. atomicguard-1.1.0/src/atomicguard.egg-info/requires.txt +4 -0
  32. atomicguard-0.1.0/src/atomicguard/application/workflow.py +0 -149
  33. atomicguard-0.1.0/src/atomicguard/domain/interfaces.py +0 -119
  34. atomicguard-0.1.0/src/atomicguard/domain/models.py +0 -145
  35. atomicguard-0.1.0/src/atomicguard/guards/__init__.py +0 -19
  36. atomicguard-0.1.0/src/atomicguard/guards/test_runner.py +0 -176
  37. atomicguard-0.1.0/src/atomicguard.egg-info/requires.txt +0 -13
  38. {atomicguard-0.1.0 → atomicguard-1.1.0}/LICENSE +0 -0
  39. {atomicguard-0.1.0 → atomicguard-1.1.0}/setup.cfg +0 -0
  40. {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard/application/__init__.py +0 -0
  41. {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard/domain/prompts.py +0 -0
  42. {atomicguard-0.1.0/src/atomicguard/guards → atomicguard-1.1.0/src/atomicguard/guards/composite}/base.py +0 -0
  43. {atomicguard-0.1.0/src/atomicguard/guards → atomicguard-1.1.0/src/atomicguard/guards/interactive}/human.py +0 -0
  44. {atomicguard-0.1.0/src/atomicguard/guards → atomicguard-1.1.0/src/atomicguard/guards/static}/syntax.py +0 -0
  45. {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard/infrastructure/llm/__init__.py +1 -1
  46. {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard.egg-info/dependency_links.txt +0 -0
  47. {atomicguard-0.1.0 → atomicguard-1.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: 0.1.0
3
+ Version: 1.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>
@@ -26,17 +26,10 @@ Classifier: Typing :: Typed
26
26
  Requires-Python: >=3.12
27
27
  Description-Content-Type: text/markdown
28
28
  License-File: LICENSE
29
- Requires-Dist: click>=8.3.1
30
29
  Requires-Dist: matplotlib>=3.10.0
31
- Requires-Dist: openai>=2.12.0
32
- Requires-Dist: rich>=14.0.0
33
- Provides-Extra: dev
34
- Requires-Dist: mypy>=1.13.0; extra == "dev"
35
- Requires-Dist: pre-commit>=4.5.0; extra == "dev"
36
- Requires-Dist: ruff>=0.14.0; extra == "dev"
37
- Provides-Extra: test
38
- Requires-Dist: pytest>=8.0.0; extra == "test"
39
- Requires-Dist: pytest-cov>=6.0.0; extra == "test"
30
+ Requires-Dist: openhands-ai>=0.27.0
31
+ Requires-Dist: pydantic-ai>=1.0.0
32
+ Requires-Dist: pytestarch>=4.0.1
40
33
  Dynamic: license-file
41
34
 
42
35
  # AtomicGuard
@@ -124,13 +117,20 @@ atomicguard/
124
117
 
125
118
  ## Citation
126
119
 
120
+ If you use this framework in your research, please cite the paper:
121
+
122
+ > Thompson, M. (2025). Managing the Stochastic: Foundations of Learning in Neuro-Symbolic Systems for Software Engineering. arXiv preprint arXiv:2512.20660.
123
+
127
124
  ```bibtex
128
125
  @article{thompson2025managing,
129
126
  title={Managing the Stochastic: Foundations of Learning in Neuro-Symbolic Systems for Software Engineering},
130
127
  author={Thompson, Matthew},
131
- year={2025}
128
+ journal={arXiv preprint arXiv:2512.20660},
129
+ year={2025},
130
+ url={[https://arxiv.org/abs/2512.20660](https://arxiv.org/abs/2512.20660)}
132
131
  }
133
- ```
132
+
133
+ ```
134
134
 
135
135
  ## License
136
136
 
@@ -83,13 +83,20 @@ atomicguard/
83
83
 
84
84
  ## Citation
85
85
 
86
+ If you use this framework in your research, please cite the paper:
87
+
88
+ > Thompson, M. (2025). Managing the Stochastic: Foundations of Learning in Neuro-Symbolic Systems for Software Engineering. arXiv preprint arXiv:2512.20660.
89
+
86
90
  ```bibtex
87
91
  @article{thompson2025managing,
88
92
  title={Managing the Stochastic: Foundations of Learning in Neuro-Symbolic Systems for Software Engineering},
89
93
  author={Thompson, Matthew},
90
- year={2025}
94
+ journal={arXiv preprint arXiv:2512.20660},
95
+ year={2025},
96
+ url={[https://arxiv.org/abs/2512.20660](https://arxiv.org/abs/2512.20660)}
91
97
  }
92
- ```
98
+
99
+ ```
93
100
 
94
101
  ## License
95
102
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "atomicguard"
3
- version = "0.1.0"
3
+ version = "1.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" }
@@ -35,10 +35,10 @@ classifiers = [
35
35
  "Typing :: Typed",
36
36
  ]
37
37
  dependencies = [
38
- "click>=8.3.1",
39
38
  "matplotlib>=3.10.0",
40
- "openai>=2.12.0",
41
- "rich>=14.0.0",
39
+ "openhands-ai>=0.27.0",
40
+ "pydantic-ai>=1.0.0",
41
+ "pytestarch>=4.0.1",
42
42
  ]
43
43
 
44
44
  [project.urls]
@@ -48,16 +48,13 @@ Documentation = "https://github.com/thompsonson/atomicguard#readme"
48
48
  Issues = "https://github.com/thompsonson/atomicguard/issues"
49
49
  Changelog = "https://github.com/thompsonson/atomicguard/blob/main/CHANGELOG.md"
50
50
 
51
- [project.optional-dependencies]
52
- dev = [
53
- "mypy>=1.13.0",
54
- "pre-commit>=4.5.0",
55
- "ruff>=0.14.0",
56
- ]
57
- test = [
58
- "pytest>=8.0.0",
59
- "pytest-cov>=6.0.0",
60
- ]
51
+ [project.entry-points."atomicguard.generators"]
52
+ # Core generators
53
+ OllamaGenerator = "atomicguard.infrastructure.llm:OllamaGenerator"
54
+ MockGenerator = "atomicguard.infrastructure.llm:MockGenerator"
55
+
56
+ # Note: Dev dependencies are in [dependency-groups] below, not here.
57
+ # [project.optional-dependencies] is for end-user extras only.
61
58
 
62
59
  [build-system]
63
60
  requires = ["setuptools>=75.0.0", "wheel"]
@@ -91,7 +88,6 @@ prerelease = false
91
88
 
92
89
  [tool.semantic_release.remote]
93
90
  type = "github"
94
- token = { env = "GH_TOKEN" }
95
91
 
96
92
  # =============================================================================
97
93
  # Testing
@@ -184,3 +180,23 @@ mypy_path = "src"
184
180
  [[tool.mypy.overrides]]
185
181
  module = "tests.*"
186
182
  disallow_untyped_defs = false
183
+
184
+ [dependency-groups]
185
+ dev = [
186
+ "mypy>=1.19.1",
187
+ "pre-commit>=4.5.0",
188
+ "python-semantic-release>=9.21.1",
189
+ "types-jsonschema>=4.25.1.20251009",
190
+ "ruff>=0.14.9",
191
+ "types-jsonschema>=4.25.1.20251009",
192
+ ]
193
+ test = [
194
+ "pytest>=8.0.0",
195
+ "pytest-cov>=6.0.0",
196
+ ]
197
+ examples = [
198
+ "click>=8.1.0",
199
+ "gradio>=4.0.0",
200
+ "rich>=13.0.0",
201
+ "typer>=0.9.0",
202
+ ]
@@ -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.1.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
  )