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.
Files changed (122) hide show
  1. agents/__init__.py +1 -0
  2. agents/catalog.py +52 -0
  3. agents/registry.py +11 -0
  4. core/__init__.py +38 -0
  5. core/artifacts.py +47 -0
  6. core/chat_agent_bridge.py +280 -0
  7. core/chat_agent_registry.py +61 -0
  8. core/chat_agent_router.py +85 -0
  9. core/chat_session.py +62 -0
  10. core/contracts/__init__.py +22 -0
  11. core/contracts/agent.py +21 -0
  12. core/contracts/ai.py +25 -0
  13. core/contracts/capability.py +44 -0
  14. core/contracts/chat_agent.py +34 -0
  15. core/contracts/checkpoint.py +78 -0
  16. core/contracts/discovery.py +30 -0
  17. core/contracts/execution.py +28 -0
  18. core/contracts/github.py +74 -0
  19. core/contracts/handoff.py +56 -0
  20. core/contracts/health.py +206 -0
  21. core/contracts/human_review.py +20 -0
  22. core/contracts/ide.py +113 -0
  23. core/contracts/mcp.py +76 -0
  24. core/contracts/model_runtime.py +25 -0
  25. core/contracts/planning.py +25 -0
  26. core/contracts/profile.py +66 -0
  27. core/contracts/quota.py +62 -0
  28. core/contracts/repository.py +34 -0
  29. core/contracts/resume.py +55 -0
  30. core/contracts/runtime.py +19 -0
  31. core/contracts/vyrelon_runtime.py +118 -0
  32. core/contracts/work_unit.py +48 -0
  33. core/delegation.py +33 -0
  34. core/handoff.py +68 -0
  35. core/lifecycle.py +85 -0
  36. core/multi_agent_workflow.py +574 -0
  37. core/orchestrator.py +51 -0
  38. core/planning.py +22 -0
  39. core/registry.py +44 -0
  40. core/routing.py +195 -0
  41. core/state.py +155 -0
  42. installer/__init__.py +1 -0
  43. installer/init.py +71 -0
  44. integrations/__init__.py +0 -0
  45. integrations/github/__init__.py +3 -0
  46. integrations/github/gateway.py +164 -0
  47. integrations/openai/__init__.py +1 -0
  48. integrations/openai/chat_agent.py +147 -0
  49. multiagentos/__init__.py +1 -0
  50. multiagentos/cli.py +759 -0
  51. multiagentos-0.4.0.dist-info/METADATA +7 -0
  52. multiagentos-0.4.0.dist-info/RECORD +122 -0
  53. multiagentos-0.4.0.dist-info/WHEEL +5 -0
  54. multiagentos-0.4.0.dist-info/entry_points.txt +2 -0
  55. multiagentos-0.4.0.dist-info/top_level.txt +7 -0
  56. profiles/__init__.py +1 -0
  57. profiles/common.py +11 -0
  58. profiles/detector.py +67 -0
  59. profiles/registry.py +46 -0
  60. profiles/resolver.py +63 -0
  61. runtime/__init__.py +5 -0
  62. runtime/agent/__init__.py +1 -0
  63. runtime/agent/ide.py +74 -0
  64. runtime/agent/model.py +197 -0
  65. runtime/agent/model_backed.py +49 -0
  66. runtime/agent/process.py +21 -0
  67. runtime/builtin_tools.py +57 -0
  68. runtime/capability.py +143 -0
  69. runtime/chat_adapter_registry.py +43 -0
  70. runtime/chat_config.py +57 -0
  71. runtime/chat_execution.py +52 -0
  72. runtime/config/__init__.py +1 -0
  73. runtime/config/loader.py +67 -0
  74. runtime/discovery.py +334 -0
  75. runtime/execution_config.py +71 -0
  76. runtime/execution_registry.py +50 -0
  77. runtime/git.py +84 -0
  78. runtime/github.py +56 -0
  79. runtime/github_probe.py +23 -0
  80. runtime/health.py +14 -0
  81. runtime/ide/__init__.py +6 -0
  82. runtime/ide/bridge.py +198 -0
  83. runtime/ide/registry.py +24 -0
  84. runtime/ide/runtime.py +35 -0
  85. runtime/local/__init__.py +18 -0
  86. runtime/local/filesystem.py +58 -0
  87. runtime/local/patch.py +79 -0
  88. runtime/local/path_security.py +31 -0
  89. runtime/local/permissions.py +20 -0
  90. runtime/local/process.py +56 -0
  91. runtime/local/shell.py +97 -0
  92. runtime/mcp/__init__.py +7 -0
  93. runtime/mcp/client.py +120 -0
  94. runtime/mcp/config.py +36 -0
  95. runtime/mcp/policy.py +57 -0
  96. runtime/mcp/proxy.py +30 -0
  97. runtime/mcp/server.py +137 -0
  98. runtime/mcp/session.py +19 -0
  99. runtime/model/__init__.py +5 -0
  100. runtime/model/ai_runtime.py +165 -0
  101. runtime/model/cli.py +49 -0
  102. runtime/model/config.py +98 -0
  103. runtime/model/credentials.py +42 -0
  104. runtime/model/factory.py +82 -0
  105. runtime/model/http.py +78 -0
  106. runtime/model/invoker.py +16 -0
  107. runtime/model/materializer.py +101 -0
  108. runtime/model/native.py +244 -0
  109. runtime/model/providers.py +55 -0
  110. runtime/model/registry.py +26 -0
  111. runtime/model/runtime_loader.py +51 -0
  112. runtime/model_control.py +136 -0
  113. runtime/multi_agent.py +142 -0
  114. runtime/policy.py +28 -0
  115. runtime/process.py +41 -0
  116. runtime/quota.py +259 -0
  117. runtime/repository.py +103 -0
  118. runtime/repository_tools.py +62 -0
  119. runtime/status.py +87 -0
  120. runtime/tool_calling.py +134 -0
  121. runtime/validation.py +121 -0
  122. 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