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
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
@@ -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 = ""
@@ -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)