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
core/contracts/ide.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Provider-neutral IDE integration contracts for VYRELON."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from enum import StrEnum
|
|
7
|
+
from typing import Protocol
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class IDEKind(StrEnum):
|
|
11
|
+
XCODE = "xcode"
|
|
12
|
+
VS_CODE = "vs_code"
|
|
13
|
+
ANDROID_STUDIO = "android_studio"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class IDEEventKind(StrEnum):
|
|
17
|
+
CONTEXT_CHANGED = "context_changed"
|
|
18
|
+
SELECTION_CHANGED = "selection_changed"
|
|
19
|
+
FILE_OPENED = "file_opened"
|
|
20
|
+
FILE_SAVED = "file_saved"
|
|
21
|
+
WORKSPACE_OPENED = "workspace_opened"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class IDECommandKind(StrEnum):
|
|
25
|
+
OPEN_WORKSPACE = "open_workspace"
|
|
26
|
+
OPEN_FILE = "open_file"
|
|
27
|
+
INSERT_TEXT = "insert_text"
|
|
28
|
+
REPLACE_SELECTION = "replace_selection"
|
|
29
|
+
RUN_COMMAND = "run_command"
|
|
30
|
+
START_WORK = "start_work"
|
|
31
|
+
SHOW_MESSAGE = "show_message"
|
|
32
|
+
SHOW_DIFF = "show_diff"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class IDEContext:
|
|
37
|
+
"""Normalized editor/workspace context supplied by an IDE adapter."""
|
|
38
|
+
|
|
39
|
+
kind: IDEKind
|
|
40
|
+
project_root: str
|
|
41
|
+
workspace_id: str | None = None
|
|
42
|
+
file_path: str | None = None
|
|
43
|
+
selection_start: int | None = None
|
|
44
|
+
selection_end: int | None = None
|
|
45
|
+
language_id: str | None = None
|
|
46
|
+
metadata: dict[str, object] = field(default_factory=dict)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True)
|
|
50
|
+
class IDEEvent:
|
|
51
|
+
"""Normalized event sent from an IDE adapter to VYRELON."""
|
|
52
|
+
|
|
53
|
+
kind: IDEEventKind
|
|
54
|
+
context: IDEContext
|
|
55
|
+
payload: dict[str, object] = field(default_factory=dict)
|
|
56
|
+
metadata: dict[str, object] = field(default_factory=dict)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass(frozen=True)
|
|
60
|
+
class IDEWorkRequest:
|
|
61
|
+
"""Explicit user/IDE request that asks VYRELON to execute agent work."""
|
|
62
|
+
|
|
63
|
+
context: IDEContext
|
|
64
|
+
objective: str
|
|
65
|
+
agent_id: str
|
|
66
|
+
model_ids: tuple[str, ...] = ()
|
|
67
|
+
apply_changes: bool = False
|
|
68
|
+
validation_commands: tuple[str, ...] = ()
|
|
69
|
+
metadata: dict[str, object] = field(default_factory=dict)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass(frozen=True)
|
|
73
|
+
class IDECommand:
|
|
74
|
+
"""Normalized command sent from VYRELON to an IDE adapter."""
|
|
75
|
+
|
|
76
|
+
kind: IDECommandKind
|
|
77
|
+
arguments: dict[str, object] = field(default_factory=dict)
|
|
78
|
+
context: IDEContext | None = None
|
|
79
|
+
metadata: dict[str, object] = field(default_factory=dict)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass(frozen=True)
|
|
83
|
+
class IDECommandResult:
|
|
84
|
+
"""Normalized result returned by an IDE adapter."""
|
|
85
|
+
|
|
86
|
+
ok: bool
|
|
87
|
+
output: object = None
|
|
88
|
+
error: str | None = None
|
|
89
|
+
metadata: dict[str, object] = field(default_factory=dict)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class IDEAdapter(Protocol):
|
|
93
|
+
"""Thin IDE-specific bridge; VYRELON never imports an IDE SDK."""
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def kind(self) -> IDEKind:
|
|
97
|
+
...
|
|
98
|
+
|
|
99
|
+
def capabilities(self) -> frozenset[str]:
|
|
100
|
+
...
|
|
101
|
+
|
|
102
|
+
def context(self) -> IDEContext:
|
|
103
|
+
...
|
|
104
|
+
|
|
105
|
+
def execute(self, command: IDECommand) -> IDECommandResult:
|
|
106
|
+
...
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class IDEEventSink(Protocol):
|
|
110
|
+
"""Consumer for normalized IDE events flowing into VYRELON."""
|
|
111
|
+
|
|
112
|
+
def handle(self, event: IDEEvent) -> object:
|
|
113
|
+
...
|
core/contracts/mcp.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Provider-neutral MCP runtime contracts for VYRELON."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from core.contracts.vyrelon_runtime import ToolSideEffect
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True)
|
|
7
|
+
class MCPServerSpec:
|
|
8
|
+
id: str
|
|
9
|
+
command: tuple[str, ...] = ()
|
|
10
|
+
args: tuple[str, ...] = ()
|
|
11
|
+
env: dict[str, str] = field(default_factory=dict)
|
|
12
|
+
transport: str = "stdio"
|
|
13
|
+
endpoint: str | None = None
|
|
14
|
+
headers: dict[str, str] = field(default_factory=dict)
|
|
15
|
+
enabled: bool = True
|
|
16
|
+
metadata: dict[str, object] = field(default_factory=dict)
|
|
17
|
+
source: str = "built-in"
|
|
18
|
+
cost_policy: str = "no_external_billing"
|
|
19
|
+
requires_explicit_enable: bool = False
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class MCPSession:
|
|
23
|
+
id: str
|
|
24
|
+
server_id: str
|
|
25
|
+
protocol_version: str = "2025-03-26"
|
|
26
|
+
metadata: dict[str, object] = field(default_factory=dict)
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class MCPTool:
|
|
30
|
+
name: str
|
|
31
|
+
description: str = ""
|
|
32
|
+
input_schema: dict[str, object] = field(default_factory=dict)
|
|
33
|
+
server_id: str = ""
|
|
34
|
+
side_effect: ToolSideEffect = ToolSideEffect.READ
|
|
35
|
+
permissions: frozenset[str] = frozenset()
|
|
36
|
+
metadata: dict[str, object] = field(default_factory=dict)
|
|
37
|
+
|
|
38
|
+
@dataclass(frozen=True)
|
|
39
|
+
class MCPToolCall:
|
|
40
|
+
server_id: str
|
|
41
|
+
tool_name: str
|
|
42
|
+
arguments: dict[str, object] = field(default_factory=dict)
|
|
43
|
+
session_id: str | None = None
|
|
44
|
+
|
|
45
|
+
@dataclass(frozen=True)
|
|
46
|
+
class MCPToolResult:
|
|
47
|
+
server_id: str
|
|
48
|
+
tool_name: str
|
|
49
|
+
content: tuple[object, ...] = ()
|
|
50
|
+
is_error: bool = False
|
|
51
|
+
raw: object = None
|
|
52
|
+
|
|
53
|
+
@dataclass(frozen=True)
|
|
54
|
+
class MCPToolProfile:
|
|
55
|
+
"""Agent-facing MCP access policy."""
|
|
56
|
+
id: str
|
|
57
|
+
server_ids: frozenset[str] = frozenset()
|
|
58
|
+
allowed_tools: frozenset[str] = frozenset()
|
|
59
|
+
denied_tools: frozenset[str] = frozenset()
|
|
60
|
+
required_permissions: frozenset[str] = frozenset()
|
|
61
|
+
allowed_side_effects: frozenset[ToolSideEffect] = frozenset({ToolSideEffect.READ})
|
|
62
|
+
metadata: dict[str, object] = field(default_factory=dict)
|
|
63
|
+
tool_side_effects: dict[str, ToolSideEffect] = field(default_factory=dict)
|
|
64
|
+
|
|
65
|
+
def allows(self, tool: MCPTool, permissions: frozenset[str] = frozenset()) -> bool:
|
|
66
|
+
if self.server_ids and tool.server_id not in self.server_ids:
|
|
67
|
+
return False
|
|
68
|
+
qualified = f"{tool.server_id}:{tool.name}"
|
|
69
|
+
if qualified in self.denied_tools or tool.name in self.denied_tools:
|
|
70
|
+
return False
|
|
71
|
+
if self.allowed_tools and qualified not in self.allowed_tools and tool.name not in self.allowed_tools:
|
|
72
|
+
return False
|
|
73
|
+
effective_side_effect = self.tool_side_effects.get(qualified, self.tool_side_effects.get(tool.name, tool.side_effect))
|
|
74
|
+
if effective_side_effect not in self.allowed_side_effects:
|
|
75
|
+
return False
|
|
76
|
+
return self.required_permissions.issubset(permissions) and tool.permissions.issubset(permissions)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Provider-neutral model invocation contracts."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Protocol
|
|
5
|
+
|
|
6
|
+
from core.contracts.ai import ModelSpec
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class ModelRequest:
|
|
11
|
+
prompt: str
|
|
12
|
+
system: str | None = None
|
|
13
|
+
metadata: dict[str, object] = field(default_factory=dict)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class ModelResponse:
|
|
18
|
+
text: str
|
|
19
|
+
model_id: str
|
|
20
|
+
metadata: dict[str, object] = field(default_factory=dict)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ModelAdapter(Protocol):
|
|
24
|
+
def generate(self, model: ModelSpec, request: ModelRequest) -> ModelResponse:
|
|
25
|
+
...
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Provider-neutral planning contracts."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True)
|
|
7
|
+
class PlanStep:
|
|
8
|
+
id: str
|
|
9
|
+
objective: str
|
|
10
|
+
agent_id: str | None = None
|
|
11
|
+
depends_on: tuple[str, ...] = ()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class WorkPlan:
|
|
16
|
+
work_unit_id: str
|
|
17
|
+
objective: str
|
|
18
|
+
steps: tuple[PlanStep, ...]
|
|
19
|
+
|
|
20
|
+
def validate(self) -> None:
|
|
21
|
+
known = {step.id for step in self.steps}
|
|
22
|
+
for step in self.steps:
|
|
23
|
+
missing = set(step.depends_on) - known
|
|
24
|
+
if missing:
|
|
25
|
+
raise ValueError(f"Unknown plan dependencies: {sorted(missing)}")
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Technology, project, and agent profile contracts for VYRELON."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True)
|
|
7
|
+
class ProfileSpec:
|
|
8
|
+
id: str
|
|
9
|
+
display_name: str
|
|
10
|
+
detect_files: frozenset[str] = frozenset()
|
|
11
|
+
detect_markers: frozenset[str] = frozenset()
|
|
12
|
+
roles: tuple[str, ...] = ()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class DetectionResult:
|
|
17
|
+
profile_id: str
|
|
18
|
+
confidence: float
|
|
19
|
+
evidence: tuple[str, ...]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class AgentProfile:
|
|
24
|
+
"""Stable agent identity derived from a project technology profile."""
|
|
25
|
+
|
|
26
|
+
id: str
|
|
27
|
+
role: str
|
|
28
|
+
profile_ids: tuple[str, ...] = ()
|
|
29
|
+
capabilities: frozenset[str] = frozenset()
|
|
30
|
+
tools: frozenset[str] = frozenset()
|
|
31
|
+
permissions: frozenset[str] = frozenset()
|
|
32
|
+
model_ids: tuple[str, ...] = ()
|
|
33
|
+
metadata: dict[str, object] = field(default_factory=dict)
|
|
34
|
+
|
|
35
|
+
def to_contract(self):
|
|
36
|
+
from core.contracts.agent import AgentContract
|
|
37
|
+
|
|
38
|
+
return AgentContract(
|
|
39
|
+
id=self.id,
|
|
40
|
+
role=self.role,
|
|
41
|
+
capabilities=self.capabilities,
|
|
42
|
+
tools=self.tools,
|
|
43
|
+
permissions=self.permissions,
|
|
44
|
+
model_ids=self.model_ids,
|
|
45
|
+
metadata={
|
|
46
|
+
**self.metadata,
|
|
47
|
+
"profiles": list(self.profile_ids),
|
|
48
|
+
"agent_profile_id": self.id,
|
|
49
|
+
},
|
|
50
|
+
kind="profile",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass(frozen=True)
|
|
55
|
+
class ProjectProfile:
|
|
56
|
+
"""Resolved project identity shared by VYRELON integrations."""
|
|
57
|
+
|
|
58
|
+
id: str
|
|
59
|
+
root: str
|
|
60
|
+
technology_profile_ids: tuple[str, ...] = ()
|
|
61
|
+
agent_profile_ids: tuple[str, ...] = ()
|
|
62
|
+
metadata: dict[str, object] = field(default_factory=dict)
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def primary_technology_profile(self) -> str | None:
|
|
66
|
+
return self.technology_profile_ids[0] if self.technology_profile_ids else None
|
core/contracts/quota.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Provider-neutral quota and rate-limit contracts for VYRELON."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from enum import StrEnum
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class QuotaConfidence(StrEnum):
|
|
11
|
+
ACTUAL = "actual"
|
|
12
|
+
OBSERVED = "observed"
|
|
13
|
+
ESTIMATED = "estimated"
|
|
14
|
+
UNKNOWN = "unknown"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class QuotaDimension:
|
|
19
|
+
"""One quota/rate-limit dimension such as requests or tokens."""
|
|
20
|
+
|
|
21
|
+
name: str
|
|
22
|
+
limit: int | float | None = None
|
|
23
|
+
used: int | float | None = None
|
|
24
|
+
remaining: int | float | None = None
|
|
25
|
+
reset_at: datetime | None = None
|
|
26
|
+
window_seconds: int | None = None
|
|
27
|
+
confidence: QuotaConfidence = QuotaConfidence.UNKNOWN
|
|
28
|
+
source: str = "unknown"
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def utilization(self) -> float | None:
|
|
32
|
+
if self.limit is None or self.limit <= 0 or self.used is None:
|
|
33
|
+
return None
|
|
34
|
+
return min(1.0, max(0.0, float(self.used) / float(self.limit)))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
|
+
class QuotaSnapshot:
|
|
39
|
+
"""Latest quota intelligence for one configured model."""
|
|
40
|
+
|
|
41
|
+
model_id: str
|
|
42
|
+
provider_id: str
|
|
43
|
+
observed_at: datetime
|
|
44
|
+
dimensions: tuple[QuotaDimension, ...] = ()
|
|
45
|
+
scope: str = "model"
|
|
46
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
47
|
+
|
|
48
|
+
def dimension(self, name: str) -> QuotaDimension | None:
|
|
49
|
+
for item in self.dimensions:
|
|
50
|
+
if item.name == name:
|
|
51
|
+
return item
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def confidence(self) -> QuotaConfidence:
|
|
56
|
+
if any(item.confidence is QuotaConfidence.ACTUAL for item in self.dimensions):
|
|
57
|
+
return QuotaConfidence.ACTUAL
|
|
58
|
+
if any(item.confidence is QuotaConfidence.ESTIMATED for item in self.dimensions):
|
|
59
|
+
return QuotaConfidence.ESTIMATED
|
|
60
|
+
if any(item.confidence is QuotaConfidence.OBSERVED for item in self.dimensions):
|
|
61
|
+
return QuotaConfidence.OBSERVED
|
|
62
|
+
return QuotaConfidence.UNKNOWN
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Repository-level contracts for VYRELON."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class RepositoryCheckpoint:
|
|
10
|
+
id: str
|
|
11
|
+
project_root: str
|
|
12
|
+
marker: str
|
|
13
|
+
status: str
|
|
14
|
+
diff: str
|
|
15
|
+
created_at: str
|
|
16
|
+
metadata: dict[str, object] = field(default_factory=dict)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class RepositoryEvidence:
|
|
21
|
+
checkpoint_id: str | None
|
|
22
|
+
git_status: str
|
|
23
|
+
git_diff: str
|
|
24
|
+
workflows: tuple[object, ...] = ()
|
|
25
|
+
validation: dict[str, object] | None = None
|
|
26
|
+
metadata: dict[str, object] = field(default_factory=dict)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class RecoveryResult:
|
|
31
|
+
checkpoint_id: str
|
|
32
|
+
restored: bool
|
|
33
|
+
output: str
|
|
34
|
+
error: str = ""
|
core/contracts/resume.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Durable context required to resume a VYRELON workflow."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True)
|
|
7
|
+
class WorkflowResumeContext:
|
|
8
|
+
"""Serializable execution context recorded before a resumable boundary."""
|
|
9
|
+
|
|
10
|
+
workflow: str
|
|
11
|
+
work_unit_id: str
|
|
12
|
+
review_cycle: int
|
|
13
|
+
max_review_cycles: int
|
|
14
|
+
developer_id: str
|
|
15
|
+
tester_id: str
|
|
16
|
+
reviewer_ids: tuple[str, ...] = ()
|
|
17
|
+
model_ids: tuple[str, ...] = ()
|
|
18
|
+
artifact_ids: tuple[str, ...] = ()
|
|
19
|
+
findings: tuple[str, ...] = ()
|
|
20
|
+
last_agent_id: str | None = None
|
|
21
|
+
|
|
22
|
+
def to_metadata(self) -> dict[str, object]:
|
|
23
|
+
return {
|
|
24
|
+
"workflow": self.workflow,
|
|
25
|
+
"work_unit_id": self.work_unit_id,
|
|
26
|
+
"review_cycle": self.review_cycle,
|
|
27
|
+
"max_review_cycles": self.max_review_cycles,
|
|
28
|
+
"developer_id": self.developer_id,
|
|
29
|
+
"tester_id": self.tester_id,
|
|
30
|
+
"reviewer_ids": list(self.reviewer_ids),
|
|
31
|
+
"model_ids": list(self.model_ids),
|
|
32
|
+
"artifact_ids": list(self.artifact_ids),
|
|
33
|
+
"findings": list(self.findings),
|
|
34
|
+
"last_agent_id": self.last_agent_id,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def from_metadata(cls, data: dict[str, object]) -> "WorkflowResumeContext":
|
|
39
|
+
return cls(
|
|
40
|
+
workflow=str(data["workflow"]),
|
|
41
|
+
work_unit_id=str(data["work_unit_id"]),
|
|
42
|
+
review_cycle=int(data["review_cycle"]),
|
|
43
|
+
max_review_cycles=int(data["max_review_cycles"]),
|
|
44
|
+
developer_id=str(data["developer_id"]),
|
|
45
|
+
tester_id=str(data["tester_id"]),
|
|
46
|
+
reviewer_ids=tuple(str(v) for v in data.get("reviewer_ids", ())),
|
|
47
|
+
model_ids=tuple(str(v) for v in data.get("model_ids", ())),
|
|
48
|
+
artifact_ids=tuple(str(v) for v in data.get("artifact_ids", ())),
|
|
49
|
+
findings=tuple(str(v) for v in data.get("findings", ())),
|
|
50
|
+
last_agent_id=(
|
|
51
|
+
str(data["last_agent_id"])
|
|
52
|
+
if data.get("last_agent_id") is not None
|
|
53
|
+
else None
|
|
54
|
+
),
|
|
55
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Runtime contracts for executing work without coupling to a vendor."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Protocol
|
|
5
|
+
|
|
6
|
+
from core.contracts.agent import AgentContract
|
|
7
|
+
from core.contracts.work_unit import WorkUnit
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class ExecutionRequest:
|
|
12
|
+
agent: AgentContract
|
|
13
|
+
model_id: str
|
|
14
|
+
work_unit: WorkUnit
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class RuntimeExecutor(Protocol):
|
|
18
|
+
def execute(self, request: ExecutionRequest) -> object:
|
|
19
|
+
...
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Provider-neutral runtime contracts for the VYRELON core."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from enum import StrEnum
|
|
7
|
+
from typing import Protocol
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ToolSideEffect(StrEnum):
|
|
11
|
+
READ = "read"
|
|
12
|
+
WRITE = "write"
|
|
13
|
+
EXECUTE = "execute"
|
|
14
|
+
NETWORK = "network"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class RuntimeEventKind(StrEnum):
|
|
18
|
+
REQUEST = "request"
|
|
19
|
+
DELTA = "delta"
|
|
20
|
+
REASONING = "reasoning"
|
|
21
|
+
TOOL_CALL = "tool_call"
|
|
22
|
+
TOOL_RESULT = "tool_result"
|
|
23
|
+
MESSAGE = "message"
|
|
24
|
+
USAGE = "usage"
|
|
25
|
+
ERROR = "error"
|
|
26
|
+
COMPLETED = "completed"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class SessionSpec:
|
|
31
|
+
"""Continuity policy for an active VYRELON development session."""
|
|
32
|
+
|
|
33
|
+
id: str
|
|
34
|
+
project_root: str
|
|
35
|
+
agent_id: str | None = None
|
|
36
|
+
model_id: str | None = None
|
|
37
|
+
checkpoint_id: str | None = None
|
|
38
|
+
metadata: dict[str, object] = field(default_factory=dict)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class SessionState:
|
|
43
|
+
"""Persisted development-session state spanning multiple WorkUnits."""
|
|
44
|
+
|
|
45
|
+
spec: SessionSpec
|
|
46
|
+
work_unit_ids: list[str] = field(default_factory=list)
|
|
47
|
+
status: str = "active"
|
|
48
|
+
metadata: dict[str, object] = field(default_factory=dict)
|
|
49
|
+
|
|
50
|
+
@dataclass(frozen=True)
|
|
51
|
+
class ToolSpec:
|
|
52
|
+
"""Explicit executable capability exposed to an agent."""
|
|
53
|
+
|
|
54
|
+
id: str
|
|
55
|
+
description: str
|
|
56
|
+
side_effect: ToolSideEffect = ToolSideEffect.READ
|
|
57
|
+
permissions: frozenset[str] = frozenset()
|
|
58
|
+
input_schema: dict[str, object] = field(default_factory=dict)
|
|
59
|
+
metadata: dict[str, object] = field(default_factory=dict)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True)
|
|
63
|
+
class ToolRequest:
|
|
64
|
+
"""Normalized request to invoke a VYRELON tool."""
|
|
65
|
+
|
|
66
|
+
tool_id: str
|
|
67
|
+
arguments: dict[str, object] = field(default_factory=dict)
|
|
68
|
+
session_id: str | None = None
|
|
69
|
+
work_unit_id: str | None = None
|
|
70
|
+
metadata: dict[str, object] = field(default_factory=dict)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass(frozen=True)
|
|
74
|
+
class ToolResult:
|
|
75
|
+
"""Normalized result returned by a VYRELON tool."""
|
|
76
|
+
|
|
77
|
+
tool_id: str
|
|
78
|
+
ok: bool
|
|
79
|
+
output: object = None
|
|
80
|
+
error: str | None = None
|
|
81
|
+
metadata: dict[str, object] = field(default_factory=dict)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass(frozen=True)
|
|
85
|
+
class RuntimeEvent:
|
|
86
|
+
"""Normalized event emitted by an AI, tool, or execution runtime."""
|
|
87
|
+
|
|
88
|
+
kind: RuntimeEventKind
|
|
89
|
+
session_id: str | None = None
|
|
90
|
+
work_unit_id: str | None = None
|
|
91
|
+
payload: object = None
|
|
92
|
+
sequence: int | None = None
|
|
93
|
+
metadata: dict[str, object] = field(default_factory=dict)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class ProtocolAdapter(Protocol):
|
|
97
|
+
"""Translate between provider wire formats and VYRELON requests/events."""
|
|
98
|
+
|
|
99
|
+
def encode_request(self, request: object) -> object:
|
|
100
|
+
...
|
|
101
|
+
|
|
102
|
+
def decode_event(self, payload: object) -> RuntimeEvent:
|
|
103
|
+
...
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass(frozen=True)
|
|
107
|
+
class FallbackPolicy:
|
|
108
|
+
"""Ordered model/provider fallback behavior."""
|
|
109
|
+
|
|
110
|
+
model_ids: tuple[str, ...] = ()
|
|
111
|
+
max_attempts: int | None = None
|
|
112
|
+
retryable_errors: frozenset[str] = frozenset()
|
|
113
|
+
metadata: dict[str, object] = field(default_factory=dict)
|
|
114
|
+
|
|
115
|
+
def candidates(self) -> tuple[str, ...]:
|
|
116
|
+
if self.max_attempts is None:
|
|
117
|
+
return self.model_ids
|
|
118
|
+
return self.model_ids[: max(self.max_attempts, 0)]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Provider-neutral work unit contract for VYRELON."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from enum import Enum
|
|
5
|
+
|
|
6
|
+
class WorkStatus(str, Enum):
|
|
7
|
+
PENDING = "pending"
|
|
8
|
+
PLANNING = "planning"
|
|
9
|
+
EXECUTING = "executing"
|
|
10
|
+
VERIFYING = "verifying"
|
|
11
|
+
REVIEWING = "reviewing"
|
|
12
|
+
WAITING_HUMAN_APPROVAL = "waiting_human_approval"
|
|
13
|
+
HANDOFF = "handoff"
|
|
14
|
+
COMPLETED = "completed"
|
|
15
|
+
FAILED = "failed"
|
|
16
|
+
|
|
17
|
+
_ALLOWED_TRANSITIONS: dict[WorkStatus, frozenset[WorkStatus]] = {
|
|
18
|
+
WorkStatus.PENDING: frozenset({WorkStatus.PLANNING, WorkStatus.EXECUTING, WorkStatus.FAILED}),
|
|
19
|
+
WorkStatus.PLANNING: frozenset({WorkStatus.EXECUTING, WorkStatus.FAILED}),
|
|
20
|
+
WorkStatus.EXECUTING: frozenset({WorkStatus.VERIFYING, WorkStatus.COMPLETED, WorkStatus.FAILED}),
|
|
21
|
+
WorkStatus.VERIFYING: frozenset({WorkStatus.EXECUTING, WorkStatus.REVIEWING, WorkStatus.HANDOFF, WorkStatus.COMPLETED, WorkStatus.FAILED}),
|
|
22
|
+
WorkStatus.REVIEWING: frozenset({WorkStatus.EXECUTING, WorkStatus.WAITING_HUMAN_APPROVAL, WorkStatus.HANDOFF, WorkStatus.COMPLETED, WorkStatus.FAILED}),
|
|
23
|
+
WorkStatus.WAITING_HUMAN_APPROVAL: frozenset({WorkStatus.EXECUTING, WorkStatus.HANDOFF, WorkStatus.COMPLETED, WorkStatus.FAILED}),
|
|
24
|
+
WorkStatus.HANDOFF: frozenset({WorkStatus.EXECUTING, WorkStatus.COMPLETED, WorkStatus.FAILED}),
|
|
25
|
+
WorkStatus.COMPLETED: frozenset(),
|
|
26
|
+
WorkStatus.FAILED: frozenset({WorkStatus.EXECUTING}),
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class WorkUnit:
|
|
31
|
+
id: str
|
|
32
|
+
objective: str
|
|
33
|
+
status: WorkStatus = WorkStatus.PENDING
|
|
34
|
+
inputs: dict[str, object] = field(default_factory=dict)
|
|
35
|
+
artifacts: list[str] = field(default_factory=list)
|
|
36
|
+
assigned_agents: list[str] = field(default_factory=list)
|
|
37
|
+
metadata: dict[str, object] = field(default_factory=dict)
|
|
38
|
+
|
|
39
|
+
def assign(self, agent_id: str) -> None:
|
|
40
|
+
if agent_id not in self.assigned_agents:
|
|
41
|
+
self.assigned_agents.append(agent_id)
|
|
42
|
+
|
|
43
|
+
def transition(self, status: WorkStatus) -> None:
|
|
44
|
+
if status == self.status:
|
|
45
|
+
return
|
|
46
|
+
if status not in _ALLOWED_TRANSITIONS[self.status]:
|
|
47
|
+
raise ValueError(f"Invalid WorkUnit transition: {self.status.value} -> {status.value}")
|
|
48
|
+
self.status = status
|
core/delegation.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Agent delegation engine for VYRELON."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from core.contracts.agent import AgentContract
|
|
6
|
+
from core.contracts.ai import ModelSpec
|
|
7
|
+
from core.contracts.work_unit import WorkStatus, WorkUnit
|
|
8
|
+
from core.routing import AIRouter, Assignment, RoutingStrategy
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class Delegation:
|
|
13
|
+
work_unit_id: str
|
|
14
|
+
agent_id: str
|
|
15
|
+
assignment: Assignment
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DelegationEngine:
|
|
19
|
+
def __init__(self, router: AIRouter | None = None) -> None:
|
|
20
|
+
self.router = router or AIRouter()
|
|
21
|
+
|
|
22
|
+
def delegate(
|
|
23
|
+
self,
|
|
24
|
+
work_unit: WorkUnit,
|
|
25
|
+
agent: AgentContract,
|
|
26
|
+
models: list[ModelSpec],
|
|
27
|
+
preferred_model_ids: list[str] | None = None,
|
|
28
|
+
strategy: RoutingStrategy | str = RoutingStrategy.POOL,
|
|
29
|
+
) -> Delegation:
|
|
30
|
+
assignment = self.router.assign(agent, models, preferred_model_ids, strategy)
|
|
31
|
+
work_unit.assign(agent.id)
|
|
32
|
+
work_unit.transition(WorkStatus.EXECUTING)
|
|
33
|
+
return Delegation(work_unit.id, agent.id, assignment)
|