multiagentos 0.4.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.
- agents/__init__.py +1 -0
- agents/catalog.py +52 -0
- agents/registry.py +11 -0
- core/__init__.py +38 -0
- core/artifacts.py +47 -0
- core/chat_agent_bridge.py +280 -0
- core/chat_agent_registry.py +61 -0
- core/chat_agent_router.py +85 -0
- core/chat_session.py +62 -0
- core/contracts/__init__.py +22 -0
- core/contracts/agent.py +21 -0
- core/contracts/ai.py +25 -0
- core/contracts/capability.py +44 -0
- core/contracts/chat_agent.py +34 -0
- core/contracts/checkpoint.py +78 -0
- core/contracts/discovery.py +30 -0
- core/contracts/execution.py +28 -0
- core/contracts/github.py +74 -0
- core/contracts/handoff.py +56 -0
- core/contracts/health.py +206 -0
- core/contracts/human_review.py +20 -0
- core/contracts/ide.py +113 -0
- core/contracts/mcp.py +76 -0
- core/contracts/model_runtime.py +25 -0
- core/contracts/planning.py +25 -0
- core/contracts/profile.py +66 -0
- core/contracts/quota.py +62 -0
- core/contracts/repository.py +34 -0
- core/contracts/resume.py +55 -0
- core/contracts/runtime.py +19 -0
- core/contracts/vyrelon_runtime.py +118 -0
- core/contracts/work_unit.py +48 -0
- core/delegation.py +33 -0
- core/handoff.py +68 -0
- core/lifecycle.py +85 -0
- core/multi_agent_workflow.py +574 -0
- core/orchestrator.py +51 -0
- core/planning.py +22 -0
- core/registry.py +44 -0
- core/routing.py +195 -0
- core/state.py +155 -0
- installer/__init__.py +1 -0
- installer/init.py +71 -0
- integrations/__init__.py +0 -0
- integrations/github/__init__.py +3 -0
- integrations/github/gateway.py +164 -0
- integrations/openai/__init__.py +1 -0
- integrations/openai/chat_agent.py +147 -0
- multiagentos/__init__.py +1 -0
- multiagentos/cli.py +759 -0
- multiagentos-0.4.0.dist-info/METADATA +7 -0
- multiagentos-0.4.0.dist-info/RECORD +122 -0
- multiagentos-0.4.0.dist-info/WHEEL +5 -0
- multiagentos-0.4.0.dist-info/entry_points.txt +2 -0
- multiagentos-0.4.0.dist-info/top_level.txt +7 -0
- profiles/__init__.py +1 -0
- profiles/common.py +11 -0
- profiles/detector.py +67 -0
- profiles/registry.py +46 -0
- profiles/resolver.py +63 -0
- runtime/__init__.py +5 -0
- runtime/agent/__init__.py +1 -0
- runtime/agent/ide.py +74 -0
- runtime/agent/model.py +197 -0
- runtime/agent/model_backed.py +49 -0
- runtime/agent/process.py +21 -0
- runtime/builtin_tools.py +57 -0
- runtime/capability.py +143 -0
- runtime/chat_adapter_registry.py +43 -0
- runtime/chat_config.py +57 -0
- runtime/chat_execution.py +52 -0
- runtime/config/__init__.py +1 -0
- runtime/config/loader.py +67 -0
- runtime/discovery.py +334 -0
- runtime/execution_config.py +71 -0
- runtime/execution_registry.py +50 -0
- runtime/git.py +84 -0
- runtime/github.py +56 -0
- runtime/github_probe.py +23 -0
- runtime/health.py +14 -0
- runtime/ide/__init__.py +6 -0
- runtime/ide/bridge.py +198 -0
- runtime/ide/registry.py +24 -0
- runtime/ide/runtime.py +35 -0
- runtime/local/__init__.py +18 -0
- runtime/local/filesystem.py +58 -0
- runtime/local/patch.py +79 -0
- runtime/local/path_security.py +31 -0
- runtime/local/permissions.py +20 -0
- runtime/local/process.py +56 -0
- runtime/local/shell.py +97 -0
- runtime/mcp/__init__.py +7 -0
- runtime/mcp/client.py +120 -0
- runtime/mcp/config.py +36 -0
- runtime/mcp/policy.py +57 -0
- runtime/mcp/proxy.py +30 -0
- runtime/mcp/server.py +137 -0
- runtime/mcp/session.py +19 -0
- runtime/model/__init__.py +5 -0
- runtime/model/ai_runtime.py +165 -0
- runtime/model/cli.py +49 -0
- runtime/model/config.py +98 -0
- runtime/model/credentials.py +42 -0
- runtime/model/factory.py +82 -0
- runtime/model/http.py +78 -0
- runtime/model/invoker.py +16 -0
- runtime/model/materializer.py +101 -0
- runtime/model/native.py +244 -0
- runtime/model/providers.py +55 -0
- runtime/model/registry.py +26 -0
- runtime/model/runtime_loader.py +51 -0
- runtime/model_control.py +136 -0
- runtime/multi_agent.py +142 -0
- runtime/policy.py +28 -0
- runtime/process.py +41 -0
- runtime/quota.py +259 -0
- runtime/repository.py +103 -0
- runtime/repository_tools.py +62 -0
- runtime/status.py +87 -0
- runtime/tool_calling.py +134 -0
- runtime/validation.py +121 -0
- runtime/vyrelon.py +1557 -0
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
"""Sequential multi-agent handoff workflow for VYRELON."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from core.contracts.agent import AgentContract
|
|
8
|
+
from core.contracts.ai import ModelSpec
|
|
9
|
+
from core.contracts.execution import AgentExecutor, ResultReviewer, ResultVerifier
|
|
10
|
+
from core.contracts.handoff import ArtifactContract, HandoffArtifact, ReviewContext, ReviewResult
|
|
11
|
+
from core.contracts.work_unit import WorkStatus, WorkUnit
|
|
12
|
+
from core.contracts.resume import WorkflowResumeContext
|
|
13
|
+
from core.delegation import Delegation, DelegationEngine
|
|
14
|
+
from core.handoff import HandoffManager, ReviewPanel
|
|
15
|
+
from core.routing import RoutingStrategy
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _checkpoint_value(value: object) -> object:
|
|
19
|
+
"""Return a JSON-safe execution snapshot for durable recovery."""
|
|
20
|
+
if value is None or isinstance(value, (str, int, float, bool)):
|
|
21
|
+
return value
|
|
22
|
+
if isinstance(value, dict):
|
|
23
|
+
return {str(key): _checkpoint_value(item) for key, item in value.items()}
|
|
24
|
+
if isinstance(value, (list, tuple)):
|
|
25
|
+
return [_checkpoint_value(item) for item in value]
|
|
26
|
+
return {"repr": repr(value), "type": type(value).__name__}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class AgentStageResult:
|
|
31
|
+
agent_id: str
|
|
32
|
+
delegation: Delegation
|
|
33
|
+
output: object
|
|
34
|
+
handoff: HandoffArtifact | None = None
|
|
35
|
+
artifacts: tuple[ArtifactContract, ...] = ()
|
|
36
|
+
findings: tuple[str, ...] = ()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
|
+
class MultiAgentWorkflowResult:
|
|
41
|
+
work_unit: WorkUnit
|
|
42
|
+
stages: tuple[AgentStageResult, ...]
|
|
43
|
+
final_output: object
|
|
44
|
+
reviews: tuple[ReviewResult, ...] = ()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class MultiAgentWorkflow:
|
|
48
|
+
"""Run one WorkUnit through cooperating agents under VYRELON authority.
|
|
49
|
+
|
|
50
|
+
Agents never transfer execution authority directly to one another. VYRELON
|
|
51
|
+
performs each delegation and records the handoff artifact between stages.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
delegation: DelegationEngine | None = None,
|
|
57
|
+
handoffs: HandoffManager | None = None,
|
|
58
|
+
review_panel: ReviewPanel | None = None,
|
|
59
|
+
) -> None:
|
|
60
|
+
self.delegation = delegation or DelegationEngine()
|
|
61
|
+
self.handoffs = handoffs or HandoffManager()
|
|
62
|
+
self.review_panel = review_panel or ReviewPanel()
|
|
63
|
+
|
|
64
|
+
def run_with_debug_retry(
|
|
65
|
+
self,
|
|
66
|
+
*,
|
|
67
|
+
work_unit: WorkUnit,
|
|
68
|
+
developer: AgentContract,
|
|
69
|
+
tester: AgentContract,
|
|
70
|
+
debugger: AgentContract,
|
|
71
|
+
models: list[ModelSpec],
|
|
72
|
+
executor: AgentExecutor,
|
|
73
|
+
verifier: ResultVerifier,
|
|
74
|
+
max_retries: int = 2,
|
|
75
|
+
preferred_model_ids: list[str] | None = None,
|
|
76
|
+
routing_strategy: RoutingStrategy | str = RoutingStrategy.POOL,
|
|
77
|
+
artifact_store=None,
|
|
78
|
+
) -> MultiAgentWorkflowResult:
|
|
79
|
+
if max_retries < 0:
|
|
80
|
+
raise ValueError("max_retries must be >= 0")
|
|
81
|
+
stages: list[AgentStageResult] = []
|
|
82
|
+
previous_output = None
|
|
83
|
+
for attempt in range(max_retries + 1):
|
|
84
|
+
agent = developer if attempt == 0 else debugger
|
|
85
|
+
delegation = self.delegation.delegate(work_unit, agent, models, preferred_model_ids, routing_strategy)
|
|
86
|
+
output = executor.execute(agent=agent, model_id=delegation.assignment.model_id, work_unit=work_unit)
|
|
87
|
+
stages.append(AgentStageResult(agent.id, delegation, output))
|
|
88
|
+
tester_delegation = self.delegation.delegate(
|
|
89
|
+
work_unit, tester, models, preferred_model_ids, routing_strategy
|
|
90
|
+
)
|
|
91
|
+
previous_output = executor.execute(
|
|
92
|
+
agent=tester,
|
|
93
|
+
model_id=tester_delegation.assignment.model_id,
|
|
94
|
+
work_unit=work_unit,
|
|
95
|
+
)
|
|
96
|
+
stages.append(
|
|
97
|
+
AgentStageResult(tester.id, tester_delegation, previous_output)
|
|
98
|
+
)
|
|
99
|
+
work_unit.transition(WorkStatus.VERIFYING)
|
|
100
|
+
if verifier.verify(work_unit=work_unit, output=previous_output):
|
|
101
|
+
work_unit.transition(WorkStatus.HANDOFF)
|
|
102
|
+
work_unit.transition(WorkStatus.COMPLETED)
|
|
103
|
+
work_unit.metadata["retry_count"] = attempt
|
|
104
|
+
return MultiAgentWorkflowResult(work_unit, tuple(stages), previous_output)
|
|
105
|
+
work_unit.metadata.setdefault("findings", []).append(f"verification failed on attempt {attempt + 1}")
|
|
106
|
+
if attempt < max_retries:
|
|
107
|
+
work_unit.transition(WorkStatus.EXECUTING)
|
|
108
|
+
work_unit.metadata["stage_input"] = {
|
|
109
|
+
"from_agent": tester.id,
|
|
110
|
+
"artifacts": tuple(work_unit.artifacts),
|
|
111
|
+
"findings": tuple(work_unit.metadata.get("findings", ())),
|
|
112
|
+
}
|
|
113
|
+
work_unit.transition(WorkStatus.FAILED)
|
|
114
|
+
work_unit.metadata["retry_count"] = max_retries + 1
|
|
115
|
+
raise RuntimeError(f"verification failed after {max_retries + 1} attempts for work unit: {work_unit.id}")
|
|
116
|
+
def run_with_review_rework(
|
|
117
|
+
self,
|
|
118
|
+
*,
|
|
119
|
+
work_unit: WorkUnit,
|
|
120
|
+
developer: AgentContract,
|
|
121
|
+
tester: AgentContract,
|
|
122
|
+
reviewers: list[tuple[AgentContract, object]],
|
|
123
|
+
models: list[ModelSpec],
|
|
124
|
+
executor: AgentExecutor,
|
|
125
|
+
reviewer_runner,
|
|
126
|
+
verifier: ResultVerifier | None = None,
|
|
127
|
+
max_review_cycles: int = 2,
|
|
128
|
+
preferred_model_ids: list[str] | None = None,
|
|
129
|
+
routing_strategy: RoutingStrategy | str = RoutingStrategy.POOL,
|
|
130
|
+
artifact_store=None,
|
|
131
|
+
checkpoint=None,
|
|
132
|
+
start_cycle: int = 0,
|
|
133
|
+
resume_action: str | None = None,
|
|
134
|
+
resume_output: object = None,
|
|
135
|
+
) -> MultiAgentWorkflowResult:
|
|
136
|
+
"""Run Developer -> Tester -> Review and bounded reviewer-requested rework."""
|
|
137
|
+
if max_review_cycles < 1:
|
|
138
|
+
raise ValueError("max_review_cycles must be >= 1")
|
|
139
|
+
if not reviewers:
|
|
140
|
+
raise ValueError("reviewers are required")
|
|
141
|
+
if reviewer_runner is None:
|
|
142
|
+
raise ValueError("reviewer_runner is required")
|
|
143
|
+
|
|
144
|
+
if start_cycle < 0 or start_cycle > max_review_cycles:
|
|
145
|
+
raise ValueError("start_cycle must be within review cycle bounds")
|
|
146
|
+
if resume_action not in {None, "verify", "review", "rework"}:
|
|
147
|
+
raise ValueError("unsupported review-rework resume action")
|
|
148
|
+
if resume_action in {"verify", "review"} and start_cycle >= max_review_cycles:
|
|
149
|
+
raise ValueError("resume cycle must be within review cycle bounds")
|
|
150
|
+
|
|
151
|
+
stages: list[AgentStageResult] = []
|
|
152
|
+
reviews: list[ReviewResult] = []
|
|
153
|
+
previous_agent: AgentContract | None = tester if resume_action in {"verify", "review"} else None
|
|
154
|
+
previous_output: object = resume_output if resume_action in {"verify", "review"} else None
|
|
155
|
+
|
|
156
|
+
for cycle in range(start_cycle, max_review_cycles):
|
|
157
|
+
work_unit.metadata["review_cycle"] = cycle + 1
|
|
158
|
+
work_unit.metadata["checkpoint_next_action"] = "rework"
|
|
159
|
+
work_unit.metadata["resume_context"] = WorkflowResumeContext(
|
|
160
|
+
workflow="review_rework",
|
|
161
|
+
work_unit_id=work_unit.id,
|
|
162
|
+
review_cycle=cycle + 1,
|
|
163
|
+
max_review_cycles=max_review_cycles,
|
|
164
|
+
developer_id=developer.id,
|
|
165
|
+
tester_id=tester.id,
|
|
166
|
+
reviewer_ids=tuple(agent.id for agent, _ in reviewers),
|
|
167
|
+
model_ids=tuple(model.id for model in models),
|
|
168
|
+
artifact_ids=tuple(work_unit.artifacts),
|
|
169
|
+
findings=tuple(work_unit.metadata.get("findings", ())),
|
|
170
|
+
last_agent_id=previous_agent.id if previous_agent else None,
|
|
171
|
+
).to_metadata()
|
|
172
|
+
if checkpoint is not None:
|
|
173
|
+
checkpoint(work_unit, stage="rework", sequence=cycle,
|
|
174
|
+
next_action="rework", agent_ids=(developer.id, tester.id, *[agent.id for agent, _ in reviewers]),
|
|
175
|
+
model_ids=tuple(model.id for model in models))
|
|
176
|
+
if resume_action not in {"verify", "review"}:
|
|
177
|
+
for agent in (developer, tester):
|
|
178
|
+
work_unit.metadata["stage_input"] = {
|
|
179
|
+
"from_agent": previous_agent.id if previous_agent else None,
|
|
180
|
+
"artifacts": tuple(work_unit.artifacts),
|
|
181
|
+
"findings": tuple(work_unit.metadata.get("findings", ())),
|
|
182
|
+
"review_cycle": cycle + 1,
|
|
183
|
+
}
|
|
184
|
+
delegation = self.delegation.delegate(
|
|
185
|
+
work_unit, agent, models, preferred_model_ids, routing_strategy
|
|
186
|
+
)
|
|
187
|
+
output = executor.execute(
|
|
188
|
+
agent=agent,
|
|
189
|
+
model_id=delegation.assignment.model_id,
|
|
190
|
+
work_unit=work_unit,
|
|
191
|
+
)
|
|
192
|
+
stage_findings = tuple(work_unit.metadata.get("stage_findings", ()))
|
|
193
|
+
stage_artifacts = tuple(
|
|
194
|
+
artifact for artifact in work_unit.metadata.get("artifacts", ())
|
|
195
|
+
if isinstance(artifact, ArtifactContract)
|
|
196
|
+
)
|
|
197
|
+
for artifact in stage_artifacts:
|
|
198
|
+
artifact.validate()
|
|
199
|
+
if artifact_store is not None:
|
|
200
|
+
artifact_store.save(artifact)
|
|
201
|
+
for artifact in stage_artifacts:
|
|
202
|
+
if artifact.id not in work_unit.artifacts:
|
|
203
|
+
work_unit.artifacts.append(artifact.id)
|
|
204
|
+
work_unit.metadata.setdefault("artifact_ids", [])
|
|
205
|
+
for artifact in stage_artifacts:
|
|
206
|
+
if artifact.id not in work_unit.metadata["artifact_ids"]:
|
|
207
|
+
work_unit.metadata["artifact_ids"].append(artifact.id)
|
|
208
|
+
handoff = None
|
|
209
|
+
if previous_agent is not None:
|
|
210
|
+
handoff = self.handoffs.create(
|
|
211
|
+
work_unit.id,
|
|
212
|
+
previous_agent,
|
|
213
|
+
agent,
|
|
214
|
+
summary=f"Handoff from {previous_agent.id} to {agent.id}",
|
|
215
|
+
artifacts=work_unit.artifacts,
|
|
216
|
+
findings=tuple(work_unit.metadata.get("findings", ())),
|
|
217
|
+
)
|
|
218
|
+
work_unit.metadata.setdefault("handoffs", []).append({
|
|
219
|
+
"from_agent": handoff.from_agent,
|
|
220
|
+
"to_agent": handoff.to_agent,
|
|
221
|
+
"summary": handoff.summary,
|
|
222
|
+
"review_cycle": cycle + 1,
|
|
223
|
+
})
|
|
224
|
+
stages.append(
|
|
225
|
+
AgentStageResult(
|
|
226
|
+
agent.id, delegation, output, handoff,
|
|
227
|
+
stage_artifacts, stage_findings
|
|
228
|
+
)
|
|
229
|
+
)
|
|
230
|
+
previous_agent = agent
|
|
231
|
+
previous_output = output
|
|
232
|
+
|
|
233
|
+
work_unit.metadata["resume_context"] = WorkflowResumeContext(
|
|
234
|
+
workflow="review_rework",
|
|
235
|
+
work_unit_id=work_unit.id,
|
|
236
|
+
review_cycle=cycle + 1,
|
|
237
|
+
max_review_cycles=max_review_cycles,
|
|
238
|
+
developer_id=developer.id,
|
|
239
|
+
tester_id=tester.id,
|
|
240
|
+
reviewer_ids=tuple(agent.id for agent, _ in reviewers),
|
|
241
|
+
model_ids=tuple(model.id for model in models),
|
|
242
|
+
artifact_ids=tuple(work_unit.artifacts),
|
|
243
|
+
findings=tuple(work_unit.metadata.get("findings", ())),
|
|
244
|
+
last_agent_id=previous_agent.id if previous_agent else None,
|
|
245
|
+
).to_metadata()
|
|
246
|
+
|
|
247
|
+
if verifier is not None:
|
|
248
|
+
work_unit.metadata["checkpoint_output"] = _checkpoint_value(previous_output)
|
|
249
|
+
work_unit.metadata["checkpoint_next_action"] = "verify"
|
|
250
|
+
if checkpoint is not None:
|
|
251
|
+
checkpoint(work_unit, stage="verification", sequence=cycle + 1,
|
|
252
|
+
next_action="verify", agent_ids=(developer.id, tester.id, *[agent.id for agent, _ in reviewers]),
|
|
253
|
+
model_ids=tuple(model.id for model in models))
|
|
254
|
+
work_unit.transition(WorkStatus.VERIFYING)
|
|
255
|
+
if not verifier.verify(work_unit=work_unit, output=previous_output):
|
|
256
|
+
work_unit.metadata.setdefault("findings", []).append(
|
|
257
|
+
f"verification failed before review cycle {cycle + 1}"
|
|
258
|
+
)
|
|
259
|
+
if cycle + 1 < max_review_cycles:
|
|
260
|
+
work_unit.transition(WorkStatus.EXECUTING)
|
|
261
|
+
continue
|
|
262
|
+
work_unit.transition(WorkStatus.FAILED)
|
|
263
|
+
work_unit.metadata["review_cycle_count"] = cycle + 1
|
|
264
|
+
if checkpoint is not None:
|
|
265
|
+
checkpoint(work_unit, stage="verification", sequence=cycle + 1,
|
|
266
|
+
next_action=None,
|
|
267
|
+
agent_ids=(developer.id, tester.id, *[agent.id for agent, _ in reviewers]),
|
|
268
|
+
model_ids=tuple(model.id for model in models), resumable=False)
|
|
269
|
+
raise RuntimeError(
|
|
270
|
+
f"verification failed after {cycle + 1} review cycles for work unit: {work_unit.id}"
|
|
271
|
+
)
|
|
272
|
+
elif work_unit.status == WorkStatus.EXECUTING:
|
|
273
|
+
work_unit.transition(WorkStatus.VERIFYING)
|
|
274
|
+
|
|
275
|
+
work_unit.metadata["checkpoint_next_action"] = "review"
|
|
276
|
+
if checkpoint is not None:
|
|
277
|
+
checkpoint(work_unit, stage="review", sequence=cycle + 1,
|
|
278
|
+
next_action="review", agent_ids=(developer.id, tester.id, *[agent.id for agent, _ in reviewers]),
|
|
279
|
+
model_ids=tuple(model.id for model in models))
|
|
280
|
+
work_unit.transition(WorkStatus.REVIEWING)
|
|
281
|
+
review_context = ReviewContext(
|
|
282
|
+
work_unit_id=work_unit.id,
|
|
283
|
+
artifact_ids=tuple(work_unit.artifacts),
|
|
284
|
+
findings=tuple(work_unit.metadata.get("findings", ())),
|
|
285
|
+
last_agent_id=previous_agent.id if previous_agent else None,
|
|
286
|
+
review_cycle=cycle + 1,
|
|
287
|
+
metadata={"objective": work_unit.objective},
|
|
288
|
+
)
|
|
289
|
+
panel = self.review_panel.review(
|
|
290
|
+
work_unit.id,
|
|
291
|
+
reviewers,
|
|
292
|
+
reviewer_runner,
|
|
293
|
+
context=review_context,
|
|
294
|
+
)
|
|
295
|
+
reviews.extend(panel.reviews)
|
|
296
|
+
work_unit.metadata.setdefault("review_history", []).append({
|
|
297
|
+
"cycle": cycle + 1,
|
|
298
|
+
"approved": panel.approved,
|
|
299
|
+
"consensus": panel.consensus,
|
|
300
|
+
"feedback": [review.feedback for review in panel.reviews],
|
|
301
|
+
})
|
|
302
|
+
if panel.approved:
|
|
303
|
+
work_unit.metadata["review_cycle_count"] = cycle + 1
|
|
304
|
+
work_unit.metadata["review_consensus"] = panel.consensus
|
|
305
|
+
work_unit.transition(WorkStatus.HANDOFF)
|
|
306
|
+
work_unit.transition(WorkStatus.COMPLETED)
|
|
307
|
+
work_unit.metadata["execution_agent_ids"] = [stage.agent_id for stage in stages]
|
|
308
|
+
work_unit.metadata["multi_agent_stage_count"] = len(stages)
|
|
309
|
+
if checkpoint is not None:
|
|
310
|
+
checkpoint(work_unit, stage="completed", sequence=cycle + 1,
|
|
311
|
+
next_action=None,
|
|
312
|
+
agent_ids=(developer.id, tester.id, *[agent.id for agent, _ in reviewers]),
|
|
313
|
+
model_ids=tuple(model.id for model in models), resumable=False)
|
|
314
|
+
return MultiAgentWorkflowResult(
|
|
315
|
+
work_unit, tuple(stages), previous_output, tuple(reviews)
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
for review in panel.reviews:
|
|
319
|
+
if review.feedback.strip():
|
|
320
|
+
work_unit.metadata.setdefault("findings", []).append(
|
|
321
|
+
f"{review.reviewer_id}: {review.feedback}"
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
if cycle + 1 < max_review_cycles:
|
|
325
|
+
work_unit.metadata["rework_required"] = True
|
|
326
|
+
work_unit.transition(WorkStatus.EXECUTING)
|
|
327
|
+
if checkpoint is not None:
|
|
328
|
+
checkpoint(work_unit, stage="rework", sequence=cycle + 1,
|
|
329
|
+
next_action="rework", agent_ids=(developer.id, tester.id, *[agent.id for agent, _ in reviewers]),
|
|
330
|
+
model_ids=tuple(model.id for model in models))
|
|
331
|
+
continue
|
|
332
|
+
|
|
333
|
+
work_unit.metadata["review_cycle_count"] = cycle + 1
|
|
334
|
+
work_unit.metadata["review_consensus"] = panel.consensus
|
|
335
|
+
work_unit.metadata["rework_required"] = True
|
|
336
|
+
work_unit.metadata["human_review_required"] = True
|
|
337
|
+
work_unit.metadata["human_review_reason"] = (
|
|
338
|
+
f"review rejected after {cycle + 1} cycles"
|
|
339
|
+
)
|
|
340
|
+
work_unit.metadata["resume_context"] = WorkflowResumeContext(
|
|
341
|
+
workflow="review_rework",
|
|
342
|
+
work_unit_id=work_unit.id,
|
|
343
|
+
review_cycle=cycle + 1,
|
|
344
|
+
max_review_cycles=max_review_cycles,
|
|
345
|
+
developer_id=developer.id,
|
|
346
|
+
tester_id=tester.id,
|
|
347
|
+
reviewer_ids=tuple(agent.id for agent, _ in reviewers),
|
|
348
|
+
model_ids=tuple(model.id for model in models),
|
|
349
|
+
artifact_ids=tuple(work_unit.artifacts),
|
|
350
|
+
findings=tuple(work_unit.metadata.get("findings", ())),
|
|
351
|
+
last_agent_id=previous_agent.id if previous_agent else None,
|
|
352
|
+
).to_metadata()
|
|
353
|
+
work_unit.transition(WorkStatus.WAITING_HUMAN_APPROVAL)
|
|
354
|
+
if checkpoint is not None:
|
|
355
|
+
checkpoint(work_unit, stage="human_gate", sequence=cycle + 1,
|
|
356
|
+
next_action="human_review", agent_ids=(developer.id, tester.id, *[agent.id for agent, _ in reviewers]),
|
|
357
|
+
model_ids=tuple(model.id for model in models), resumable=True)
|
|
358
|
+
return MultiAgentWorkflowResult(
|
|
359
|
+
work_unit, tuple(stages), previous_output, tuple(reviews)
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
raise RuntimeError("review rework loop exited without a terminal result")
|
|
363
|
+
|
|
364
|
+
def resolve_human_review(
|
|
365
|
+
self,
|
|
366
|
+
*,
|
|
367
|
+
work_unit: WorkUnit,
|
|
368
|
+
approved: bool,
|
|
369
|
+
notes: str = "",
|
|
370
|
+
) -> MultiAgentWorkflowResult:
|
|
371
|
+
"""Resolve a bounded-workflow escalation with an explicit human decision."""
|
|
372
|
+
if work_unit.status is not WorkStatus.WAITING_HUMAN_APPROVAL:
|
|
373
|
+
raise ValueError("work unit is not waiting for human approval")
|
|
374
|
+
|
|
375
|
+
work_unit.metadata["human_review_required"] = False
|
|
376
|
+
work_unit.metadata["human_review_decision"] = "approved" if approved else "rejected"
|
|
377
|
+
if notes.strip():
|
|
378
|
+
work_unit.metadata["human_review_notes"] = notes
|
|
379
|
+
|
|
380
|
+
if approved:
|
|
381
|
+
work_unit.metadata["rework_required"] = False
|
|
382
|
+
work_unit.transition(WorkStatus.HANDOFF)
|
|
383
|
+
work_unit.transition(WorkStatus.COMPLETED)
|
|
384
|
+
else:
|
|
385
|
+
work_unit.metadata["rework_required"] = False
|
|
386
|
+
work_unit.transition(WorkStatus.FAILED)
|
|
387
|
+
|
|
388
|
+
return MultiAgentWorkflowResult(
|
|
389
|
+
work_unit=work_unit,
|
|
390
|
+
stages=(),
|
|
391
|
+
final_output=None,
|
|
392
|
+
reviews=(),
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
def run(
|
|
396
|
+
self,
|
|
397
|
+
*,
|
|
398
|
+
work_unit: WorkUnit,
|
|
399
|
+
stages: list[AgentContract],
|
|
400
|
+
models: list[ModelSpec],
|
|
401
|
+
executor: AgentExecutor,
|
|
402
|
+
verifier: ResultVerifier | None = None,
|
|
403
|
+
reviewers: list[tuple[AgentContract, object]] | None = None,
|
|
404
|
+
reviewer_runner=None,
|
|
405
|
+
preferred_model_ids: list[str] | None = None,
|
|
406
|
+
routing_strategy: RoutingStrategy | str = RoutingStrategy.POOL,
|
|
407
|
+
artifact_store=None,
|
|
408
|
+
checkpoint=None,
|
|
409
|
+
start_stage_index: int = 0,
|
|
410
|
+
resume_action: str | None = None,
|
|
411
|
+
resume_output: object = None,
|
|
412
|
+
) -> MultiAgentWorkflowResult:
|
|
413
|
+
if not stages:
|
|
414
|
+
raise ValueError("multi-agent workflow requires at least one stage")
|
|
415
|
+
|
|
416
|
+
if start_stage_index < 0 or start_stage_index > len(stages):
|
|
417
|
+
raise ValueError("start_stage_index must be within the stage list")
|
|
418
|
+
|
|
419
|
+
if resume_action not in {None, "verify", "review"}:
|
|
420
|
+
raise ValueError("unsupported multi-agent resume action")
|
|
421
|
+
if resume_action is not None and start_stage_index != len(stages):
|
|
422
|
+
raise ValueError("resume_action requires all execution stages to be complete")
|
|
423
|
+
|
|
424
|
+
results: list[AgentStageResult] = []
|
|
425
|
+
previous_agent: AgentContract | None = stages[start_stage_index - 1] if start_stage_index else None
|
|
426
|
+
previous_output: object = resume_output if resume_action is not None else None
|
|
427
|
+
previous_artifacts: tuple[ArtifactContract, ...] = ()
|
|
428
|
+
previous_findings: tuple[str, ...] = tuple(work_unit.metadata.get("findings", ()))
|
|
429
|
+
|
|
430
|
+
for stage_index, agent in enumerate(stages[start_stage_index:], start=start_stage_index):
|
|
431
|
+
work_unit.metadata["checkpoint_stage_index"] = stage_index
|
|
432
|
+
work_unit.metadata["checkpoint_next_action"] = "execute_stage"
|
|
433
|
+
if checkpoint is not None:
|
|
434
|
+
checkpoint(work_unit, stage=agent.id, sequence=stage_index,
|
|
435
|
+
next_action="execute_stage",
|
|
436
|
+
agent_ids=tuple(a.id for a in stages),
|
|
437
|
+
model_ids=tuple(model.id for model in models))
|
|
438
|
+
work_unit.metadata["stage_input"] = {
|
|
439
|
+
"from_agent": previous_agent.id if previous_agent else None,
|
|
440
|
+
"artifacts": tuple(a.id for a in previous_artifacts),
|
|
441
|
+
"findings": previous_findings,
|
|
442
|
+
}
|
|
443
|
+
delegation = self.delegation.delegate(
|
|
444
|
+
work_unit,
|
|
445
|
+
agent,
|
|
446
|
+
models,
|
|
447
|
+
preferred_model_ids,
|
|
448
|
+
routing_strategy,
|
|
449
|
+
)
|
|
450
|
+
output = executor.execute(
|
|
451
|
+
agent=agent,
|
|
452
|
+
model_id=delegation.assignment.model_id,
|
|
453
|
+
work_unit=work_unit,
|
|
454
|
+
)
|
|
455
|
+
work_unit.metadata["checkpoint_output"] = _checkpoint_value(output)
|
|
456
|
+
work_unit.metadata["checkpoint_stage_index"] = stage_index + 1
|
|
457
|
+
work_unit.metadata["checkpoint_next_action"] = (
|
|
458
|
+
"execute_stage" if stage_index + 1 < len(stages) else "verify"
|
|
459
|
+
)
|
|
460
|
+
if checkpoint is not None:
|
|
461
|
+
checkpoint(work_unit, stage=agent.id, sequence=stage_index + 1,
|
|
462
|
+
next_action=work_unit.metadata["checkpoint_next_action"],
|
|
463
|
+
agent_ids=tuple(a.id for a in stages),
|
|
464
|
+
model_ids=tuple(model.id for model in models))
|
|
465
|
+
stage_findings = tuple(work_unit.metadata.get("stage_findings", ()))
|
|
466
|
+
previous_findings = stage_findings
|
|
467
|
+
stage_artifacts = tuple(
|
|
468
|
+
artifact for artifact in work_unit.metadata.get("artifacts", ())
|
|
469
|
+
if isinstance(artifact, ArtifactContract)
|
|
470
|
+
)
|
|
471
|
+
for artifact in stage_artifacts:
|
|
472
|
+
artifact.validate()
|
|
473
|
+
if artifact_store is not None:
|
|
474
|
+
artifact_store.save(artifact)
|
|
475
|
+
work_unit.metadata.setdefault("artifact_ids", []).extend(
|
|
476
|
+
artifact.id for artifact in stage_artifacts
|
|
477
|
+
)
|
|
478
|
+
work_unit.artifacts.extend(
|
|
479
|
+
artifact.id for artifact in stage_artifacts if artifact.id not in work_unit.artifacts
|
|
480
|
+
)
|
|
481
|
+
previous_artifacts = stage_artifacts
|
|
482
|
+
handoff = None
|
|
483
|
+
if previous_agent is not None:
|
|
484
|
+
handoff = self.handoffs.create(
|
|
485
|
+
work_unit.id,
|
|
486
|
+
previous_agent,
|
|
487
|
+
agent,
|
|
488
|
+
summary=f"Handoff from {previous_agent.id} to {agent.id}",
|
|
489
|
+
artifacts=work_unit.artifacts,
|
|
490
|
+
findings=tuple(work_unit.metadata.get("findings", ())),
|
|
491
|
+
)
|
|
492
|
+
work_unit.metadata.setdefault("handoffs", []).append({
|
|
493
|
+
"from_agent": handoff.from_agent,
|
|
494
|
+
"to_agent": handoff.to_agent,
|
|
495
|
+
"summary": handoff.summary,
|
|
496
|
+
})
|
|
497
|
+
results.append(AgentStageResult(agent.id, delegation, output, handoff, stage_artifacts, stage_findings))
|
|
498
|
+
previous_agent = agent
|
|
499
|
+
previous_output = output
|
|
500
|
+
|
|
501
|
+
if verifier is not None and resume_action != "review":
|
|
502
|
+
work_unit.metadata["checkpoint_next_action"] = "verify"
|
|
503
|
+
work_unit.metadata["checkpoint_output"] = _checkpoint_value(previous_output)
|
|
504
|
+
if checkpoint is not None:
|
|
505
|
+
checkpoint(work_unit, stage="verification", sequence=len(stages),
|
|
506
|
+
next_action="verify", agent_ids=tuple(a.id for a in stages),
|
|
507
|
+
model_ids=tuple(model.id for model in models))
|
|
508
|
+
work_unit.transition(WorkStatus.VERIFYING)
|
|
509
|
+
if not verifier.verify(work_unit=work_unit, output=previous_output):
|
|
510
|
+
work_unit.transition(WorkStatus.FAILED)
|
|
511
|
+
if checkpoint is not None:
|
|
512
|
+
checkpoint(work_unit, stage="verification", sequence=len(stages),
|
|
513
|
+
next_action=None, agent_ids=tuple(a.id for a in stages),
|
|
514
|
+
model_ids=tuple(model.id for model in models), resumable=False)
|
|
515
|
+
raise RuntimeError(f"verification failed for work unit: {work_unit.id}")
|
|
516
|
+
|
|
517
|
+
if resume_action == "verify" and work_unit.status == WorkStatus.EXECUTING:
|
|
518
|
+
work_unit.transition(WorkStatus.VERIFYING)
|
|
519
|
+
elif resume_action == "review" and work_unit.status == WorkStatus.EXECUTING:
|
|
520
|
+
work_unit.transition(WorkStatus.VERIFYING)
|
|
521
|
+
|
|
522
|
+
reviews: tuple[ReviewResult, ...] = ()
|
|
523
|
+
if reviewers:
|
|
524
|
+
if reviewer_runner is None:
|
|
525
|
+
raise ValueError("reviewer_runner is required when reviewers are provided")
|
|
526
|
+
work_unit.metadata["checkpoint_next_action"] = "review"
|
|
527
|
+
if checkpoint is not None:
|
|
528
|
+
checkpoint(work_unit, stage="review", sequence=len(stages),
|
|
529
|
+
next_action="review", agent_ids=tuple(a.id for a in stages),
|
|
530
|
+
model_ids=tuple(model.id for model in models))
|
|
531
|
+
if work_unit.status != WorkStatus.VERIFYING:
|
|
532
|
+
work_unit.transition(WorkStatus.VERIFYING)
|
|
533
|
+
work_unit.transition(WorkStatus.REVIEWING)
|
|
534
|
+
panel = self.review_panel.review(
|
|
535
|
+
work_unit.id,
|
|
536
|
+
reviewers,
|
|
537
|
+
reviewer_runner,
|
|
538
|
+
)
|
|
539
|
+
reviews = panel.reviews
|
|
540
|
+
work_unit.metadata["review_consensus"] = panel.consensus
|
|
541
|
+
if not panel.approved:
|
|
542
|
+
work_unit.metadata["findings"] = [
|
|
543
|
+
*work_unit.metadata.get("findings", []),
|
|
544
|
+
*[review.feedback for review in panel.reviews if review.feedback.strip()],
|
|
545
|
+
]
|
|
546
|
+
work_unit.transition(WorkStatus.FAILED)
|
|
547
|
+
if checkpoint is not None:
|
|
548
|
+
checkpoint(work_unit, stage="review", sequence=len(stages),
|
|
549
|
+
next_action=None, agent_ids=tuple(a.id for a in stages),
|
|
550
|
+
model_ids=tuple(model.id for model in models), resumable=False)
|
|
551
|
+
raise RuntimeError(f"review rejected work unit: {work_unit.id}")
|
|
552
|
+
|
|
553
|
+
if verifier is not None or reviewers:
|
|
554
|
+
work_unit.transition(WorkStatus.HANDOFF)
|
|
555
|
+
work_unit.transition(WorkStatus.COMPLETED)
|
|
556
|
+
work_unit.metadata["execution_agent_ids"] = [stage.agent_id for stage in results]
|
|
557
|
+
work_unit.metadata["multi_agent_stage_count"] = len(results)
|
|
558
|
+
work_unit.metadata["checkpoint_next_action"] = None
|
|
559
|
+
if checkpoint is not None:
|
|
560
|
+
checkpoint(
|
|
561
|
+
work_unit,
|
|
562
|
+
stage="completed",
|
|
563
|
+
sequence=len(stages),
|
|
564
|
+
next_action=None,
|
|
565
|
+
agent_ids=tuple(a.id for a in stages),
|
|
566
|
+
model_ids=tuple(model.id for model in models),
|
|
567
|
+
resumable=False,
|
|
568
|
+
)
|
|
569
|
+
return MultiAgentWorkflowResult(
|
|
570
|
+
work_unit=work_unit,
|
|
571
|
+
stages=tuple(results),
|
|
572
|
+
final_output=previous_output,
|
|
573
|
+
reviews=reviews,
|
|
574
|
+
)
|
core/orchestrator.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Minimal executable VYRELON orchestration loop."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
|
|
6
|
+
from core.contracts.agent import AgentContract
|
|
7
|
+
from core.contracts.ai import ModelSpec
|
|
8
|
+
from core.contracts.execution import AgentExecutor, ResultReviewer, ResultVerifier
|
|
9
|
+
from core.contracts.work_unit import WorkUnit
|
|
10
|
+
from core.delegation import Delegation
|
|
11
|
+
from core.lifecycle import LifecycleCoordinator
|
|
12
|
+
from core.routing import RoutingStrategy
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class OrchestrationResult:
|
|
17
|
+
work_unit: WorkUnit
|
|
18
|
+
delegation: Delegation
|
|
19
|
+
output: object
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Orchestrator:
|
|
23
|
+
"""Coordinate delegation, execution, verification, review, and completion."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, lifecycle: LifecycleCoordinator | None = None) -> None:
|
|
26
|
+
self.lifecycle = lifecycle or LifecycleCoordinator()
|
|
27
|
+
|
|
28
|
+
def run(
|
|
29
|
+
self,
|
|
30
|
+
work_unit: WorkUnit,
|
|
31
|
+
agent: AgentContract,
|
|
32
|
+
models: list[ModelSpec],
|
|
33
|
+
executor: AgentExecutor,
|
|
34
|
+
preferred_model_ids: list[str] | None = None,
|
|
35
|
+
verifier: ResultVerifier | None = None,
|
|
36
|
+
reviewer: ResultReviewer | None = None,
|
|
37
|
+
routing_strategy: RoutingStrategy | str = RoutingStrategy.POOL,
|
|
38
|
+
checkpoint: Callable[[WorkUnit], None] | None = None,
|
|
39
|
+
) -> OrchestrationResult:
|
|
40
|
+
delegation, output = self.lifecycle.run(
|
|
41
|
+
work_unit,
|
|
42
|
+
agent,
|
|
43
|
+
models,
|
|
44
|
+
executor,
|
|
45
|
+
verifier,
|
|
46
|
+
reviewer,
|
|
47
|
+
preferred_model_ids,
|
|
48
|
+
routing_strategy,
|
|
49
|
+
checkpoint=checkpoint,
|
|
50
|
+
)
|
|
51
|
+
return OrchestrationResult(work_unit, delegation, output)
|
core/planning.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Deterministic planning service for VYRELON."""
|
|
2
|
+
|
|
3
|
+
from core.contracts.planning import PlanStep, WorkPlan
|
|
4
|
+
from core.contracts.work_unit import WorkStatus, WorkUnit
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BasicPlanner:
|
|
8
|
+
def plan(self, work_unit: WorkUnit, steps: list[PlanStep]) -> WorkPlan:
|
|
9
|
+
work_unit.transition(WorkStatus.PLANNING)
|
|
10
|
+
plan = WorkPlan(work_unit.id, work_unit.objective, tuple(steps))
|
|
11
|
+
plan.validate()
|
|
12
|
+
work_unit.metadata["plan_steps"] = [step.id for step in plan.steps]
|
|
13
|
+
work_unit.metadata["plan_details"] = [
|
|
14
|
+
{
|
|
15
|
+
"id": step.id,
|
|
16
|
+
"objective": step.objective,
|
|
17
|
+
"agent_id": step.agent_id,
|
|
18
|
+
"depends_on": list(step.depends_on),
|
|
19
|
+
}
|
|
20
|
+
for step in plan.steps
|
|
21
|
+
]
|
|
22
|
+
return plan
|