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
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
|
+
]
|