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.
- {atomicguard-0.1.0/src/atomicguard.egg-info → atomicguard-1.1.0}/PKG-INFO +13 -13
- {atomicguard-0.1.0 → atomicguard-1.1.0}/README.md +9 -2
- {atomicguard-0.1.0 → atomicguard-1.1.0}/pyproject.toml +31 -15
- {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard/__init__.py +8 -3
- {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard/application/action_pair.py +7 -1
- {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard/application/agent.py +46 -6
- atomicguard-1.1.0/src/atomicguard/application/workflow.py +632 -0
- {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard/domain/__init__.py +4 -1
- {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard/domain/exceptions.py +19 -0
- atomicguard-1.1.0/src/atomicguard/domain/interfaces.py +250 -0
- atomicguard-1.1.0/src/atomicguard/domain/models.py +259 -0
- atomicguard-1.1.0/src/atomicguard/guards/__init__.py +30 -0
- atomicguard-1.1.0/src/atomicguard/guards/composite/__init__.py +11 -0
- atomicguard-1.1.0/src/atomicguard/guards/dynamic/__init__.py +13 -0
- atomicguard-1.1.0/src/atomicguard/guards/dynamic/test_runner.py +207 -0
- atomicguard-1.1.0/src/atomicguard/guards/interactive/__init__.py +11 -0
- atomicguard-1.1.0/src/atomicguard/guards/static/__init__.py +13 -0
- atomicguard-1.1.0/src/atomicguard/guards/static/imports.py +177 -0
- {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard/infrastructure/__init__.py +4 -1
- {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard/infrastructure/llm/mock.py +32 -6
- {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard/infrastructure/llm/ollama.py +40 -17
- {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard/infrastructure/persistence/__init__.py +7 -1
- atomicguard-1.1.0/src/atomicguard/infrastructure/persistence/checkpoint.py +361 -0
- {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard/infrastructure/persistence/filesystem.py +69 -5
- {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard/infrastructure/persistence/memory.py +25 -3
- atomicguard-1.1.0/src/atomicguard/infrastructure/registry.py +126 -0
- atomicguard-1.1.0/src/atomicguard/schemas/__init__.py +142 -0
- {atomicguard-0.1.0 → atomicguard-1.1.0/src/atomicguard.egg-info}/PKG-INFO +13 -13
- {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard.egg-info/SOURCES.txt +14 -5
- atomicguard-1.1.0/src/atomicguard.egg-info/entry_points.txt +3 -0
- atomicguard-1.1.0/src/atomicguard.egg-info/requires.txt +4 -0
- atomicguard-0.1.0/src/atomicguard/application/workflow.py +0 -149
- atomicguard-0.1.0/src/atomicguard/domain/interfaces.py +0 -119
- atomicguard-0.1.0/src/atomicguard/domain/models.py +0 -145
- atomicguard-0.1.0/src/atomicguard/guards/__init__.py +0 -19
- atomicguard-0.1.0/src/atomicguard/guards/test_runner.py +0 -176
- atomicguard-0.1.0/src/atomicguard.egg-info/requires.txt +0 -13
- {atomicguard-0.1.0 → atomicguard-1.1.0}/LICENSE +0 -0
- {atomicguard-0.1.0 → atomicguard-1.1.0}/setup.cfg +0 -0
- {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard/application/__init__.py +0 -0
- {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard/domain/prompts.py +0 -0
- {atomicguard-0.1.0/src/atomicguard/guards → atomicguard-1.1.0/src/atomicguard/guards/composite}/base.py +0 -0
- {atomicguard-0.1.0/src/atomicguard/guards → atomicguard-1.1.0/src/atomicguard/guards/interactive}/human.py +0 -0
- {atomicguard-0.1.0/src/atomicguard/guards → atomicguard-1.1.0/src/atomicguard/guards/static}/syntax.py +0 -0
- {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard/infrastructure/llm/__init__.py +1 -1
- {atomicguard-0.1.0 → atomicguard-1.1.0}/src/atomicguard.egg-info/dependency_links.txt +0 -0
- {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:
|
|
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:
|
|
32
|
-
Requires-Dist:
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 = "
|
|
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
|
-
"
|
|
41
|
-
"
|
|
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.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
]
|
|
57
|
-
|
|
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__ = "
|
|
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(
|
|
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(
|
|
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
|
-
|
|
77
|
-
|
|
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
|
-
|
|
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
|
-
|
|
166
|
+
dependency_artifacts=tuple(
|
|
167
|
+
(k, v.artifact_id) for k, v in dependencies.items()
|
|
168
|
+
), # Preserve dependencies on retry
|
|
129
169
|
)
|