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
agents/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """Built-in VYRELON agent catalog."""
agents/catalog.py ADDED
@@ -0,0 +1,52 @@
1
+ """Built-in specialist agent definitions."""
2
+
3
+ from core.contracts.agent import AgentContract
4
+
5
+ _COMMON = {
6
+ "planner": ({"planning"}, {"filesystem.read"}),
7
+ "researcher": ({"research"}, {"network.read"}),
8
+ "editor": ({"editing"}, {"filesystem.read", "filesystem.write"}),
9
+ "executor": ({"execution"}, {"filesystem.read", "filesystem.write", "process"}),
10
+ "tester": ({"testing"}, {"filesystem.read", "process"}),
11
+ "debugger": ({"debugging"}, {"filesystem.read", "process"}),
12
+ "reviewer": ({"review"}, {"filesystem.read"}),
13
+ }
14
+
15
+ _PROFILE_SPECIALISTS = {
16
+ "react-native": {
17
+ "architect": ({"architecture", "react-native"}, {"filesystem.read"}),
18
+ "developer": ({"code", "react-native"}, {"filesystem.read", "filesystem.write"}),
19
+ "ui": ({"ui", "react-native"}, {"filesystem.read", "filesystem.write"}),
20
+ "navigation": ({"navigation", "react-native"}, {"filesystem.read", "filesystem.write"}),
21
+ "state-management": ({"state-management", "react-native"}, {"filesystem.read", "filesystem.write"}),
22
+ },
23
+ "android-native": {
24
+ "android-architect": ({"architecture", "android"}, {"filesystem.read"}),
25
+ "kotlin-developer": ({"code", "kotlin"}, {"filesystem.read", "filesystem.write"}),
26
+ "jetpack-compose": ({"ui", "compose", "kotlin"}, {"filesystem.read", "filesystem.write"}),
27
+ "gradle": ({"build", "gradle"}, {"filesystem.read", "filesystem.write", "process"}),
28
+ },
29
+ "ios-native": {
30
+ "ios-architect": ({"architecture", "ios"}, {"filesystem.read"}),
31
+ "swift-developer": ({"code", "swift"}, {"filesystem.read", "filesystem.write"}),
32
+ "swiftui": ({"ui", "swiftui"}, {"filesystem.read", "filesystem.write"}),
33
+ "xcode": ({"build", "xcode"}, {"filesystem.read", "filesystem.write", "process"}),
34
+ },
35
+ }
36
+
37
+
38
+ def build_agent_catalog(profile_ids: tuple[str, ...] = ()) -> tuple[AgentContract, ...]:
39
+ definitions: dict[str, tuple[set[str], set[str]]] = dict(_COMMON)
40
+ for profile_id in profile_ids:
41
+ definitions.update(_PROFILE_SPECIALISTS.get(profile_id, {}))
42
+ return tuple(
43
+ AgentContract(
44
+ id=agent_id,
45
+ role=agent_id,
46
+ capabilities=frozenset(capabilities),
47
+ tools=frozenset(tools),
48
+ metadata={"profiles": list(profile_ids)},
49
+ kind="specialist",
50
+ )
51
+ for agent_id, (capabilities, tools) in sorted(definitions.items())
52
+ )
agents/registry.py ADDED
@@ -0,0 +1,11 @@
1
+ """Agent catalog registration utilities."""
2
+
3
+ from core.registry import AgentRegistry
4
+ from agents.catalog import build_agent_catalog
5
+
6
+
7
+ def build_registry(profile_ids: tuple[str, ...] = ()) -> AgentRegistry:
8
+ registry = AgentRegistry()
9
+ for agent in build_agent_catalog(profile_ids):
10
+ registry.register(agent)
11
+ return registry
core/__init__.py ADDED
@@ -0,0 +1,38 @@
1
+ """VYRELON orchestration core."""
2
+
3
+ from core.chat_agent_bridge import ChatAgentBridge, ChatAgentExecutionResult, ChatAgentRequest, ChatAgentResponse
4
+ from core.chat_agent_registry import ChatAgentRegistry, default_chat_agents
5
+ from core.chat_agent_router import ChatAgentAssignment, ChatAgentRouter, ChatAgentRoutingStrategy
6
+ from core.chat_session import ChatSession, ChatSessionStore
7
+ from core.contracts.chat_agent import ChatAgentContract, ChatAgentProvider
8
+ from core.contracts.checkpoint import WorkflowCheckpoint
9
+ from core.contracts.execution import AgentExecutor, ResultReviewer, ResultVerifier, ReviewDecision
10
+ from core.contracts.human_review import HumanReviewDecision
11
+ from core.contracts.resume import WorkflowResumeContext
12
+ from core.contracts.handoff import ReviewContext
13
+ from core.contracts.model_runtime import ModelAdapter, ModelRequest, ModelResponse
14
+ from core.contracts.profile import DetectionResult, ProfileSpec
15
+ from core.contracts.runtime import ExecutionRequest, RuntimeExecutor
16
+ from core.delegation import Delegation, DelegationEngine
17
+ from core.lifecycle import ExecutionInterrupted, LifecycleCoordinator, LifecycleError
18
+ from core.orchestrator import OrchestrationResult, Orchestrator
19
+ from core.planning import BasicPlanner
20
+ from core.state import WorkStateStore
21
+ from core.registry import AIRegistry, AgentRegistry
22
+ from core.routing import AIRouter, Assignment, RoutingStrategy
23
+
24
+ __all__ = [
25
+ "AIRegistry", "AgentRegistry", "ChatAgentAssignment", "ChatAgentBridge", "ChatAgentRouter", "ChatAgentRoutingStrategy", "ChatSession", "ChatSessionStore", "ChatAgentExecutionResult", "ChatAgentRequest", "ChatAgentResponse", "ChatAgentRegistry", "ChatAgentContract", "ChatAgentProvider", "AIRouter", "Assignment", "RoutingStrategy",
26
+ "AgentExecutor", "ResultVerifier", "ResultReviewer", "ReviewDecision", "HumanReviewDecision", "WorkflowResumeContext", "WorkflowCheckpoint", "ReviewContext",
27
+ "ModelAdapter", "ModelRequest", "ModelResponse",
28
+ "ExecutionRequest", "RuntimeExecutor",
29
+ "DetectionResult", "ProfileSpec",
30
+ "Delegation", "DelegationEngine",
31
+ "ExecutionInterrupted", "LifecycleCoordinator", "LifecycleError",
32
+ "OrchestrationResult", "Orchestrator", "BasicPlanner", "WorkStateStore",
33
+ ]
34
+
35
+ from core.multi_agent_workflow import AgentStageResult, MultiAgentWorkflow, MultiAgentWorkflowResult
36
+
37
+ from core.artifacts import ArtifactStore
38
+ from core.contracts.handoff import ArtifactContract
core/artifacts.py ADDED
@@ -0,0 +1,47 @@
1
+ """Persistent artifact registry for VYRELON workflows."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+ from core.contracts.handoff import ArtifactContract
9
+
10
+
11
+ class ArtifactStore:
12
+ """Persist artifact metadata separately from transient model output."""
13
+
14
+ def __init__(self, root: Path) -> None:
15
+ self.root = root
16
+ self.root.mkdir(parents=True, exist_ok=True)
17
+
18
+ def save(self, artifact: ArtifactContract) -> Path:
19
+ artifact.validate()
20
+ path = self.root / f"{artifact.id}.json"
21
+ payload = {
22
+ "id": artifact.id,
23
+ "kind": artifact.kind,
24
+ "producer_agent_id": artifact.producer_agent_id,
25
+ "content_ref": artifact.content_ref,
26
+ "summary": artifact.summary,
27
+ "media_type": artifact.media_type,
28
+ "metadata": artifact.metadata,
29
+ }
30
+ path.write_text(
31
+ json.dumps(payload, indent=2, ensure_ascii=False, default=str) + "\n",
32
+ encoding="utf-8",
33
+ )
34
+ return path
35
+
36
+ def load(self, artifact_id: str) -> ArtifactContract:
37
+ path = self.root / f"{artifact_id}.json"
38
+ data = json.loads(path.read_text(encoding="utf-8"))
39
+ return ArtifactContract(
40
+ id=data["id"],
41
+ kind=data["kind"],
42
+ producer_agent_id=data["producer_agent_id"],
43
+ content_ref=data["content_ref"],
44
+ summary=data.get("summary", ""),
45
+ media_type=data.get("media_type", "text/plain"),
46
+ metadata=dict(data.get("metadata", {})),
47
+ )
@@ -0,0 +1,280 @@
1
+ """Provider-neutral bridge between a Chat Agent and the VYRELON runtime."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from uuid import uuid4
7
+ from typing import Protocol
8
+
9
+ from core.chat_agent_registry import ChatAgentRegistry
10
+ from core.contracts.agent import AgentContract
11
+ from core.contracts.ai import ModelSpec
12
+ from core.contracts.chat_agent import ChatAgentContract
13
+ from core.contracts.execution import AgentExecutor, ResultReviewer, ResultVerifier
14
+ from core.contracts.planning import PlanStep, WorkPlan
15
+ from core.contracts.work_unit import WorkUnit
16
+ from core.orchestrator import OrchestrationResult, Orchestrator
17
+ from core.planning import BasicPlanner
18
+ from core.state import WorkStateStore
19
+ from core.chat_session import ChatSession, ChatSessionStore
20
+
21
+
22
+ VYRELON_AGENT_RULES: tuple[str, ...] = (
23
+ "VYRELON is the execution authority.",
24
+ "Never report an action as completed without execution evidence.",
25
+ "Inspect relevant repository and runtime state before changing anything.",
26
+ "Preserve user intent and do not silently expand scope.",
27
+ "Respect repository boundaries; borrowed material is adapted into the target repository.",
28
+ "Keep credentials and secrets outside source code and persistent project state.",
29
+ "Use least privilege and obey VYRELON execution policy.",
30
+ "Verify changes with appropriate tests or checks before declaring completion.",
31
+ "Maintain WorkUnit, plan, artifact, finding, and handoff state.",
32
+ "Consequential Git and GitHub mutations follow VYRELON approval policy.",
33
+ "No Chat Agent provider has authority above VYRELON contracts.",
34
+ "Required independent reviewers must not be bypassed.",
35
+ )
36
+
37
+
38
+ def build_agent_instructions(agent: ChatAgentContract) -> str:
39
+ """Build the common instruction envelope for a connected Chat Agent."""
40
+ rules = "\n".join(f"{index}. {rule}" for index, rule in enumerate(VYRELON_AGENT_RULES, 1))
41
+ return (
42
+ f"You are {agent.name}, a VYRELON Chat Agent.\n"
43
+ f"Provider: {agent.provider.value}.\n"
44
+ f"Instruction profile: {agent.instruction_profile}.\n\n"
45
+ "Follow these mandatory VYRELON Agent Rules:\n"
46
+ f"{rules}\n\n"
47
+ "VYRELON is the execution authority. Use the capabilities exposed by VYRELON "
48
+ "rather than inventing tool execution or bypassing runtime policy."
49
+ )
50
+
51
+
52
+ @dataclass(frozen=True)
53
+ class ChatAgentRequest:
54
+ """A user task entering VYRELON through a Chat Agent."""
55
+
56
+ objective: str
57
+ inputs: dict[str, object] | None = None
58
+ work_unit_id: str | None = None
59
+
60
+
61
+ @dataclass(frozen=True)
62
+ class ChatAgentResponse:
63
+ """Structured response returned by a Chat Agent adapter."""
64
+
65
+ summary: str
66
+ steps: tuple[PlanStep, ...] = ()
67
+ findings: tuple[str, ...] = ()
68
+ artifacts: tuple[str, ...] = ()
69
+ evidence: tuple[str, ...] = ()
70
+
71
+
72
+ @dataclass(frozen=True)
73
+ class ChatAgentExecutionResult:
74
+ """The complete result of turning a Chat Agent turn into VYRELON execution."""
75
+
76
+ work_unit: WorkUnit
77
+ plan: WorkPlan
78
+ chat_response: ChatAgentResponse
79
+ orchestration: OrchestrationResult
80
+
81
+
82
+ class ChatAgentAdapter(Protocol):
83
+ """Provider adapter implemented by a connected Chat Agent."""
84
+
85
+ def respond(
86
+ self,
87
+ *,
88
+ agent: ChatAgentContract,
89
+ instructions: str,
90
+ request: ChatAgentRequest,
91
+ ) -> ChatAgentResponse:
92
+ """Return a structured response without directly owning VYRELON execution."""
93
+
94
+
95
+ class ChatAgentBridge:
96
+ """Translate Chat Agent turns into VYRELON WorkUnits, Plans, and execution."""
97
+
98
+ def __init__(
99
+ self,
100
+ registry: ChatAgentRegistry,
101
+ planner: BasicPlanner | None = None,
102
+ orchestrator: Orchestrator | None = None,
103
+ ) -> None:
104
+ self.registry = registry
105
+ self.planner = planner or BasicPlanner()
106
+ self.orchestrator = orchestrator or Orchestrator()
107
+
108
+ def instructions(self, agent_id: str | None = None) -> str:
109
+ agent = self.registry.get(agent_id) if agent_id else self.registry.primary()
110
+ return build_agent_instructions(agent)
111
+
112
+ def request(
113
+ self,
114
+ request: ChatAgentRequest,
115
+ adapter: ChatAgentAdapter,
116
+ agent_id: str | None = None,
117
+ ) -> tuple[WorkUnit, WorkPlan, ChatAgentResponse]:
118
+ agent = self.registry.get(agent_id) if agent_id else self.registry.primary()
119
+ if not request.objective.strip():
120
+ raise ValueError("chat agent objective must not be empty")
121
+
122
+ work_unit = WorkUnit(
123
+ id=request.work_unit_id or f"chat-{agent.id}-{uuid4().hex}",
124
+ objective=request.objective,
125
+ inputs=dict(request.inputs or {}),
126
+ )
127
+ response = adapter.respond(
128
+ agent=agent,
129
+ instructions=build_agent_instructions(agent),
130
+ request=request,
131
+ )
132
+ plan = self.planner.plan(work_unit, list(response.steps))
133
+ work_unit.metadata["chat_agent_id"] = agent.id
134
+ work_unit.metadata["chat_agent_summary"] = response.summary
135
+ work_unit.metadata["chat_agent_findings"] = list(response.findings)
136
+ work_unit.metadata["chat_agent_evidence"] = list(response.evidence)
137
+ work_unit.artifacts.extend(response.artifacts)
138
+ return work_unit, plan, response
139
+
140
+ def execute(
141
+ self,
142
+ request: ChatAgentRequest,
143
+ adapter: ChatAgentAdapter,
144
+ agent: AgentContract,
145
+ models: list[ModelSpec],
146
+ executor: AgentExecutor,
147
+ *,
148
+ chat_agent_id: str | None = None,
149
+ preferred_model_ids: list[str] | None = None,
150
+ verifier: ResultVerifier | None = None,
151
+ reviewer: ResultReviewer | None = None,
152
+ routing_strategy="pool",
153
+ state_store: WorkStateStore | None = None,
154
+ session_store: ChatSessionStore | None = None,
155
+ session: ChatSession | None = None,
156
+ ) -> ChatAgentExecutionResult:
157
+ """Run a Chat Agent request through the VYRELON execution lifecycle.
158
+
159
+ The Chat Agent supplies intent and planning. The supplied AgentContract,
160
+ model pool, executor, verifier, and reviewer determine actual execution.
161
+ """
162
+ work_unit, plan, response = self.request(
163
+ request, adapter, agent_id=chat_agent_id
164
+ )
165
+ work_unit.metadata["execution_authority"] = "vyrelon"
166
+ work_unit.metadata["execution_agent_id"] = agent.id
167
+ if session is not None:
168
+ session.work_unit_id = work_unit.id
169
+ session.metadata["execution_agent_id"] = agent.id
170
+ checkpoint_sequence = 0
171
+
172
+ def checkpoint(unit: WorkUnit) -> None:
173
+ nonlocal checkpoint_sequence
174
+ checkpoint_sequence += 1
175
+ unit.metadata["checkpoint"] = {
176
+ "status": unit.status.value,
177
+ "sequence": checkpoint_sequence,
178
+ }
179
+ if state_store is not None:
180
+ state_store.checkpoint(
181
+ unit,
182
+ workflow="chat_execution",
183
+ stage=unit.status.value,
184
+ sequence=checkpoint_sequence,
185
+ next_action=(
186
+ "resume_execution"
187
+ if unit.status.value not in {"completed", "failed"}
188
+ else None
189
+ ),
190
+ agent_ids=(agent.id,),
191
+ model_ids=tuple(model.id for model in models),
192
+ resumable=unit.status.value not in {"completed", "failed"},
193
+ )
194
+ if session is not None:
195
+ session.metadata["lifecycle_status"] = unit.status.value
196
+ if session_store is not None:
197
+ session_store.save(session)
198
+ checkpoint(work_unit)
199
+ orchestration = self.orchestrator.run(
200
+ work_unit=work_unit,
201
+ agent=agent,
202
+ models=models,
203
+ executor=executor,
204
+ preferred_model_ids=preferred_model_ids,
205
+ verifier=verifier,
206
+ reviewer=reviewer,
207
+ routing_strategy=routing_strategy,
208
+ checkpoint=checkpoint,
209
+ )
210
+ work_unit.metadata["execution_evidence"] = [
211
+ f"VYRELON execution completed with agent {agent.id}",
212
+ f"assigned model: {orchestration.delegation.assignment.model_id}",
213
+ ]
214
+ if verifier is not None:
215
+ work_unit.metadata["verification_evidence"] = ["VYRELON verifier accepted output"]
216
+ if reviewer is not None:
217
+ work_unit.metadata["review_evidence"] = ["VYRELON reviewer approved output"]
218
+ return ChatAgentExecutionResult(
219
+ work_unit=work_unit,
220
+ plan=plan,
221
+ chat_response=response,
222
+ orchestration=orchestration,
223
+ )
224
+
225
+
226
+ def resume(
227
+ self,
228
+ work_unit_id: str,
229
+ *,
230
+ state_store: WorkStateStore,
231
+ agent: AgentContract,
232
+ models: list[ModelSpec],
233
+ executor: AgentExecutor,
234
+ verifier: ResultVerifier | None = None,
235
+ reviewer: ResultReviewer | None = None,
236
+ preferred_model_ids: list[str] | None = None,
237
+ routing_strategy="pool",
238
+ ) -> OrchestrationResult:
239
+ """Resume a persisted WorkUnit after interruption."""
240
+ work_unit = state_store.load(work_unit_id)
241
+ if work_unit.status.value == "completed":
242
+ raise ValueError(f"work unit is already completed: {work_unit_id}")
243
+ if work_unit.status.value == "failed":
244
+ raise ValueError(f"failed work unit requires explicit retry policy: {work_unit_id}")
245
+
246
+ checkpoint_sequence = 0
247
+
248
+ def checkpoint(unit: WorkUnit) -> None:
249
+ nonlocal checkpoint_sequence
250
+ checkpoint_sequence += 1
251
+ unit.metadata["checkpoint"] = {
252
+ "status": unit.status.value,
253
+ "sequence": checkpoint_sequence,
254
+ }
255
+ state_store.checkpoint(
256
+ unit,
257
+ workflow="chat_execution",
258
+ stage=unit.status.value,
259
+ sequence=checkpoint_sequence,
260
+ next_action=(
261
+ "resume_execution"
262
+ if unit.status.value not in {"completed", "failed"}
263
+ else None
264
+ ),
265
+ agent_ids=(agent.id,),
266
+ model_ids=tuple(model.id for model in models),
267
+ resumable=unit.status.value not in {"completed", "failed"},
268
+ )
269
+
270
+ return self.orchestrator.run(
271
+ work_unit=work_unit,
272
+ agent=agent,
273
+ models=models,
274
+ executor=executor,
275
+ preferred_model_ids=preferred_model_ids,
276
+ verifier=verifier,
277
+ reviewer=reviewer,
278
+ routing_strategy=routing_strategy,
279
+ checkpoint=checkpoint,
280
+ )
@@ -0,0 +1,61 @@
1
+ """Registry for chat-facing AI agents."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from core.contracts.chat_agent import ChatAgentContract, ChatAgentProvider
6
+
7
+
8
+ class ChatAgentRegistry:
9
+ def __init__(self) -> None:
10
+ self._agents: dict[str, ChatAgentContract] = {}
11
+
12
+ def register(self, agent: ChatAgentContract) -> None:
13
+ agent.validate()
14
+ if agent.id in self._agents:
15
+ raise ValueError(f"chat agent already registered: {agent.id}")
16
+ self._agents[agent.id] = agent
17
+
18
+ def get(self, agent_id: str) -> ChatAgentContract:
19
+ return self._agents[agent_id]
20
+
21
+ def primary(self) -> ChatAgentContract:
22
+ for agent in self._agents.values():
23
+ if agent.primary:
24
+ return agent
25
+ raise LookupError("no primary chat agent registered")
26
+
27
+ def list(self) -> tuple[ChatAgentContract, ...]:
28
+ return tuple(sorted(self._agents.values(), key=lambda agent: agent.id))
29
+
30
+
31
+ def default_chat_agents() -> ChatAgentRegistry:
32
+ registry = ChatAgentRegistry()
33
+ registry.register(
34
+ ChatAgentContract(
35
+ id="chatgpt",
36
+ provider=ChatAgentProvider.CHATGPT,
37
+ name="ChatGPT Agent",
38
+ capabilities=frozenset({"conversation", "planning", "orchestration"}),
39
+ instruction_profile="vyrelon",
40
+ primary=True,
41
+ )
42
+ )
43
+ registry.register(
44
+ ChatAgentContract(
45
+ id="gemini",
46
+ provider=ChatAgentProvider.GEMINI,
47
+ name="Gemini Agent",
48
+ capabilities=frozenset({"conversation", "planning"}),
49
+ instruction_profile="vyrelon",
50
+ )
51
+ )
52
+ registry.register(
53
+ ChatAgentContract(
54
+ id="claude",
55
+ provider=ChatAgentProvider.CLAUDE,
56
+ name="Claude Agent",
57
+ capabilities=frozenset({"conversation", "planning"}),
58
+ instruction_profile="vyrelon",
59
+ )
60
+ )
61
+ return registry
@@ -0,0 +1,85 @@
1
+ """Routing policy for provider-neutral Chat Agents."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from enum import Enum
7
+
8
+ from core.chat_agent_registry import ChatAgentRegistry
9
+ from core.contracts.chat_agent import ChatAgentContract
10
+
11
+
12
+ class ChatAgentRoutingStrategy(str, Enum):
13
+ EXPLICIT = "explicit"
14
+ FALLBACK = "fallback"
15
+ AUTO = "auto"
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class ChatAgentAssignment:
20
+ agent: ChatAgentContract
21
+ strategy: ChatAgentRoutingStrategy
22
+ candidates: tuple[str, ...]
23
+
24
+
25
+ class ChatAgentRouter:
26
+ """Select a Chat Agent without giving any provider execution authority."""
27
+
28
+ def __init__(self, registry: ChatAgentRegistry) -> None:
29
+ self.registry = registry
30
+
31
+ def route(
32
+ self,
33
+ *,
34
+ preferred_agent_id: str | None = None,
35
+ fallback_agent_ids: tuple[str, ...] = (),
36
+ required_capabilities: frozenset[str] = frozenset(),
37
+ strategy: ChatAgentRoutingStrategy | str = ChatAgentRoutingStrategy.AUTO,
38
+ ) -> ChatAgentAssignment:
39
+ strategy = ChatAgentRoutingStrategy(strategy)
40
+ candidates: list[ChatAgentContract] = []
41
+
42
+ if preferred_agent_id is not None:
43
+ candidates.append(self.registry.get(preferred_agent_id))
44
+
45
+ for agent_id in fallback_agent_ids:
46
+ agent = self.registry.get(agent_id)
47
+ if agent.id not in {item.id for item in candidates}:
48
+ candidates.append(agent)
49
+
50
+ if strategy is ChatAgentRoutingStrategy.AUTO and not candidates:
51
+ candidates.extend(self.registry.list())
52
+
53
+ if strategy is ChatAgentRoutingStrategy.EXPLICIT:
54
+ if preferred_agent_id is None:
55
+ raise ValueError("explicit chat-agent routing requires preferred_agent_id")
56
+ candidates = [self.registry.get(preferred_agent_id)]
57
+
58
+ if strategy is ChatAgentRoutingStrategy.FALLBACK and not candidates:
59
+ raise ValueError("fallback chat-agent routing requires candidates")
60
+
61
+ compatible = [
62
+ agent for agent in candidates
63
+ if required_capabilities.issubset(agent.capabilities)
64
+ ]
65
+ if not compatible:
66
+ raise LookupError(
67
+ "no chat agent satisfies required capabilities: "
68
+ + ", ".join(sorted(required_capabilities))
69
+ )
70
+
71
+ # AUTO prefers the registered primary ChatGPT agent when it is compatible.
72
+ if strategy is ChatAgentRoutingStrategy.AUTO:
73
+ primary = self.registry.primary()
74
+ if primary in compatible:
75
+ selected = primary
76
+ else:
77
+ selected = compatible[0]
78
+ else:
79
+ selected = compatible[0]
80
+
81
+ return ChatAgentAssignment(
82
+ agent=selected,
83
+ strategy=strategy,
84
+ candidates=tuple(agent.id for agent in compatible),
85
+ )
core/chat_session.py ADDED
@@ -0,0 +1,62 @@
1
+ """Persistent conversational session state for VYRELON Chat Agents."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from pathlib import Path
7
+ import json
8
+ from datetime import datetime, timezone
9
+
10
+
11
+ @dataclass
12
+ class ChatSession:
13
+ id: str
14
+ chat_agent_id: str
15
+ work_unit_id: str | None = None
16
+ turns: list[dict[str, object]] = field(default_factory=list)
17
+ metadata: dict[str, object] = field(default_factory=dict)
18
+ updated_at: str = ""
19
+
20
+ def add_turn(self, role: str, content: str, **metadata: object) -> None:
21
+ self.turns.append(
22
+ {"role": role, "content": content, "metadata": metadata}
23
+ )
24
+ self.updated_at = datetime.now(timezone.utc).isoformat()
25
+
26
+
27
+ class ChatSessionStore:
28
+ """Store Chat Agent conversation state separately from provider credentials."""
29
+
30
+ def __init__(self, root: Path) -> None:
31
+ self.root = root
32
+ self.root.mkdir(parents=True, exist_ok=True)
33
+
34
+ def save(self, session: ChatSession) -> Path:
35
+ if not session.updated_at:
36
+ session.updated_at = datetime.now(timezone.utc).isoformat()
37
+ path = self.root / f"{session.id}.json"
38
+ payload = {
39
+ "id": session.id,
40
+ "chat_agent_id": session.chat_agent_id,
41
+ "work_unit_id": session.work_unit_id,
42
+ "turns": session.turns,
43
+ "metadata": session.metadata,
44
+ "updated_at": session.updated_at,
45
+ }
46
+ path.write_text(
47
+ json.dumps(payload, indent=2, ensure_ascii=False, default=str) + "\n",
48
+ encoding="utf-8",
49
+ )
50
+ return path
51
+
52
+ def load(self, session_id: str) -> ChatSession:
53
+ path = self.root / f"{session_id}.json"
54
+ data = json.loads(path.read_text(encoding="utf-8"))
55
+ return ChatSession(
56
+ id=data["id"],
57
+ chat_agent_id=data["chat_agent_id"],
58
+ work_unit_id=data.get("work_unit_id"),
59
+ turns=list(data.get("turns", [])),
60
+ metadata=dict(data.get("metadata", {})),
61
+ updated_at=data.get("updated_at", ""),
62
+ )
@@ -0,0 +1,22 @@
1
+ """VYRELON core contracts."""
2
+
3
+ from core.contracts.chat_agent import ChatAgentContract, ChatAgentProvider
4
+ from core.contracts.agent import AgentContract
5
+ from core.contracts.ai import AIProvider, ModelSpec
6
+ from core.contracts.execution import AgentExecutor, ResultReviewer, ResultVerifier, ReviewDecision
7
+ from core.contracts.mcp import MCPServerSpec, MCPSession, MCPTool, MCPToolCall, MCPToolResult
8
+ from core.contracts.model_runtime import ModelAdapter, ModelRequest, ModelResponse
9
+ from core.contracts.health import HealthStatus, ModelHealth
10
+ from core.contracts.runtime import ExecutionRequest, RuntimeExecutor
11
+ from core.contracts.work_unit import WorkStatus, WorkUnit
12
+ from core.contracts.profile import DetectionResult, ProfileSpec
13
+ from core.contracts.planning import PlanStep, WorkPlan
14
+
15
+ __all__ = [
16
+ "AgentContract", "ChatAgentContract", "ChatAgentProvider", "AIProvider", "ModelSpec",
17
+ "AgentExecutor", "ResultVerifier", "ResultReviewer", "ReviewDecision",
18
+ "MCPServerSpec", "MCPSession", "MCPTool", "MCPToolCall", "MCPToolResult",
19
+ "ModelAdapter", "ModelRequest", "ModelResponse", "HealthStatus", "ModelHealth",
20
+ "ExecutionRequest", "RuntimeExecutor", "DetectionResult", "ProfileSpec",
21
+ "PlanStep", "WorkPlan", "WorkStatus", "WorkUnit",
22
+ ]