atomicguard 1.1.0__py3-none-any.whl → 2.0.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 +4 -3
- atomicguard/application/__init__.py +5 -0
- atomicguard/application/action_pair.py +2 -2
- atomicguard/application/agent.py +2 -3
- atomicguard/application/checkpoint_service.py +130 -0
- atomicguard/application/resume_service.py +242 -0
- atomicguard/application/workflow.py +53 -3
- atomicguard/domain/__init__.py +2 -0
- atomicguard/domain/extraction.py +257 -0
- atomicguard/domain/interfaces.py +17 -2
- atomicguard/domain/models.py +91 -13
- atomicguard/domain/multiagent.py +279 -0
- atomicguard/domain/prompts.py +12 -1
- atomicguard/domain/workflow.py +642 -0
- atomicguard/infrastructure/llm/__init__.py +2 -0
- atomicguard/infrastructure/llm/huggingface.py +162 -0
- atomicguard/infrastructure/llm/mock.py +5 -4
- atomicguard/infrastructure/llm/ollama.py +5 -23
- atomicguard/infrastructure/persistence/filesystem.py +133 -46
- atomicguard/infrastructure/persistence/memory.py +10 -0
- {atomicguard-1.1.0.dist-info → atomicguard-2.0.0.dist-info}/METADATA +68 -6
- atomicguard-2.0.0.dist-info/RECORD +42 -0
- {atomicguard-1.1.0.dist-info → atomicguard-2.0.0.dist-info}/WHEEL +1 -1
- {atomicguard-1.1.0.dist-info → atomicguard-2.0.0.dist-info}/entry_points.txt +1 -0
- atomicguard-1.1.0.dist-info/RECORD +0 -36
- {atomicguard-1.1.0.dist-info → atomicguard-2.0.0.dist-info}/licenses/LICENSE +0 -0
- {atomicguard-1.1.0.dist-info → atomicguard-2.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Multi-Agent System (Extension 03: Definitions 19-20).
|
|
3
|
+
|
|
4
|
+
Implements:
|
|
5
|
+
- MultiAgentSystem: MAS = ⟨{Ag₁, ..., Agₙ}, ℛ, G⟩
|
|
6
|
+
- AgentState: σᵢ: G → {⊥, ⊤} - Agent's belief about workflow progress
|
|
7
|
+
|
|
8
|
+
All coordination happens through the shared repository (ℛ).
|
|
9
|
+
Agents do not communicate directly - they read/write artifacts.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import TYPE_CHECKING, Any
|
|
15
|
+
|
|
16
|
+
from atomicguard.domain.extraction import (
|
|
17
|
+
AndPredicate,
|
|
18
|
+
StatusPredicate,
|
|
19
|
+
WorkflowPredicate,
|
|
20
|
+
extract,
|
|
21
|
+
)
|
|
22
|
+
from atomicguard.domain.models import (
|
|
23
|
+
AmbientEnvironment,
|
|
24
|
+
ArtifactStatus,
|
|
25
|
+
Context,
|
|
26
|
+
GuardResult,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from atomicguard.domain.interfaces import ArtifactDAGInterface, GuardInterface
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# =============================================================================
|
|
34
|
+
# AGENT STATE (Definition 20)
|
|
35
|
+
# =============================================================================
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class AgentState:
|
|
39
|
+
"""
|
|
40
|
+
Agent-local state σᵢ: G → {⊥, ⊤} (Definition 20).
|
|
41
|
+
|
|
42
|
+
Value object derived from repository state.
|
|
43
|
+
No stored state - always computed from artifacts via extraction.
|
|
44
|
+
|
|
45
|
+
The agent's belief about which workflow steps are complete is
|
|
46
|
+
derived from the presence of ACCEPTED artifacts in the shared repository.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(self, agent_id: str, repository: ArtifactDAGInterface) -> None:
|
|
50
|
+
"""Initialize agent state view.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
agent_id: The workflow_id for this agent.
|
|
54
|
+
repository: Shared artifact repository.
|
|
55
|
+
"""
|
|
56
|
+
self._agent_id = agent_id
|
|
57
|
+
self._repository = repository
|
|
58
|
+
|
|
59
|
+
def is_step_complete(self, action_pair_id: str) -> bool:
|
|
60
|
+
"""Check if step g has an ACCEPTED artifact.
|
|
61
|
+
|
|
62
|
+
σᵢ(g) = ⊤ when there exists an ACCEPTED artifact for this
|
|
63
|
+
agent's workflow at the given action_pair_id.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
action_pair_id: The step/guard identifier to check.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
True if an ACCEPTED artifact exists, False otherwise.
|
|
70
|
+
"""
|
|
71
|
+
from atomicguard.domain.extraction import ActionPairPredicate
|
|
72
|
+
|
|
73
|
+
# Build predicate: workflow_id matches AND action_pair matches AND status is ACCEPTED
|
|
74
|
+
predicate = AndPredicate(
|
|
75
|
+
WorkflowPredicate(self._agent_id),
|
|
76
|
+
AndPredicate(
|
|
77
|
+
ActionPairPredicate(action_pair_id),
|
|
78
|
+
StatusPredicate(ArtifactStatus.ACCEPTED),
|
|
79
|
+
),
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Extract matching artifacts
|
|
83
|
+
results = extract(self._repository, predicate, limit=1)
|
|
84
|
+
|
|
85
|
+
return len(results) > 0
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# =============================================================================
|
|
89
|
+
# AGENT RUNNER (Minimal interface for MAS)
|
|
90
|
+
# =============================================================================
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class AgentRunner:
|
|
94
|
+
"""
|
|
95
|
+
Minimal runner interface for MAS agents.
|
|
96
|
+
|
|
97
|
+
Provides execute_step method expected by tests.
|
|
98
|
+
The actual execution follows Definition 7 dynamics.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
def __init__(
|
|
102
|
+
self,
|
|
103
|
+
agent_id: str,
|
|
104
|
+
workflow: dict[str, Any],
|
|
105
|
+
repository: ArtifactDAGInterface,
|
|
106
|
+
guards: dict[str, GuardInterface],
|
|
107
|
+
) -> None:
|
|
108
|
+
"""Initialize agent runner.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
agent_id: The workflow_id for this agent.
|
|
112
|
+
workflow: Workflow definition dict with steps.
|
|
113
|
+
repository: Shared artifact repository.
|
|
114
|
+
guards: Shared guard library.
|
|
115
|
+
"""
|
|
116
|
+
self._agent_id = agent_id
|
|
117
|
+
self._workflow = workflow
|
|
118
|
+
self._repository = repository
|
|
119
|
+
self._guards = guards
|
|
120
|
+
|
|
121
|
+
def execute_step(self, step_id: str) -> None:
|
|
122
|
+
"""Execute a single workflow step.
|
|
123
|
+
|
|
124
|
+
Follows Definition 7 dynamics:
|
|
125
|
+
1. Check preconditions
|
|
126
|
+
2. Generate artifact
|
|
127
|
+
3. Validate with guard
|
|
128
|
+
4. Store in repository
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
step_id: The step/guard identifier to execute.
|
|
132
|
+
"""
|
|
133
|
+
# This is a placeholder for the protocol
|
|
134
|
+
# Actual execution would use DualStateAgent or similar
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# =============================================================================
|
|
139
|
+
# MULTI-AGENT SYSTEM (Definition 19)
|
|
140
|
+
# =============================================================================
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class MultiAgentSystem:
|
|
144
|
+
"""
|
|
145
|
+
Multi-Agent System MAS = ⟨{Ag₁, ..., Agₙ}, ℛ, G⟩ (Definition 19).
|
|
146
|
+
|
|
147
|
+
Aggregate root for multi-agent coordination.
|
|
148
|
+
All coordination happens through the shared repository.
|
|
149
|
+
|
|
150
|
+
Key invariants:
|
|
151
|
+
- Agents communicate only through ℛ (no direct messages)
|
|
152
|
+
- Guards are deterministic: G(r) same for all agents
|
|
153
|
+
- Agent state σᵢ is derived from ℛ, not stored separately
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
def __init__(
|
|
157
|
+
self,
|
|
158
|
+
repository: ArtifactDAGInterface,
|
|
159
|
+
guards: dict[str, GuardInterface] | None = None,
|
|
160
|
+
agents: list[dict[str, Any]] | None = None,
|
|
161
|
+
) -> None:
|
|
162
|
+
"""Initialize Multi-Agent System.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
repository: Shared artifact repository ℛ.
|
|
166
|
+
guards: Shared guard library G (guard_name -> GuardInterface).
|
|
167
|
+
agents: Collection of workflow definitions (agent configurations).
|
|
168
|
+
"""
|
|
169
|
+
self._repository = repository
|
|
170
|
+
self._guards = guards or {}
|
|
171
|
+
self._agents: list[dict[str, Any]] = agents or []
|
|
172
|
+
self._agent_index: dict[str, dict[str, Any]] = {}
|
|
173
|
+
|
|
174
|
+
# Index agents by id for fast lookup
|
|
175
|
+
for agent in self._agents:
|
|
176
|
+
agent_id = agent.get("id")
|
|
177
|
+
if agent_id:
|
|
178
|
+
self._agent_index[agent_id] = agent
|
|
179
|
+
|
|
180
|
+
@property
|
|
181
|
+
def repository(self) -> ArtifactDAGInterface:
|
|
182
|
+
"""Get shared repository ℛ."""
|
|
183
|
+
return self._repository
|
|
184
|
+
|
|
185
|
+
@property
|
|
186
|
+
def guards(self) -> dict[str, GuardInterface]:
|
|
187
|
+
"""Get shared guard library G."""
|
|
188
|
+
return self._guards
|
|
189
|
+
|
|
190
|
+
@property
|
|
191
|
+
def agents(self) -> list[dict[str, Any]]:
|
|
192
|
+
"""Get registered agent workflows."""
|
|
193
|
+
return self._agents
|
|
194
|
+
|
|
195
|
+
def register_agent(self, workflow: dict[str, Any]) -> None:
|
|
196
|
+
"""Register an agent workflow with the MAS.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
workflow: Workflow definition dict with 'id' and 'steps'.
|
|
200
|
+
"""
|
|
201
|
+
self._agents.append(workflow)
|
|
202
|
+
agent_id = workflow.get("id")
|
|
203
|
+
if agent_id:
|
|
204
|
+
self._agent_index[agent_id] = workflow
|
|
205
|
+
|
|
206
|
+
def get_agent_state(self, agent_id: str) -> AgentState:
|
|
207
|
+
"""Get agent's derived state σᵢ.
|
|
208
|
+
|
|
209
|
+
State is computed from repository, not stored.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
agent_id: The workflow_id for the agent.
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
AgentState view for this agent.
|
|
216
|
+
"""
|
|
217
|
+
return AgentState(agent_id, self._repository)
|
|
218
|
+
|
|
219
|
+
def get_agent_runner(self, agent_id: str) -> AgentRunner:
|
|
220
|
+
"""Get runner for executing agent workflow steps.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
agent_id: The workflow_id for the agent.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
AgentRunner for executing workflow steps.
|
|
227
|
+
"""
|
|
228
|
+
workflow = self._agent_index.get(agent_id, {"id": agent_id, "steps": []})
|
|
229
|
+
return AgentRunner(agent_id, workflow, self._repository, self._guards)
|
|
230
|
+
|
|
231
|
+
def create_agent_context(self, agent_id: str) -> Context:
|
|
232
|
+
"""Create execution context for an agent.
|
|
233
|
+
|
|
234
|
+
Each agent gets an independent context with its own workflow_id.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
agent_id: The workflow_id for the agent.
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
Context configured for this agent.
|
|
241
|
+
"""
|
|
242
|
+
ambient = AmbientEnvironment(
|
|
243
|
+
repository=self._repository,
|
|
244
|
+
constraints="",
|
|
245
|
+
)
|
|
246
|
+
return Context(
|
|
247
|
+
ambient=ambient,
|
|
248
|
+
specification="",
|
|
249
|
+
workflow_id=agent_id,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
def evaluate_guard(
|
|
253
|
+
self, _agent_id: str, guard_name: str, artifact_id: str
|
|
254
|
+
) -> GuardResult:
|
|
255
|
+
"""Evaluate a guard on an artifact.
|
|
256
|
+
|
|
257
|
+
Guards are deterministic: same artifact → same result,
|
|
258
|
+
regardless of which agent evaluates (Theorem 6).
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
_agent_id: The agent requesting evaluation (unused, for API consistency).
|
|
262
|
+
guard_name: Name of guard in the guard library.
|
|
263
|
+
artifact_id: ID of artifact to validate.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
GuardResult from validation.
|
|
267
|
+
|
|
268
|
+
Raises:
|
|
269
|
+
KeyError: If guard_name not in library or artifact not found.
|
|
270
|
+
"""
|
|
271
|
+
if guard_name not in self._guards:
|
|
272
|
+
raise KeyError(f"Guard '{guard_name}' not found in library")
|
|
273
|
+
|
|
274
|
+
artifact = self._repository.get_artifact(artifact_id)
|
|
275
|
+
if artifact is None:
|
|
276
|
+
raise KeyError(f"Artifact '{artifact_id}' not found")
|
|
277
|
+
|
|
278
|
+
guard = self._guards[guard_name]
|
|
279
|
+
return guard.validate(artifact)
|
atomicguard/domain/prompts.py
CHANGED
|
@@ -34,7 +34,7 @@ class PromptTemplate:
|
|
|
34
34
|
)
|
|
35
35
|
|
|
36
36
|
def render(self, context: "Context") -> str:
|
|
37
|
-
"""Render prompt with context."""
|
|
37
|
+
"""Render prompt with context including dependency artifacts."""
|
|
38
38
|
parts = [
|
|
39
39
|
f"# ROLE\n{self.role}",
|
|
40
40
|
f"# CONSTRAINTS\n{self.constraints}",
|
|
@@ -43,6 +43,17 @@ class PromptTemplate:
|
|
|
43
43
|
if context.ambient.constraints:
|
|
44
44
|
parts.append(f"# CONTEXT\n{context.ambient.constraints}")
|
|
45
45
|
|
|
46
|
+
if context.dependency_artifacts and context.ambient.repository:
|
|
47
|
+
dep_parts = []
|
|
48
|
+
for key, artifact_id in context.dependency_artifacts:
|
|
49
|
+
try:
|
|
50
|
+
artifact = context.ambient.repository.get_artifact(artifact_id)
|
|
51
|
+
dep_parts.append(f"## {key}\n{artifact.content}")
|
|
52
|
+
except (KeyError, Exception):
|
|
53
|
+
pass
|
|
54
|
+
if dep_parts:
|
|
55
|
+
parts.append("# DEPENDENCIES\n" + "\n\n".join(dep_parts))
|
|
56
|
+
|
|
46
57
|
if context.feedback_history:
|
|
47
58
|
parts.append("# HISTORY (Context Refinement)")
|
|
48
59
|
for i, (_artifact_content, feedback) in enumerate(context.feedback_history):
|