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
multiagentos/cli.py
ADDED
|
@@ -0,0 +1,759 @@
|
|
|
1
|
+
"""Command-line entry point for VYRELON project bootstrap and execution."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import uuid
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from core.contracts.agent import AgentContract
|
|
11
|
+
from core.contracts.ai import ModelSpec
|
|
12
|
+
from core.contracts.work_unit import WorkStatus, WorkUnit
|
|
13
|
+
from runtime.execution_registry import resolve_execution_contracts
|
|
14
|
+
from runtime.execution_config import load_execution_config
|
|
15
|
+
from core.chat_agent_bridge import ChatAgentRequest
|
|
16
|
+
from installer.init import ProjectInitializer
|
|
17
|
+
from profiles.detector import ProfileDetector
|
|
18
|
+
from runtime.github_probe import probe
|
|
19
|
+
from runtime.process import ProcessRuntime
|
|
20
|
+
from runtime.status import project_status
|
|
21
|
+
from runtime.vyrelon import VYRELONRuntime
|
|
22
|
+
from runtime.agent.process import ProcessAgentExecutor
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _execution_contracts(root: Path, agent_override: str | None, model_override: str | None):
|
|
26
|
+
config = load_execution_config(root)
|
|
27
|
+
if config.runtime != "process":
|
|
28
|
+
raise ValueError(f"unsupported CLI execution runtime: {config.runtime}")
|
|
29
|
+
agent_id = agent_override or config.agent_id
|
|
30
|
+
model_id = model_override or config.model_id
|
|
31
|
+
agent, model = resolve_execution_contracts(agent_id, model_id)
|
|
32
|
+
return config, agent, [model]
|
|
33
|
+
|
|
34
|
+
def _chat_request(root: Path, objective: str, session_id: str | None) -> ChatAgentRequest:
|
|
35
|
+
inputs: dict[str, object] = {}
|
|
36
|
+
if session_id:
|
|
37
|
+
store = VYRELONRuntime().chat_session_store(root)
|
|
38
|
+
try:
|
|
39
|
+
session = store.load(session_id)
|
|
40
|
+
except FileNotFoundError:
|
|
41
|
+
session = None
|
|
42
|
+
if session is not None:
|
|
43
|
+
inputs["conversation"] = tuple(
|
|
44
|
+
{"role": turn.get("role"), "content": turn.get("content")}
|
|
45
|
+
for turn in session.turns
|
|
46
|
+
)
|
|
47
|
+
return ChatAgentRequest(objective=objective, inputs=inputs)
|
|
48
|
+
|
|
49
|
+
def _run_chat(
|
|
50
|
+
root: Path,
|
|
51
|
+
objective: str,
|
|
52
|
+
agent_override: str | None,
|
|
53
|
+
model_override: str | None,
|
|
54
|
+
session_id: str | None,
|
|
55
|
+
execute: bool = False,
|
|
56
|
+
exec_command: list[str] | None = None,
|
|
57
|
+
) -> int:
|
|
58
|
+
runtime = VYRELONRuntime()
|
|
59
|
+
configured_agent, configured_model = runtime.project_chat_agent(root)
|
|
60
|
+
agent_id = agent_override or configured_agent.id
|
|
61
|
+
agent = runtime.chat_agent_registry().get(agent_id)
|
|
62
|
+
model = model_override or (configured_model if agent_id == configured_agent.id else None)
|
|
63
|
+
|
|
64
|
+
if agent_id == configured_agent.id and model == configured_model:
|
|
65
|
+
_, adapter = runtime.project_chat_adapter(root)
|
|
66
|
+
else:
|
|
67
|
+
from runtime.chat_adapter_registry import resolve_project_chat_adapter
|
|
68
|
+
adapter = resolve_project_chat_adapter(agent, model=model)
|
|
69
|
+
|
|
70
|
+
session = None
|
|
71
|
+
if session_id:
|
|
72
|
+
store = runtime.chat_session_store(root)
|
|
73
|
+
try:
|
|
74
|
+
session = store.load(session_id)
|
|
75
|
+
except FileNotFoundError:
|
|
76
|
+
session = runtime.create_chat_session(session_id, chat_agent_id=agent.id)
|
|
77
|
+
|
|
78
|
+
request = _chat_request(root, objective, session_id)
|
|
79
|
+
|
|
80
|
+
if execute:
|
|
81
|
+
command = list(exec_command or [])
|
|
82
|
+
while command and command[0] == "--":
|
|
83
|
+
command.pop(0)
|
|
84
|
+
if not command:
|
|
85
|
+
raise SystemExit("chat --execute requires an explicit command after --")
|
|
86
|
+
config, execution_agent, models = _execution_contracts(root, None, model_override)
|
|
87
|
+
|
|
88
|
+
class CommandExecutor:
|
|
89
|
+
def execute(self, *, agent, model_id, work_unit):
|
|
90
|
+
result = ProcessRuntime(runtime.policy).run(
|
|
91
|
+
list(work_unit.inputs["command"]), cwd=str(root)
|
|
92
|
+
)
|
|
93
|
+
work_unit.metadata["process_returncode"] = result.returncode
|
|
94
|
+
work_unit.metadata["process_stdout"] = result.stdout
|
|
95
|
+
work_unit.metadata["process_stderr"] = result.stderr
|
|
96
|
+
if result.returncode != 0:
|
|
97
|
+
raise RuntimeError(f"command failed with exit code {result.returncode}")
|
|
98
|
+
return result
|
|
99
|
+
|
|
100
|
+
request = ChatAgentRequest(
|
|
101
|
+
objective=objective,
|
|
102
|
+
inputs={**(request.inputs or {}), "command": command},
|
|
103
|
+
work_unit_id=request.work_unit_id,
|
|
104
|
+
)
|
|
105
|
+
result = runtime.execute_chat_request(
|
|
106
|
+
request=request,
|
|
107
|
+
adapter=adapter,
|
|
108
|
+
agent=execution_agent,
|
|
109
|
+
models=models,
|
|
110
|
+
executor=CommandExecutor(),
|
|
111
|
+
chat_agent_id=agent.id,
|
|
112
|
+
preferred_model_ids=[models[0].id],
|
|
113
|
+
session=session,
|
|
114
|
+
project_root=root,
|
|
115
|
+
)
|
|
116
|
+
if session is not None:
|
|
117
|
+
session.add_turn("user", objective)
|
|
118
|
+
session.add_turn("assistant", result.chat_response.summary)
|
|
119
|
+
session.metadata["last_plan_steps"] = [step.id for step in result.plan.steps]
|
|
120
|
+
runtime.chat_session_store(root).save(session)
|
|
121
|
+
print(json.dumps({
|
|
122
|
+
"mode": "execute",
|
|
123
|
+
"chat_agent_id": agent.id,
|
|
124
|
+
"provider": agent.provider.value,
|
|
125
|
+
"summary": result.chat_response.summary,
|
|
126
|
+
"work_unit_id": result.work_unit.id,
|
|
127
|
+
"status": result.work_unit.status.value,
|
|
128
|
+
"execution_agent_id": execution_agent.id,
|
|
129
|
+
"execution_model_id": result.orchestration.delegation.assignment.model_id,
|
|
130
|
+
"execution_evidence": result.work_unit.metadata.get("execution_evidence", []),
|
|
131
|
+
"verification_evidence": result.work_unit.metadata.get("verification_evidence", []),
|
|
132
|
+
"review_evidence": result.work_unit.metadata.get("review_evidence", []),
|
|
133
|
+
"session_id": session.id if session is not None else None,
|
|
134
|
+
}, indent=2, ensure_ascii=False))
|
|
135
|
+
return 0
|
|
136
|
+
|
|
137
|
+
_, plan, response = runtime.chat_request(request, adapter, agent_id=agent.id)
|
|
138
|
+
if session is None and session_id:
|
|
139
|
+
session = runtime.create_chat_session(session_id, chat_agent_id=agent.id)
|
|
140
|
+
if session is not None:
|
|
141
|
+
session.add_turn("user", objective)
|
|
142
|
+
session.add_turn("assistant", response.summary)
|
|
143
|
+
session.metadata["last_plan_steps"] = [step.id for step in plan.steps]
|
|
144
|
+
runtime.chat_session_store(root).save(session)
|
|
145
|
+
|
|
146
|
+
print(json.dumps({
|
|
147
|
+
"mode": "chat",
|
|
148
|
+
"chat_agent_id": agent.id,
|
|
149
|
+
"provider": agent.provider.value,
|
|
150
|
+
"summary": response.summary,
|
|
151
|
+
"steps": [
|
|
152
|
+
{"id": step.id, "objective": step.objective, "agent_id": step.agent_id, "depends_on": list(step.depends_on)}
|
|
153
|
+
for step in plan.steps
|
|
154
|
+
],
|
|
155
|
+
"findings": list(response.findings),
|
|
156
|
+
"artifacts": list(response.artifacts),
|
|
157
|
+
"evidence": list(response.evidence),
|
|
158
|
+
"session_id": session.id if session is not None else None,
|
|
159
|
+
}, indent=2, ensure_ascii=False))
|
|
160
|
+
return 0
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _work_state(root: Path):
|
|
164
|
+
from runtime.vyrelon import VYRELONRuntime
|
|
165
|
+
|
|
166
|
+
return VYRELONRuntime().state_store(root)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _provider_runtime(root: Path):
|
|
170
|
+
from runtime.vyrelon import VYRELONRuntime
|
|
171
|
+
|
|
172
|
+
runtime = VYRELONRuntime()
|
|
173
|
+
runtime.load_project_provider_config(root)
|
|
174
|
+
return runtime
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _run_process(root: Path, work: WorkUnit, command: list[str]) -> int:
|
|
178
|
+
store = _work_state(root)
|
|
179
|
+
work.metadata["command"] = command
|
|
180
|
+
if work.status == WorkStatus.FAILED:
|
|
181
|
+
work.status = WorkStatus.EXECUTING
|
|
182
|
+
elif work.status != WorkStatus.EXECUTING:
|
|
183
|
+
work.transition(WorkStatus.EXECUTING)
|
|
184
|
+
store.save(work)
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
result = ProcessRuntime().run(command, cwd=str(root))
|
|
188
|
+
work.metadata["returncode"] = result.returncode
|
|
189
|
+
work.metadata["stdout"] = result.stdout
|
|
190
|
+
work.metadata["stderr"] = result.stderr
|
|
191
|
+
if result.returncode == 0:
|
|
192
|
+
work.transition(WorkStatus.COMPLETED)
|
|
193
|
+
store.save(work)
|
|
194
|
+
print(result.stdout, end="")
|
|
195
|
+
return 0
|
|
196
|
+
|
|
197
|
+
work.transition(WorkStatus.FAILED)
|
|
198
|
+
store.save(work)
|
|
199
|
+
print(result.stdout, end="")
|
|
200
|
+
print(result.stderr, end="")
|
|
201
|
+
return result.returncode
|
|
202
|
+
except Exception as exc:
|
|
203
|
+
work.metadata["error"] = str(exc)
|
|
204
|
+
if work.status not in {WorkStatus.FAILED, WorkStatus.COMPLETED}:
|
|
205
|
+
work.transition(WorkStatus.FAILED)
|
|
206
|
+
store.save(work)
|
|
207
|
+
raise
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
211
|
+
parser = argparse.ArgumentParser(prog="multiagentos")
|
|
212
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
213
|
+
|
|
214
|
+
detect = subparsers.add_parser("detect", help="detect technology profiles")
|
|
215
|
+
detect.add_argument("path", nargs="?", default=".")
|
|
216
|
+
|
|
217
|
+
profile = subparsers.add_parser("profile", help="show resolved VYRELON project/agent profiles")
|
|
218
|
+
profile.add_argument("path", nargs="?", default=".")
|
|
219
|
+
|
|
220
|
+
init = subparsers.add_parser("init", help="initialize VYRELON in a project")
|
|
221
|
+
init.add_argument("path", nargs="?", default=".")
|
|
222
|
+
init.add_argument("--component", choices=("vyrelon", "multi-agent", "all"), default="all")
|
|
223
|
+
|
|
224
|
+
providers = subparsers.add_parser(
|
|
225
|
+
"providers", help="inspect configured AI providers and models"
|
|
226
|
+
)
|
|
227
|
+
provider_sub = providers.add_subparsers(dest="providers_command", required=True)
|
|
228
|
+
providers_list = provider_sub.add_parser("list", help="list configured providers/models")
|
|
229
|
+
providers_list.add_argument("path", nargs="?", default=".")
|
|
230
|
+
providers_validate = provider_sub.add_parser(
|
|
231
|
+
"validate", help="validate provider configuration and environment credentials"
|
|
232
|
+
)
|
|
233
|
+
providers_validate.add_argument("path", nargs="?", default=".")
|
|
234
|
+
|
|
235
|
+
models = subparsers.add_parser("models", help="execute configured AI models")
|
|
236
|
+
model_sub = models.add_subparsers(dest="models_command", required=True)
|
|
237
|
+
models_quota = model_sub.add_parser("quota", help="show observed and estimated model quota")
|
|
238
|
+
models_quota.add_argument("path", nargs="?", default=".")
|
|
239
|
+
models_health = model_sub.add_parser("health", help="show model health and cooldown state")
|
|
240
|
+
models_health.add_argument("path", nargs="?", default=".")
|
|
241
|
+
models_capabilities = model_sub.add_parser("capabilities", help="show normalized model capabilities")
|
|
242
|
+
models_capabilities.add_argument("path", nargs="?", default=".")
|
|
243
|
+
models_discover = model_sub.add_parser("discover", help="refresh provider model capabilities and quota")
|
|
244
|
+
models_discover.add_argument("path", nargs="?", default=".")
|
|
245
|
+
models_discover.add_argument("--no-refresh", action="store_true")
|
|
246
|
+
models_control = model_sub.add_parser("control", help="show unified model control-plane state")
|
|
247
|
+
models_control.add_argument("path", nargs="?", default=".")
|
|
248
|
+
models_control.add_argument("--events", action="store_true", help="include recent control-plane events")
|
|
249
|
+
models_explain = model_sub.add_parser("explain", help="explain model routing eligibility and selection")
|
|
250
|
+
models_explain.add_argument("path", nargs="?", default=".")
|
|
251
|
+
models_explain.add_argument("--agent", default="executor")
|
|
252
|
+
models_explain.add_argument("--model", action="append", dest="preferred_models", default=None)
|
|
253
|
+
models_explain.add_argument("--strategy", choices=("explicit", "pool", "auto"), default="pool")
|
|
254
|
+
|
|
255
|
+
models_run = model_sub.add_parser("run", help="run a configured model through VYRELON")
|
|
256
|
+
models_run.add_argument("path", nargs="?", default=".")
|
|
257
|
+
models_run.add_argument("--model", required=True)
|
|
258
|
+
models_run.add_argument("--objective", required=True)
|
|
259
|
+
models_run.add_argument("--agent", default="executor")
|
|
260
|
+
models_run.add_argument("--apply-changes", action="store_true")
|
|
261
|
+
models_run.add_argument("--validate", action="append", default=[])
|
|
262
|
+
models_run.add_argument("--mcp", action="append", default=[])
|
|
263
|
+
|
|
264
|
+
run = subparsers.add_parser("run", help="create and execute a persistent WorkUnit")
|
|
265
|
+
run.add_argument("path_arg", nargs="?", default=None)
|
|
266
|
+
run.add_argument("--path", dest="path", default=None)
|
|
267
|
+
run.add_argument("--objective", required=True)
|
|
268
|
+
run.add_argument("--agent", default="executor")
|
|
269
|
+
run.add_argument("--id", dest="work_unit_id")
|
|
270
|
+
run.add_argument("--model", help="pin a model; omit to route automatically")
|
|
271
|
+
run.add_argument("--apply-changes", action="store_true")
|
|
272
|
+
run.add_argument("--validate", action="append", default=[])
|
|
273
|
+
run.add_argument("--mcp", action="append", default=[])
|
|
274
|
+
run.add_argument("--command", dest="process_command", nargs=argparse.REMAINDER)
|
|
275
|
+
|
|
276
|
+
resume = subparsers.add_parser("resume", help="resume a persisted WorkUnit")
|
|
277
|
+
resume.add_argument("work_unit_id")
|
|
278
|
+
resume.add_argument("path_arg", nargs="?", default=None)
|
|
279
|
+
resume.add_argument("--path", dest="path", default=None)
|
|
280
|
+
|
|
281
|
+
status = subparsers.add_parser("status", help="show project state or a persisted WorkUnit")
|
|
282
|
+
status.add_argument("target", nargs="?", default=".")
|
|
283
|
+
status.add_argument("path", nargs="?", default=None)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
chat = subparsers.add_parser("chat", help="send a request to the project's configured Chat Agent")
|
|
288
|
+
chat.add_argument("--path", default=".")
|
|
289
|
+
chat.add_argument("--objective", required=True, help="request for the Chat Agent")
|
|
290
|
+
chat.add_argument("--agent", help="override the configured Chat Agent id")
|
|
291
|
+
chat.add_argument("--model", help="override the configured Chat Agent model")
|
|
292
|
+
chat.add_argument("--session", help="persist conversation turns in this session id")
|
|
293
|
+
chat.add_argument("--execute", action="store_true", help="execute the explicit command after -- through VYRELON")
|
|
294
|
+
chat.add_argument("exec_command", nargs=argparse.REMAINDER, help="explicit command after -- when using --execute")
|
|
295
|
+
|
|
296
|
+
mcp = subparsers.add_parser("mcp", help="serve or inspect VYRELON MCP")
|
|
297
|
+
mcp_sub = mcp.add_subparsers(dest="mcp_command", required=True)
|
|
298
|
+
mcp_serve = mcp_sub.add_parser("serve", help="run VYRELON as a stdio MCP server")
|
|
299
|
+
mcp_serve.add_argument("--path", default=".")
|
|
300
|
+
mcp_serve.add_argument("--allow-write", action="store_true", help="expose filesystem write and patch tools")
|
|
301
|
+
mcp_serve.add_argument("--allow-process", action="store_true", help="expose shell execution")
|
|
302
|
+
|
|
303
|
+
github = subparsers.add_parser("github", help="use VYRELON GitHub runtime")
|
|
304
|
+
github_sub = github.add_subparsers(dest="github_command", required=True)
|
|
305
|
+
probe_parser = github_sub.add_parser(
|
|
306
|
+
"probe", help="verify GitHub access for a repository"
|
|
307
|
+
)
|
|
308
|
+
probe_parser.add_argument("repository")
|
|
309
|
+
|
|
310
|
+
return parser
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _provider_list(root: Path) -> int:
|
|
314
|
+
runtime = _provider_runtime(root)
|
|
315
|
+
payload = [
|
|
316
|
+
{
|
|
317
|
+
"id": provider.id,
|
|
318
|
+
"kind": provider.kind,
|
|
319
|
+
"models": [
|
|
320
|
+
{
|
|
321
|
+
"id": model.id,
|
|
322
|
+
"capabilities": sorted(model.capabilities),
|
|
323
|
+
"adapter_id": model.metadata.get("adapter_id"),
|
|
324
|
+
}
|
|
325
|
+
for model in provider.models
|
|
326
|
+
],
|
|
327
|
+
}
|
|
328
|
+
for provider in runtime.providers.providers()
|
|
329
|
+
]
|
|
330
|
+
print(json.dumps(payload, indent=2))
|
|
331
|
+
return 0
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _model_quota(root: Path) -> int:
|
|
336
|
+
runtime = _provider_runtime(root)
|
|
337
|
+
store = runtime.quota_store(root)
|
|
338
|
+
payload = []
|
|
339
|
+
for model in runtime.configured_models():
|
|
340
|
+
if not store.exists(model.id):
|
|
341
|
+
payload.append({
|
|
342
|
+
"model": model.id,
|
|
343
|
+
"provider": model.provider_id,
|
|
344
|
+
"status": "unknown",
|
|
345
|
+
"confidence": "unknown",
|
|
346
|
+
"dimensions": {},
|
|
347
|
+
})
|
|
348
|
+
continue
|
|
349
|
+
snapshot = store.load(model.id)
|
|
350
|
+
payload.append({
|
|
351
|
+
"model": snapshot.model_id,
|
|
352
|
+
"provider": snapshot.provider_id,
|
|
353
|
+
"scope": snapshot.scope,
|
|
354
|
+
"observed_at": snapshot.observed_at.isoformat(),
|
|
355
|
+
"confidence": snapshot.confidence.value,
|
|
356
|
+
"dimensions": {
|
|
357
|
+
item.name: {
|
|
358
|
+
"limit": item.limit,
|
|
359
|
+
"used": item.used,
|
|
360
|
+
"remaining": item.remaining,
|
|
361
|
+
"reset_at": item.reset_at.isoformat() if item.reset_at else None,
|
|
362
|
+
"confidence": item.confidence.value,
|
|
363
|
+
"source": item.source,
|
|
364
|
+
}
|
|
365
|
+
for item in snapshot.dimensions
|
|
366
|
+
},
|
|
367
|
+
})
|
|
368
|
+
print(json.dumps(payload, indent=2))
|
|
369
|
+
return 0
|
|
370
|
+
|
|
371
|
+
def _model_health(root: Path) -> int:
|
|
372
|
+
runtime = _provider_runtime(root)
|
|
373
|
+
registry = runtime.health_registry(root)
|
|
374
|
+
payload = []
|
|
375
|
+
for model in runtime.configured_models():
|
|
376
|
+
health = registry.get(model.id)
|
|
377
|
+
if health is None:
|
|
378
|
+
payload.append({
|
|
379
|
+
"model": model.id,
|
|
380
|
+
"provider": model.provider_id,
|
|
381
|
+
"status": "healthy",
|
|
382
|
+
"available": True,
|
|
383
|
+
"consecutive_failures": 0,
|
|
384
|
+
"successes": 0,
|
|
385
|
+
})
|
|
386
|
+
continue
|
|
387
|
+
payload.append({
|
|
388
|
+
"model": health.model_id,
|
|
389
|
+
"provider": health.provider_id,
|
|
390
|
+
"status": health.status.value,
|
|
391
|
+
"available": health.available,
|
|
392
|
+
"consecutive_failures": health.consecutive_failures,
|
|
393
|
+
"successes": health.successes,
|
|
394
|
+
"last_error": health.last_error,
|
|
395
|
+
"last_failure_at": health.last_failure_at.isoformat() if health.last_failure_at else None,
|
|
396
|
+
"last_success_at": health.last_success_at.isoformat() if health.last_success_at else None,
|
|
397
|
+
"cooldown_until": health.cooldown_until.isoformat() if health.cooldown_until else None,
|
|
398
|
+
})
|
|
399
|
+
print(json.dumps(payload, indent=2))
|
|
400
|
+
return 0
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def _model_capabilities(root: Path) -> int:
|
|
404
|
+
runtime = _provider_runtime(root)
|
|
405
|
+
registry = runtime.capability_registry(root)
|
|
406
|
+
payload = []
|
|
407
|
+
for model in runtime.configured_models():
|
|
408
|
+
profile = registry.profile(model)
|
|
409
|
+
payload.append({
|
|
410
|
+
"model": profile.model_id,
|
|
411
|
+
"provider": profile.provider_id,
|
|
412
|
+
"capabilities": sorted(profile.capabilities),
|
|
413
|
+
"confidence": profile.confidence.value,
|
|
414
|
+
"source": profile.source,
|
|
415
|
+
})
|
|
416
|
+
print(json.dumps(payload, indent=2))
|
|
417
|
+
return 0
|
|
418
|
+
|
|
419
|
+
def _model_control(root: Path, include_events: bool = False) -> int:
|
|
420
|
+
runtime = _provider_runtime(root)
|
|
421
|
+
control = runtime.model_control_plane(root)
|
|
422
|
+
models = runtime.configured_models()
|
|
423
|
+
payload = {
|
|
424
|
+
"models": list(control.dashboard(models)),
|
|
425
|
+
}
|
|
426
|
+
if include_events:
|
|
427
|
+
payload["events"] = list(control.events.recent())
|
|
428
|
+
print(json.dumps(payload, indent=2, default=str))
|
|
429
|
+
return 0
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def _model_explain(root: Path, agent_id: str, preferred_models=None, strategy: str = "pool") -> int:
|
|
433
|
+
runtime = _provider_runtime(root)
|
|
434
|
+
explanation = runtime.explain_model_routing(
|
|
435
|
+
root,
|
|
436
|
+
agent_id=agent_id,
|
|
437
|
+
preferred_model_ids=preferred_models,
|
|
438
|
+
routing_strategy=strategy,
|
|
439
|
+
)
|
|
440
|
+
print(json.dumps({
|
|
441
|
+
"agent": explanation.agent_id,
|
|
442
|
+
"strategy": explanation.strategy.value,
|
|
443
|
+
"selected_model": explanation.selected_model_id,
|
|
444
|
+
"candidates": [
|
|
445
|
+
{
|
|
446
|
+
"model": candidate.model_id,
|
|
447
|
+
"provider": candidate.provider_id,
|
|
448
|
+
"selected": candidate.selected,
|
|
449
|
+
"compatible": candidate.compatible,
|
|
450
|
+
"health_available": candidate.health_available,
|
|
451
|
+
"quota_available": candidate.quota_available,
|
|
452
|
+
"capability_score": candidate.capability_score,
|
|
453
|
+
"quota_score": candidate.quota_score,
|
|
454
|
+
"missing_capabilities": sorted(candidate.missing_capabilities),
|
|
455
|
+
"rejection_reasons": list(candidate.rejection_reasons),
|
|
456
|
+
}
|
|
457
|
+
for candidate in explanation.candidates
|
|
458
|
+
],
|
|
459
|
+
}, indent=2, ensure_ascii=False))
|
|
460
|
+
return 0
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def _model_run(root: Path, model_id: str, objective: str, agent_id: str, *, apply_changes: bool = False, validation_commands: list[str] | None = None, mcp_server_ids: list[str] | None = None) -> int:
|
|
464
|
+
from runtime.vyrelon import VYRELONRuntime
|
|
465
|
+
|
|
466
|
+
runtime = _provider_runtime(root)
|
|
467
|
+
if not runtime.providers.list_model_ids():
|
|
468
|
+
raise ValueError("No configured models found")
|
|
469
|
+
result = runtime.run_configured_work(
|
|
470
|
+
root,
|
|
471
|
+
objective=objective,
|
|
472
|
+
agent_id=agent_id,
|
|
473
|
+
preferred_model_ids=[model_id],
|
|
474
|
+
apply_changes=apply_changes,
|
|
475
|
+
validation_commands=tuple(validation_commands or ()),
|
|
476
|
+
mcp_server_ids=tuple(mcp_server_ids or ()),
|
|
477
|
+
)
|
|
478
|
+
print(result.output.text, end="")
|
|
479
|
+
return 0
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def _provider_validate(root: Path) -> int:
|
|
483
|
+
runtime = _provider_runtime(root)
|
|
484
|
+
payload = []
|
|
485
|
+
valid = True
|
|
486
|
+
for model in runtime.configured_models():
|
|
487
|
+
checks = runtime.credential_checks().get(model.id, ())
|
|
488
|
+
missing = [check.environment_variable for check in checks if not check.present]
|
|
489
|
+
if missing:
|
|
490
|
+
valid = False
|
|
491
|
+
payload.append(
|
|
492
|
+
{
|
|
493
|
+
"model": model.id,
|
|
494
|
+
"provider": model.provider_id,
|
|
495
|
+
"adapter_id": model.metadata.get("adapter_id"),
|
|
496
|
+
"credential_environment_variables": [
|
|
497
|
+
{
|
|
498
|
+
"name": check.environment_variable,
|
|
499
|
+
"present": check.present,
|
|
500
|
+
}
|
|
501
|
+
for check in checks
|
|
502
|
+
],
|
|
503
|
+
"valid": not missing,
|
|
504
|
+
}
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
print(json.dumps({"valid": valid, "models": payload}, indent=2))
|
|
508
|
+
return 0 if valid else 1
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def main(argv: list[str] | None = None) -> int:
|
|
512
|
+
if argv is not None:
|
|
513
|
+
argv = list(argv)
|
|
514
|
+
if argv and argv[0] == "run" and "--" in argv:
|
|
515
|
+
argv[argv.index("--")] = "--command"
|
|
516
|
+
args = build_parser().parse_args(argv)
|
|
517
|
+
|
|
518
|
+
if args.command == "status":
|
|
519
|
+
root = Path(args.path or args.target).expanduser().resolve()
|
|
520
|
+
print(json.dumps(project_status(root), indent=2, ensure_ascii=False))
|
|
521
|
+
return 0
|
|
522
|
+
|
|
523
|
+
if args.command == "chat":
|
|
524
|
+
root = Path(args.path).expanduser().resolve()
|
|
525
|
+
return _run_chat(root, args.objective, args.agent, args.model, args.session, args.execute, args.exec_command)
|
|
526
|
+
|
|
527
|
+
if args.command == "mcp" and args.mcp_command == "serve":
|
|
528
|
+
from runtime.mcp.server import VYRELONMCPServer
|
|
529
|
+
root = Path(args.path).expanduser().resolve()
|
|
530
|
+
VYRELONMCPServer(
|
|
531
|
+
root,
|
|
532
|
+
allow_write=args.allow_write,
|
|
533
|
+
allow_process=args.allow_process,
|
|
534
|
+
).serve_forever()
|
|
535
|
+
return 0
|
|
536
|
+
|
|
537
|
+
if args.command == "github" and args.github_command == "probe":
|
|
538
|
+
print(json.dumps(probe(args.repository), indent=2))
|
|
539
|
+
return 0
|
|
540
|
+
|
|
541
|
+
if args.command == "profile":
|
|
542
|
+
root = Path(args.path).expanduser().resolve()
|
|
543
|
+
from runtime.vyrelon import VYRELONRuntime
|
|
544
|
+
project, agents = VYRELONRuntime().profiles(root)
|
|
545
|
+
print(
|
|
546
|
+
json.dumps(
|
|
547
|
+
{
|
|
548
|
+
"project": {
|
|
549
|
+
"id": project.id,
|
|
550
|
+
"root": project.root,
|
|
551
|
+
"technology_profiles": list(project.technology_profile_ids),
|
|
552
|
+
"agent_profiles": list(project.agent_profile_ids),
|
|
553
|
+
"metadata": project.metadata,
|
|
554
|
+
},
|
|
555
|
+
"agents": [
|
|
556
|
+
{
|
|
557
|
+
"id": agent.id,
|
|
558
|
+
"role": agent.role,
|
|
559
|
+
"profiles": list(agent.profile_ids),
|
|
560
|
+
"capabilities": sorted(agent.capabilities),
|
|
561
|
+
"tools": sorted(agent.tools),
|
|
562
|
+
"permissions": sorted(agent.permissions),
|
|
563
|
+
"model_ids": list(agent.model_ids),
|
|
564
|
+
}
|
|
565
|
+
for agent in agents
|
|
566
|
+
],
|
|
567
|
+
},
|
|
568
|
+
indent=2,
|
|
569
|
+
)
|
|
570
|
+
)
|
|
571
|
+
return 0
|
|
572
|
+
|
|
573
|
+
if args.command == "providers":
|
|
574
|
+
root = Path(args.path).expanduser().resolve()
|
|
575
|
+
if args.providers_command == "list":
|
|
576
|
+
return _provider_list(root)
|
|
577
|
+
return _provider_validate(root)
|
|
578
|
+
|
|
579
|
+
if args.command == "models":
|
|
580
|
+
root = Path(args.path).expanduser().resolve()
|
|
581
|
+
if args.models_command == "quota":
|
|
582
|
+
return _model_quota(root)
|
|
583
|
+
if args.models_command == "health":
|
|
584
|
+
return _model_health(root)
|
|
585
|
+
if args.models_command == "capabilities":
|
|
586
|
+
return _model_capabilities(root)
|
|
587
|
+
if args.models_command == "discover":
|
|
588
|
+
runtime = _provider_runtime(root)
|
|
589
|
+
print(json.dumps(list(runtime.discover_models(root, refresh=not args.no_refresh)), indent=2, default=str))
|
|
590
|
+
return 0
|
|
591
|
+
if args.models_command == "control":
|
|
592
|
+
return _model_control(root, args.events)
|
|
593
|
+
if args.models_command == "explain":
|
|
594
|
+
return _model_explain(root, args.agent, args.preferred_models, args.strategy)
|
|
595
|
+
if args.models_command == "run":
|
|
596
|
+
return _model_run(
|
|
597
|
+
root,
|
|
598
|
+
args.model,
|
|
599
|
+
args.objective,
|
|
600
|
+
args.agent,
|
|
601
|
+
apply_changes=args.apply_changes,
|
|
602
|
+
validation_commands=args.validate,
|
|
603
|
+
mcp_server_ids=args.mcp,
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
root = Path(getattr(args, "path", None) or getattr(args, "path_arg", None) or ".").expanduser().resolve()
|
|
607
|
+
|
|
608
|
+
if args.command == "run":
|
|
609
|
+
work = WorkUnit(
|
|
610
|
+
id=args.work_unit_id or uuid.uuid4().hex,
|
|
611
|
+
objective=args.objective,
|
|
612
|
+
)
|
|
613
|
+
agent = AgentContract(
|
|
614
|
+
id=args.agent,
|
|
615
|
+
role=args.agent,
|
|
616
|
+
capabilities=frozenset({"execution"}),
|
|
617
|
+
tools=frozenset({"process"}) if args.process_command else frozenset(),
|
|
618
|
+
)
|
|
619
|
+
print(f"WorkUnit: {work.id}")
|
|
620
|
+
try:
|
|
621
|
+
from runtime.vyrelon import VYRELONRuntime
|
|
622
|
+
runtime = VYRELONRuntime()
|
|
623
|
+
if not args.process_command:
|
|
624
|
+
work.metadata["runtime"] = "configured-model"
|
|
625
|
+
work.metadata["agent_id"] = args.agent
|
|
626
|
+
if args.model:
|
|
627
|
+
work.metadata["model_id"] = args.model
|
|
628
|
+
result = runtime.run_configured_work(
|
|
629
|
+
root,
|
|
630
|
+
objective=args.objective,
|
|
631
|
+
agent_id=args.agent,
|
|
632
|
+
work_unit_id=work.id,
|
|
633
|
+
preferred_model_ids=[args.model] if args.model else None,
|
|
634
|
+
apply_changes=args.apply_changes,
|
|
635
|
+
validation_commands=tuple(args.validate),
|
|
636
|
+
mcp_server_ids=tuple(args.mcp),
|
|
637
|
+
)
|
|
638
|
+
print(result.output.text, end="")
|
|
639
|
+
return 0
|
|
640
|
+
|
|
641
|
+
model = ModelSpec(
|
|
642
|
+
id=args.model or "local-process",
|
|
643
|
+
provider_id="vyrelon-local",
|
|
644
|
+
capabilities=frozenset({"execution"}),
|
|
645
|
+
)
|
|
646
|
+
work.metadata["command"] = args.process_command
|
|
647
|
+
work.metadata["runtime"] = "local-process"
|
|
648
|
+
work.metadata["execution_agent_id"] = agent.id
|
|
649
|
+
work.metadata["execution_model_id"] = model.id
|
|
650
|
+
result = runtime.run_persistent(
|
|
651
|
+
root,
|
|
652
|
+
work,
|
|
653
|
+
agent,
|
|
654
|
+
[model],
|
|
655
|
+
ProcessAgentExecutor(args.process_command),
|
|
656
|
+
preferred_model_ids=[model.id],
|
|
657
|
+
)
|
|
658
|
+
if result.output.returncode == 0:
|
|
659
|
+
print(result.output.stdout, end="")
|
|
660
|
+
return 0
|
|
661
|
+
print(result.output.stdout, end="")
|
|
662
|
+
print(result.output.stderr, end="")
|
|
663
|
+
return result.output.returncode
|
|
664
|
+
except Exception as exc:
|
|
665
|
+
print(str(exc))
|
|
666
|
+
return 1
|
|
667
|
+
|
|
668
|
+
if args.command == "resume":
|
|
669
|
+
work = _work_state(root).load(args.work_unit_id)
|
|
670
|
+
if work.status not in {WorkStatus.EXECUTING, WorkStatus.FAILED}:
|
|
671
|
+
raise ValueError(
|
|
672
|
+
f"WorkUnit {work.id} is not resumable from status {work.status.value}"
|
|
673
|
+
)
|
|
674
|
+
agent_id = str(work.metadata.get("agent_id", work.assigned_agents[-1] if work.assigned_agents else "executor"))
|
|
675
|
+
from runtime.vyrelon import VYRELONRuntime
|
|
676
|
+
runtime = VYRELONRuntime()
|
|
677
|
+
if work.metadata.get("runtime") == "configured-model":
|
|
678
|
+
model_id = str(work.metadata.get("model_id", ""))
|
|
679
|
+
if not model_id:
|
|
680
|
+
raise ValueError(f"WorkUnit {work.id} has no persisted model id")
|
|
681
|
+
agent = AgentContract(
|
|
682
|
+
id=agent_id,
|
|
683
|
+
role=agent_id,
|
|
684
|
+
capabilities=frozenset({"execution"}),
|
|
685
|
+
)
|
|
686
|
+
result = runtime.run_persistent_registered_model(
|
|
687
|
+
root,
|
|
688
|
+
work,
|
|
689
|
+
agent,
|
|
690
|
+
preferred_model_ids=[model_id],
|
|
691
|
+
)
|
|
692
|
+
print(result.output.text, end="")
|
|
693
|
+
return 0
|
|
694
|
+
|
|
695
|
+
command = list(work.metadata.get("command", []))
|
|
696
|
+
if not command:
|
|
697
|
+
raise ValueError(f"WorkUnit {work.id} has no persisted command")
|
|
698
|
+
agent = AgentContract(
|
|
699
|
+
id=agent_id,
|
|
700
|
+
role=agent_id,
|
|
701
|
+
capabilities=frozenset({"execution"}),
|
|
702
|
+
tools=frozenset({"process"}),
|
|
703
|
+
)
|
|
704
|
+
model = ModelSpec(
|
|
705
|
+
id="local-process",
|
|
706
|
+
provider_id="vyrelon-local",
|
|
707
|
+
capabilities=frozenset({"execution"}),
|
|
708
|
+
)
|
|
709
|
+
result = runtime.run_persistent(
|
|
710
|
+
root,
|
|
711
|
+
work,
|
|
712
|
+
agent,
|
|
713
|
+
[model],
|
|
714
|
+
ProcessAgentExecutor(command),
|
|
715
|
+
preferred_model_ids=["local-process"],
|
|
716
|
+
)
|
|
717
|
+
if result.output.returncode == 0:
|
|
718
|
+
print(result.output.stdout, end="")
|
|
719
|
+
return 0
|
|
720
|
+
print(result.output.stdout, end="")
|
|
721
|
+
print(result.output.stderr, end="")
|
|
722
|
+
return result.output.returncode
|
|
723
|
+
|
|
724
|
+
if args.command == "status":
|
|
725
|
+
target = Path(args.target).expanduser().resolve() if args.path is None else Path(args.path).expanduser().resolve()
|
|
726
|
+
if args.path is None and target.is_dir():
|
|
727
|
+
print(json.dumps(project_status(target), indent=2, ensure_ascii=False))
|
|
728
|
+
return 0
|
|
729
|
+
work_id = args.target
|
|
730
|
+
work = _work_state(target).load(work_id)
|
|
731
|
+
print(json.dumps({"id": work.id, "objective": work.objective, "status": work.status.value, "assigned_agents": work.assigned_agents, "metadata": work.metadata}, indent=2))
|
|
732
|
+
return 0
|
|
733
|
+
|
|
734
|
+
detector = ProfileDetector()
|
|
735
|
+
detections = detector.detect(root)
|
|
736
|
+
|
|
737
|
+
if args.command == "detect":
|
|
738
|
+
print(
|
|
739
|
+
json.dumps(
|
|
740
|
+
[
|
|
741
|
+
{
|
|
742
|
+
"profile": result.profile_id,
|
|
743
|
+
"confidence": result.confidence,
|
|
744
|
+
"evidence": list(result.evidence),
|
|
745
|
+
}
|
|
746
|
+
for result in detections
|
|
747
|
+
],
|
|
748
|
+
indent=2,
|
|
749
|
+
)
|
|
750
|
+
)
|
|
751
|
+
return 0
|
|
752
|
+
|
|
753
|
+
config = ProjectInitializer().apply(root, detections, component=args.component)
|
|
754
|
+
print(f"Initialized VYRELON: {config}")
|
|
755
|
+
return 0
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
if __name__ == "__main__":
|
|
759
|
+
raise SystemExit(main())
|