agent-devkit 0.1.5 → 0.1.6
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.
- package/README.md +2 -2
- package/package.json +1 -1
- package/runtime/README.md +74 -5
- package/runtime/agents/README.md +8 -0
- package/runtime/agents/execution-reviewer/AGENTS.md +8 -0
- package/runtime/agents/execution-reviewer/README.md +13 -0
- package/runtime/agents/execution-reviewer/agent.yaml +25 -0
- package/runtime/agents/execution-reviewer/capabilities/review-agent-result/capability.yaml +15 -0
- package/runtime/agents/execution-reviewer/capabilities/review-agent-result/decision-rules.md +6 -0
- package/runtime/agents/execution-reviewer/capabilities/review-agent-result/workflow.md +7 -0
- package/runtime/agents/execution-reviewer/capabilities/review-final-output/capability.yaml +15 -0
- package/runtime/agents/execution-reviewer/capabilities/review-final-output/decision-rules.md +6 -0
- package/runtime/agents/execution-reviewer/capabilities/review-final-output/workflow.md +7 -0
- package/runtime/agents/execution-reviewer/capabilities/review-plan/capability.yaml +15 -0
- package/runtime/agents/execution-reviewer/capabilities/review-plan/decision-rules.md +6 -0
- package/runtime/agents/execution-reviewer/capabilities/review-plan/workflow.md +7 -0
- package/runtime/agents/execution-reviewer/infra/README.md +4 -0
- package/runtime/agents/execution-reviewer/knowledge/context.md +6 -0
- package/runtime/agents/execution-reviewer/knowledge/system.md +18 -0
- package/runtime/agents/execution-reviewer/templates/README.md +4 -0
- package/runtime/agents/local-llm-operator/AGENTS.md +8 -0
- package/runtime/agents/local-llm-operator/README.md +12 -0
- package/runtime/agents/local-llm-operator/agent.yaml +26 -0
- package/runtime/agents/local-llm-operator/capabilities/delegate-operational-task/capability.yaml +15 -0
- package/runtime/agents/local-llm-operator/capabilities/delegate-operational-task/decision-rules.md +6 -0
- package/runtime/agents/local-llm-operator/capabilities/delegate-operational-task/workflow.md +7 -0
- package/runtime/agents/local-llm-operator/capabilities/inspect-local-models/capability.yaml +15 -0
- package/runtime/agents/local-llm-operator/capabilities/inspect-local-models/decision-rules.md +5 -0
- package/runtime/agents/local-llm-operator/capabilities/inspect-local-models/workflow.md +6 -0
- package/runtime/agents/local-llm-operator/capabilities/select-local-worker/capability.yaml +15 -0
- package/runtime/agents/local-llm-operator/capabilities/select-local-worker/decision-rules.md +6 -0
- package/runtime/agents/local-llm-operator/capabilities/select-local-worker/workflow.md +7 -0
- package/runtime/agents/local-llm-operator/infra/README.md +4 -0
- package/runtime/agents/local-llm-operator/knowledge/context.md +6 -0
- package/runtime/agents/local-llm-operator/knowledge/system.md +18 -0
- package/runtime/agents/local-llm-operator/templates/README.md +4 -0
- package/runtime/agents/provider-configurator/AGENTS.md +8 -0
- package/runtime/agents/provider-configurator/README.md +5 -0
- package/runtime/agents/provider-configurator/agent.yaml +25 -0
- package/runtime/agents/provider-configurator/capabilities/collect-provider-credentials/capability.yaml +15 -0
- package/runtime/agents/provider-configurator/capabilities/collect-provider-credentials/decision-rules.md +6 -0
- package/runtime/agents/provider-configurator/capabilities/collect-provider-credentials/workflow.md +7 -0
- package/runtime/agents/provider-configurator/capabilities/configure-provider-source/capability.yaml +15 -0
- package/runtime/agents/provider-configurator/capabilities/configure-provider-source/decision-rules.md +6 -0
- package/runtime/agents/provider-configurator/capabilities/configure-provider-source/workflow.md +8 -0
- package/runtime/agents/provider-configurator/capabilities/validate-provider-readiness/capability.yaml +15 -0
- package/runtime/agents/provider-configurator/capabilities/validate-provider-readiness/decision-rules.md +6 -0
- package/runtime/agents/provider-configurator/capabilities/validate-provider-readiness/workflow.md +7 -0
- package/runtime/agents/provider-configurator/infra/README.md +5 -0
- package/runtime/agents/provider-configurator/knowledge/context.md +8 -0
- package/runtime/agents/provider-configurator/knowledge/system.md +19 -0
- package/runtime/agents/provider-configurator/templates/README.md +4 -0
- package/runtime/agents/task-orchestrator/AGENTS.md +8 -0
- package/runtime/agents/task-orchestrator/README.md +7 -0
- package/runtime/agents/task-orchestrator/agent.yaml +31 -0
- package/runtime/agents/task-orchestrator/capabilities/coordinate-execution/capability.yaml +15 -0
- package/runtime/agents/task-orchestrator/capabilities/coordinate-execution/decision-rules.md +6 -0
- package/runtime/agents/task-orchestrator/capabilities/coordinate-execution/workflow.md +7 -0
- package/runtime/agents/task-orchestrator/capabilities/plan-task/capability.yaml +15 -0
- package/runtime/agents/task-orchestrator/capabilities/plan-task/decision-rules.md +6 -0
- package/runtime/agents/task-orchestrator/capabilities/plan-task/workflow.md +8 -0
- package/runtime/agents/task-orchestrator/capabilities/select-specialists/capability.yaml +15 -0
- package/runtime/agents/task-orchestrator/capabilities/select-specialists/decision-rules.md +6 -0
- package/runtime/agents/task-orchestrator/capabilities/select-specialists/workflow.md +7 -0
- package/runtime/agents/task-orchestrator/infra/README.md +5 -0
- package/runtime/agents/task-orchestrator/knowledge/context.md +9 -0
- package/runtime/agents/task-orchestrator/knowledge/system.md +20 -0
- package/runtime/agents/task-orchestrator/templates/README.md +4 -0
- package/runtime/cli/README.md +76 -11
- package/runtime/cli/aikit/__init__.py +1 -1
- package/runtime/cli/aikit/agent_executor.py +66 -0
- package/runtime/cli/aikit/agent_registry.py +80 -0
- package/runtime/cli/aikit/app_home.py +87 -2
- package/runtime/cli/aikit/audit.py +44 -0
- package/runtime/cli/aikit/control_router.py +298 -0
- package/runtime/cli/aikit/decision_store.py +17 -0
- package/runtime/cli/aikit/diagnostics.py +2 -1
- package/runtime/cli/aikit/execution_reviewer.py +174 -0
- package/runtime/cli/aikit/install.py +4 -3
- package/runtime/cli/aikit/local_llm_operator.py +112 -0
- package/runtime/cli/aikit/lock.py +4 -1
- package/runtime/cli/aikit/main.py +355 -80
- package/runtime/cli/aikit/model_router.py +13 -4
- package/runtime/cli/aikit/orchestrator.py +535 -0
- package/runtime/cli/aikit/sessions.py +37 -0
- package/runtime/cli/aikit/sources.py +6 -2
- package/runtime/cli/aikit/wizard_state.py +503 -0
|
@@ -5,12 +5,12 @@ from __future__ import annotations
|
|
|
5
5
|
import re
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
-
from cli.aikit.llm import llm_preference, load_config
|
|
8
|
+
from cli.aikit.llm import BACKENDS, doctor_backend, llm_preference, load_config
|
|
9
9
|
from cli.aikit.ollama import ollama_status
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
OPERATIONAL_PATTERN = re.compile(
|
|
13
|
-
r"(?i)\b(resum
|
|
13
|
+
r"(?i)\b(resum\w*|sumari\w*|classifi\w*|extra(?:i|ir|ia|cao|ção)\w*|normaliz\w*|compar\w*|logs?|rascunho|agrupe|agrupar)\b"
|
|
14
14
|
)
|
|
15
15
|
HIGH_LEVEL_PATTERN = re.compile(
|
|
16
16
|
r"(?i)\b(arquitet|decid|aprovar|reprovar|especifica|requisit|implemente|codigo|c[oó]digo|documento|automac|deploy|seguran)\b"
|
|
@@ -18,9 +18,11 @@ HIGH_LEVEL_PATTERN = re.compile(
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def build_model_plan(prompt: str, *, route: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
21
|
-
|
|
21
|
+
config = load_config()
|
|
22
|
+
preference = llm_preference(config)
|
|
22
23
|
ollama = ollama_status()
|
|
23
|
-
|
|
24
|
+
ollama_backend = doctor_backend(BACKENDS["ollama"], config)
|
|
25
|
+
local_available = ollama.get("status") == "ok" or ollama_backend.get("status") == "ok"
|
|
24
26
|
operational = bool(OPERATIONAL_PATTERN.search(prompt))
|
|
25
27
|
high_level = bool(HIGH_LEVEL_PATTERN.search(prompt))
|
|
26
28
|
use_local = operational and local_available
|
|
@@ -32,6 +34,13 @@ def build_model_plan(prompt: str, *, route: dict[str, Any] | None = None) -> dic
|
|
|
32
34
|
"local_llm_role": "operational-worker",
|
|
33
35
|
"local_llm_available": local_available,
|
|
34
36
|
"local_llm_provider": "ollama",
|
|
37
|
+
"local_llm_backend_configured": ollama_backend.get("status") == "ok",
|
|
38
|
+
"local_llm_runtime": {
|
|
39
|
+
"binary_status": ollama.get("status"),
|
|
40
|
+
"backend_status": ollama_backend.get("status"),
|
|
41
|
+
"model": ollama_backend.get("model"),
|
|
42
|
+
"base_url": ollama_backend.get("base_url"),
|
|
43
|
+
},
|
|
35
44
|
"local_llm_recommended": operational,
|
|
36
45
|
"local_llm_selected": use_local,
|
|
37
46
|
"delegation": {
|
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
"""Multi-agent planning for natural-language Agent DevKit prompts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
import unicodedata
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from cli.aikit.agent_registry import find_capability, load_agent_registry
|
|
11
|
+
from cli.aikit.configuration_orchestrator import provider_setup_wizard
|
|
12
|
+
from cli.aikit.memory import redact_secrets
|
|
13
|
+
from cli.aikit.model_router import build_model_plan
|
|
14
|
+
from cli.aikit.review_gate import build_review_gate
|
|
15
|
+
from cli.aikit.router import route_prompt
|
|
16
|
+
from cli.aikit.sources import SourceRegistryError, public_source, resolve_source
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
RUNTIME_COORDINATOR = {
|
|
20
|
+
"id": "task-orchestrator",
|
|
21
|
+
"kind": "runtime-agent",
|
|
22
|
+
"name": "Task Orchestrator",
|
|
23
|
+
"role": "coordinator",
|
|
24
|
+
}
|
|
25
|
+
REVIEWER_AGENT_ID = "execution-reviewer"
|
|
26
|
+
PROVIDER_CONFIGURATOR_AGENT_ID = "provider-configurator"
|
|
27
|
+
LOCAL_LLM_OPERATOR_AGENT_ID = "local-llm-operator"
|
|
28
|
+
STOP_WORDS = {
|
|
29
|
+
"a",
|
|
30
|
+
"as",
|
|
31
|
+
"com",
|
|
32
|
+
"da",
|
|
33
|
+
"de",
|
|
34
|
+
"do",
|
|
35
|
+
"dos",
|
|
36
|
+
"e",
|
|
37
|
+
"em",
|
|
38
|
+
"essa",
|
|
39
|
+
"esse",
|
|
40
|
+
"no",
|
|
41
|
+
"na",
|
|
42
|
+
"o",
|
|
43
|
+
"os",
|
|
44
|
+
"para",
|
|
45
|
+
"por",
|
|
46
|
+
"uma",
|
|
47
|
+
"um",
|
|
48
|
+
}
|
|
49
|
+
TOKEN_ALIASES = {
|
|
50
|
+
"analise": {"analysis", "analyze", "analisar"},
|
|
51
|
+
"analisar": {"analysis", "analyze", "analise"},
|
|
52
|
+
"cloudwatch": {"aws", "logs", "log"},
|
|
53
|
+
"demanda": {"demand", "requirement", "requirements", "requisitos"},
|
|
54
|
+
"erro": {"error", "errors", "falha"},
|
|
55
|
+
"falha": {"error", "errors", "erro"},
|
|
56
|
+
"gere": {"generate", "create", "criar"},
|
|
57
|
+
"gerar": {"generate", "create", "criar"},
|
|
58
|
+
"logs": {"log", "cloudwatch", "elasticsearch"},
|
|
59
|
+
"servico": {"service", "serviço"},
|
|
60
|
+
"especificacao": {"specification", "spec", "requirements", "requisitos"},
|
|
61
|
+
"requisito": {"requirement", "requirements", "specification"},
|
|
62
|
+
"requisitos": {"requirement", "requirements", "specification"},
|
|
63
|
+
"tecnica": {"technical", "architecture", "component"},
|
|
64
|
+
"tecnico": {"technical", "architecture", "component"},
|
|
65
|
+
}
|
|
66
|
+
AGENT_ANCHORS = {
|
|
67
|
+
"aws-architecture-analyst": {"aws", "arquitetura", "architecture", "workload", "resiliencia", "observability", "vpc"},
|
|
68
|
+
"aws-cloudwatch-log-analyzer": {"aws", "cloudwatch", "log", "logs"},
|
|
69
|
+
"aws-operations-operator": {"aws", "ecs", "lambda", "sqs", "cloudfront", "eventbridge"},
|
|
70
|
+
"aws-security-governance-auditor": {"aws", "iam", "s3", "cloudtrail", "security", "seguranca"},
|
|
71
|
+
"azure-devops-orchestrator": {"azure", "devops", "card", "workitem", "work", "item", "board"},
|
|
72
|
+
"bpo-analyser": {"bpo", "cpf", "proposta", "proposal"},
|
|
73
|
+
"data-scientist-analyst": {"dataset", "dados", "data", "cohort", "modelo", "estatistica"},
|
|
74
|
+
"database-change-operator": {"postgres", "migration", "migracao", "database", "banco"},
|
|
75
|
+
"drawio-diagram-builder": {"drawio", "diagrama", "diagram"},
|
|
76
|
+
"elasticsearch-log-analyzer": {"elasticsearch", "elastic", "indice", "index", "logs", "log"},
|
|
77
|
+
"excel-workbook-builder": {"excel", "planilha", "spreadsheet", "workbook"},
|
|
78
|
+
"figma-ui-ux-product-designer": {"figma", "ui", "ux", "design", "tela", "wireframe"},
|
|
79
|
+
"github-pr-reviewer": {"github", "gh", "pr", "prs", "pull", "request"},
|
|
80
|
+
"knowledge-generator": {"knowledge", "conhecimento", "documentacao", "runbook"},
|
|
81
|
+
"postgres-data-analyzer": {"postgres", "postgresql", "sql", "database", "banco"},
|
|
82
|
+
"presentation-deck-builder": {"apresentacao", "presentation", "slides", "powerpoint", "deck"},
|
|
83
|
+
"software-specification-analyst": {"especificacao", "specification", "requisitos", "requirements", "historia", "stories", "demanda"},
|
|
84
|
+
"sqlserver-change-operator": {"sqlserver", "sql", "migration", "migracao", "banco"},
|
|
85
|
+
"sqlserver-data-analyzer": {"sqlserver", "sql", "database", "banco", "tabela"},
|
|
86
|
+
"technical-integration-analyst": {"integracao", "integration", "api", "rest", "soap", "sftp", "mcp"},
|
|
87
|
+
"topdesk-orchestrator": {"topdesk", "chamado"},
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def build_execution_plan(root: Path, prompt: str, *, dry_run: bool = False) -> dict[str, Any]:
|
|
92
|
+
registry = load_agent_registry(root)
|
|
93
|
+
safe_prompt = redact_secrets(prompt)
|
|
94
|
+
route = route_prompt(prompt)
|
|
95
|
+
domain_agent = select_domain_agent(registry, prompt, route)
|
|
96
|
+
specialist_tasks = specialist_tasks_for_prompt(registry, prompt, route, domain_agent)
|
|
97
|
+
configuration_tasks = configuration_tasks_for(registry, root, prompt, route, specialist_tasks, dry_run=dry_run)
|
|
98
|
+
model_plan = build_model_plan(prompt, route=route)
|
|
99
|
+
model_plan = attach_local_llm_agent_contract(registry, model_plan)
|
|
100
|
+
review_gate = build_review_gate(prompt, route=route, model_plan=model_plan)
|
|
101
|
+
status = "needs-input" if configuration_tasks and not dry_run else "planned"
|
|
102
|
+
return {
|
|
103
|
+
"kind": "agentic-execution-plan",
|
|
104
|
+
"schema_version": "ai-devkit.agentic-plan/v1",
|
|
105
|
+
"status": status,
|
|
106
|
+
"dry_run": dry_run,
|
|
107
|
+
"prompt": safe_prompt,
|
|
108
|
+
"coordinator_agent": runtime_agent(registry, "task-orchestrator", fallback=RUNTIME_COORDINATOR),
|
|
109
|
+
"domain_agent": domain_agent,
|
|
110
|
+
"route": route,
|
|
111
|
+
"model_plan": model_plan,
|
|
112
|
+
"review_gate": review_gate,
|
|
113
|
+
"specialist_tasks": specialist_tasks,
|
|
114
|
+
"configuration_tasks": configuration_tasks,
|
|
115
|
+
"review_task": review_task(registry, review_gate),
|
|
116
|
+
"executed_tasks": [],
|
|
117
|
+
"blocked_tasks": [],
|
|
118
|
+
"trace": trace_for_plan(specialist_tasks, configuration_tasks, status=status),
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def select_domain_agent(registry: dict[str, Any], prompt: str, route: dict[str, Any] | None) -> dict[str, Any]:
|
|
123
|
+
normalized = normalize(prompt)
|
|
124
|
+
agents = registry.get("agents") or {}
|
|
125
|
+
if re.search(r"\bn1\b|primeiro nivel|1o nivel|1º nivel", normalized) and "n1-support-agent" in agents:
|
|
126
|
+
return slim_agent(agents["n1-support-agent"])
|
|
127
|
+
if re.search(r"\bn2\b|segundo nivel|2o nivel|2º nivel", normalized) and "n2-support-agent" in agents:
|
|
128
|
+
return slim_agent(agents["n2-support-agent"])
|
|
129
|
+
if route and route.get("agent_id") in agents:
|
|
130
|
+
return slim_agent(agents[str(route["agent_id"])])
|
|
131
|
+
matched_agent = best_agent_match(registry, prompt)
|
|
132
|
+
if matched_agent:
|
|
133
|
+
return slim_agent(matched_agent)
|
|
134
|
+
if "task-orchestrator" in agents:
|
|
135
|
+
return slim_agent(agents["task-orchestrator"])
|
|
136
|
+
return dict(RUNTIME_COORDINATOR)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def specialist_tasks_for_prompt(
|
|
140
|
+
registry: dict[str, Any],
|
|
141
|
+
prompt: str,
|
|
142
|
+
route: dict[str, Any] | None,
|
|
143
|
+
domain_agent: dict[str, Any],
|
|
144
|
+
) -> list[dict[str, Any]]:
|
|
145
|
+
if domain_agent.get("id") in {"n1-support-agent", "n2-support-agent"}:
|
|
146
|
+
return orchestrated_tasks(registry, str(domain_agent["id"]), prompt, route)
|
|
147
|
+
if route:
|
|
148
|
+
task = task_from_route(registry, route)
|
|
149
|
+
return [task] if task else []
|
|
150
|
+
if domain_agent.get("id") and domain_agent.get("id") != "task-orchestrator":
|
|
151
|
+
task = best_capability_task(registry, str(domain_agent["id"]), prompt)
|
|
152
|
+
return [task] if task else []
|
|
153
|
+
return []
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def orchestrated_tasks(registry: dict[str, Any], agent_id: str, prompt: str, route: dict[str, Any] | None) -> list[dict[str, Any]]:
|
|
157
|
+
agent = (registry.get("agents") or {}).get(agent_id) or {}
|
|
158
|
+
tasks: list[dict[str, Any]] = []
|
|
159
|
+
for pair in agent.get("orchestrated_agents") or []:
|
|
160
|
+
if "/" not in str(pair):
|
|
161
|
+
continue
|
|
162
|
+
specialist_agent, capability_id = str(pair).split("/", 1)
|
|
163
|
+
capability = find_capability(registry, specialist_agent, capability_id)
|
|
164
|
+
if not capability:
|
|
165
|
+
continue
|
|
166
|
+
task = base_task(capability, prompt=prompt)
|
|
167
|
+
if route and specialist_agent == route.get("agent_id") and capability_id == route.get("capability_id"):
|
|
168
|
+
task["args"] = list(route.get("args") or [])
|
|
169
|
+
task["entities"] = dict(route.get("entities") or {})
|
|
170
|
+
task["provider"] = route.get("provider") or task.get("provider")
|
|
171
|
+
task["primary"] = True
|
|
172
|
+
tasks.append(task)
|
|
173
|
+
if route and not any(task.get("primary") for task in tasks):
|
|
174
|
+
routed = task_from_route(registry, route)
|
|
175
|
+
if routed:
|
|
176
|
+
routed["primary"] = True
|
|
177
|
+
tasks.insert(0, routed)
|
|
178
|
+
return tasks
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def task_from_route(registry: dict[str, Any], route: dict[str, Any]) -> dict[str, Any] | None:
|
|
182
|
+
capability = find_capability(registry, str(route.get("agent_id") or ""), str(route.get("capability_id") or ""))
|
|
183
|
+
if not capability:
|
|
184
|
+
return None
|
|
185
|
+
task = base_task(capability, prompt="")
|
|
186
|
+
task["args"] = list(route.get("args") or [])
|
|
187
|
+
task["entities"] = dict(route.get("entities") or {})
|
|
188
|
+
task["provider"] = route.get("provider") or task.get("provider")
|
|
189
|
+
task["primary"] = True
|
|
190
|
+
return task
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def best_agent_match(registry: dict[str, Any], prompt: str) -> dict[str, Any] | None:
|
|
194
|
+
prompt_tokens = expanded_tokens(prompt)
|
|
195
|
+
normalized_prompt = normalize(prompt)
|
|
196
|
+
best: tuple[int, dict[str, Any] | None] = (0, None)
|
|
197
|
+
for agent in (registry.get("agents") or {}).values():
|
|
198
|
+
if not has_agent_anchor(str(agent.get("id") or ""), prompt_tokens, normalized_prompt):
|
|
199
|
+
continue
|
|
200
|
+
agent_tokens = expanded_tokens(
|
|
201
|
+
" ".join(
|
|
202
|
+
[
|
|
203
|
+
str(agent.get("id") or ""),
|
|
204
|
+
str(agent.get("name") or ""),
|
|
205
|
+
str(agent.get("purpose") or ""),
|
|
206
|
+
" ".join(
|
|
207
|
+
" ".join(
|
|
208
|
+
[
|
|
209
|
+
str(capability.get("short_id") or ""),
|
|
210
|
+
str(capability.get("name") or ""),
|
|
211
|
+
str(capability.get("purpose") or ""),
|
|
212
|
+
]
|
|
213
|
+
)
|
|
214
|
+
for capability in (agent.get("capabilities_index") or {}).values()
|
|
215
|
+
if isinstance(capability, dict)
|
|
216
|
+
),
|
|
217
|
+
]
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
score = weighted_overlap(prompt_tokens, agent_tokens)
|
|
221
|
+
if score > best[0]:
|
|
222
|
+
best = (score, agent)
|
|
223
|
+
return best[1] if best[0] >= 3 else None
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def has_agent_anchor(agent_id: str, prompt_tokens: set[str], normalized_prompt: str) -> bool:
|
|
227
|
+
anchors = AGENT_ANCHORS.get(agent_id)
|
|
228
|
+
if not anchors:
|
|
229
|
+
return False
|
|
230
|
+
if anchors & prompt_tokens:
|
|
231
|
+
return True
|
|
232
|
+
return any(anchor in normalized_prompt for anchor in anchors if len(anchor) >= 5)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def best_capability_task(registry: dict[str, Any], agent_id: str, prompt: str) -> dict[str, Any] | None:
|
|
236
|
+
agent = (registry.get("agents") or {}).get(agent_id) or {}
|
|
237
|
+
capabilities = [item for item in (agent.get("capabilities_index") or {}).values() if isinstance(item, dict)]
|
|
238
|
+
if not capabilities:
|
|
239
|
+
return None
|
|
240
|
+
hinted = hinted_capability(capabilities, prompt)
|
|
241
|
+
if hinted:
|
|
242
|
+
task = base_task(hinted, prompt=prompt)
|
|
243
|
+
task["primary"] = True
|
|
244
|
+
task["selection"] = {"method": "registry-hint", "score": None}
|
|
245
|
+
return task
|
|
246
|
+
prompt_tokens = expanded_tokens(prompt)
|
|
247
|
+
best: tuple[int, dict[str, Any] | None] = (0, None)
|
|
248
|
+
for capability in capabilities:
|
|
249
|
+
capability_tokens = expanded_tokens(
|
|
250
|
+
" ".join(
|
|
251
|
+
[
|
|
252
|
+
str(capability.get("short_id") or ""),
|
|
253
|
+
str(capability.get("name") or ""),
|
|
254
|
+
str(capability.get("purpose") or ""),
|
|
255
|
+
]
|
|
256
|
+
)
|
|
257
|
+
)
|
|
258
|
+
score = weighted_overlap(prompt_tokens, capability_tokens)
|
|
259
|
+
if score > best[0]:
|
|
260
|
+
best = (score, capability)
|
|
261
|
+
if not best[1] or best[0] < 2:
|
|
262
|
+
return None
|
|
263
|
+
task = base_task(best[1], prompt=prompt)
|
|
264
|
+
task["primary"] = True
|
|
265
|
+
task["selection"] = {"method": "registry-score", "score": best[0]}
|
|
266
|
+
return task
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def hinted_capability(capabilities: list[dict[str, Any]], prompt: str) -> dict[str, Any] | None:
|
|
270
|
+
normalized = normalize(prompt)
|
|
271
|
+
hints = []
|
|
272
|
+
if "especificacao" in normalized and ("tecnica" in normalized or "tecnico" in normalized or "technical" in normalized):
|
|
273
|
+
hints.append("create-technical-spec")
|
|
274
|
+
if "especificacao" in normalized and ("funcional" in normalized or "functional" in normalized):
|
|
275
|
+
hints.append("create-functional-spec")
|
|
276
|
+
if "historia" in normalized or "user stor" in normalized:
|
|
277
|
+
hints.append("write-user-stories")
|
|
278
|
+
if "cloudwatch" in normalized and ("erro" in normalized or "error" in normalized):
|
|
279
|
+
hints.append("analyze-service-error")
|
|
280
|
+
if ("elasticsearch" in normalized or "elastic" in normalized) and ("erro" in normalized or "error" in normalized):
|
|
281
|
+
hints.append("analyze-service-errors")
|
|
282
|
+
for hint in hints:
|
|
283
|
+
for capability in capabilities:
|
|
284
|
+
if capability.get("short_id") == hint:
|
|
285
|
+
return capability
|
|
286
|
+
return None
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def base_task(capability: dict[str, Any], *, prompt: str) -> dict[str, Any]:
|
|
290
|
+
return {
|
|
291
|
+
"id": f"{capability['agent_id']}.{capability['short_id']}",
|
|
292
|
+
"agent_id": capability["agent_id"],
|
|
293
|
+
"capability_id": capability["short_id"],
|
|
294
|
+
"capability": capability["id"],
|
|
295
|
+
"purpose": capability.get("purpose"),
|
|
296
|
+
"write_policy": capability.get("write_policy") or "read_only",
|
|
297
|
+
"provider": infer_provider(capability),
|
|
298
|
+
"args": [],
|
|
299
|
+
"entities": {},
|
|
300
|
+
"status": "planned",
|
|
301
|
+
"executable": bool(capability.get("has_runner")),
|
|
302
|
+
"prompt": redact_secrets(prompt),
|
|
303
|
+
"primary": False,
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def configuration_tasks_for(
|
|
308
|
+
registry: dict[str, Any],
|
|
309
|
+
root: Path,
|
|
310
|
+
prompt: str,
|
|
311
|
+
route: dict[str, Any] | None,
|
|
312
|
+
specialist_tasks: list[dict[str, Any]],
|
|
313
|
+
*,
|
|
314
|
+
dry_run: bool,
|
|
315
|
+
) -> list[dict[str, Any]]:
|
|
316
|
+
providers = []
|
|
317
|
+
provider_agents: dict[str, str] = {}
|
|
318
|
+
if route and route.get("provider"):
|
|
319
|
+
providers.append(str(route["provider"]))
|
|
320
|
+
if route.get("agent_id"):
|
|
321
|
+
provider_agents[str(route["provider"])] = str(route["agent_id"])
|
|
322
|
+
for task in specialist_tasks:
|
|
323
|
+
provider = task.get("provider")
|
|
324
|
+
if provider:
|
|
325
|
+
provider_id = str(provider)
|
|
326
|
+
providers.append(provider_id)
|
|
327
|
+
provider_agents.setdefault(provider_id, str(task.get("agent_id") or ""))
|
|
328
|
+
result: list[dict[str, Any]] = []
|
|
329
|
+
seen: set[str] = set()
|
|
330
|
+
for provider in providers:
|
|
331
|
+
if provider in seen:
|
|
332
|
+
continue
|
|
333
|
+
seen.add(provider)
|
|
334
|
+
source = None
|
|
335
|
+
agent_id = (route or {}).get("agent_id") or provider_agents.get(provider)
|
|
336
|
+
try:
|
|
337
|
+
source = resolve_source(provider=provider, intent=(route or {}).get("intent"), agent_id=agent_id)
|
|
338
|
+
except SourceRegistryError:
|
|
339
|
+
source = None
|
|
340
|
+
if source:
|
|
341
|
+
continue
|
|
342
|
+
capability = find_capability(registry, PROVIDER_CONFIGURATOR_AGENT_ID, "configure-provider-source") or {}
|
|
343
|
+
wizard = provider_setup_wizard(root, provider, prompt=prompt, route=route, reason="Provider/source required by multi-agent plan.")
|
|
344
|
+
result.append(
|
|
345
|
+
{
|
|
346
|
+
"id": f"provider-configurator.{provider}",
|
|
347
|
+
"agent_id": PROVIDER_CONFIGURATOR_AGENT_ID,
|
|
348
|
+
"capability_id": "configure-provider-source",
|
|
349
|
+
"capability": capability.get("id") or "provider-configurator.configure-provider-source",
|
|
350
|
+
"purpose": capability.get("purpose"),
|
|
351
|
+
"write_policy": capability.get("write_policy") or "local-config-write",
|
|
352
|
+
"path": capability.get("path"),
|
|
353
|
+
"provider": provider,
|
|
354
|
+
"status": "waiting-for-user",
|
|
355
|
+
"setup_wizard": wizard,
|
|
356
|
+
}
|
|
357
|
+
)
|
|
358
|
+
return result
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def attach_source_to_primary_task(plan: dict[str, Any]) -> dict[str, Any]:
|
|
362
|
+
route = plan.get("route") if isinstance(plan.get("route"), dict) else None
|
|
363
|
+
if not route:
|
|
364
|
+
return plan
|
|
365
|
+
try:
|
|
366
|
+
source = resolve_source(provider=route.get("provider"), intent=route.get("intent"), agent_id=route.get("agent_id"))
|
|
367
|
+
except SourceRegistryError:
|
|
368
|
+
source = None
|
|
369
|
+
if not source:
|
|
370
|
+
return plan
|
|
371
|
+
for task in plan.get("specialist_tasks") or []:
|
|
372
|
+
if task.get("primary"):
|
|
373
|
+
task["source"] = public_source(source)
|
|
374
|
+
task["args"] = [*list(task.get("args") or []), "--source", str(source["id"])]
|
|
375
|
+
task["status"] = "ready"
|
|
376
|
+
plan["configuration_tasks"] = []
|
|
377
|
+
if plan.get("status") == "needs-input":
|
|
378
|
+
plan["status"] = "planned"
|
|
379
|
+
return plan
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def mark_plan_after_execution(plan: dict[str, Any], executed: list[dict[str, Any]], blocked: list[dict[str, Any]]) -> dict[str, Any]:
|
|
383
|
+
plan = dict(plan)
|
|
384
|
+
plan["executed_tasks"] = executed
|
|
385
|
+
plan["blocked_tasks"] = blocked
|
|
386
|
+
if blocked and not executed:
|
|
387
|
+
plan["status"] = "blocked"
|
|
388
|
+
elif blocked and executed:
|
|
389
|
+
plan["status"] = "partial"
|
|
390
|
+
elif executed:
|
|
391
|
+
plan["status"] = "ok"
|
|
392
|
+
plan["trace"] = trace_for_plan(plan.get("specialist_tasks") or [], plan.get("configuration_tasks") or [], executed=executed, blocked=blocked, status=plan["status"])
|
|
393
|
+
return plan
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def review_task(registry: dict[str, Any], review_gate: dict[str, Any]) -> dict[str, Any]:
|
|
397
|
+
capability = find_capability(registry, REVIEWER_AGENT_ID, "review-final-output") or {}
|
|
398
|
+
task = {
|
|
399
|
+
"id": "execution-reviewer.review-final-output",
|
|
400
|
+
"agent_id": REVIEWER_AGENT_ID,
|
|
401
|
+
"capability_id": "review-final-output",
|
|
402
|
+
"capability": capability.get("id") or "execution-reviewer.review-final-output",
|
|
403
|
+
"purpose": capability.get("purpose"),
|
|
404
|
+
"write_policy": capability.get("write_policy") or "read-only",
|
|
405
|
+
"path": capability.get("path"),
|
|
406
|
+
"status": "pending" if review_gate.get("required") else "not-required",
|
|
407
|
+
"required": bool(review_gate.get("required")),
|
|
408
|
+
"preferred_reviewers": list(review_gate.get("preferred_reviewers") or []),
|
|
409
|
+
}
|
|
410
|
+
return task
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def mark_review_task(plan: dict[str, Any], *, reviewer: str) -> dict[str, Any]:
|
|
414
|
+
task = dict(plan.get("review_task") or {"agent_id": REVIEWER_AGENT_ID, "capability_id": "review-final-output"})
|
|
415
|
+
if task.get("required"):
|
|
416
|
+
task["status"] = "reviewed"
|
|
417
|
+
task["reviewer"] = reviewer
|
|
418
|
+
plan["review_task"] = task
|
|
419
|
+
return plan
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def trace_for_plan(
|
|
423
|
+
specialist_tasks: list[dict[str, Any]],
|
|
424
|
+
configuration_tasks: list[dict[str, Any]],
|
|
425
|
+
*,
|
|
426
|
+
executed: list[dict[str, Any]] | None = None,
|
|
427
|
+
blocked: list[dict[str, Any]] | None = None,
|
|
428
|
+
status: str,
|
|
429
|
+
) -> list[dict[str, Any]]:
|
|
430
|
+
trace = [{"agent_id": RUNTIME_COORDINATOR["id"], "action": "plan", "status": status}]
|
|
431
|
+
trace.extend({"agent_id": task["agent_id"], "action": "configure", "status": task.get("status")} for task in configuration_tasks)
|
|
432
|
+
trace.extend({"agent_id": task["agent_id"], "action": "execute", "status": task.get("status")} for task in specialist_tasks)
|
|
433
|
+
trace.extend({"agent_id": item["agent_id"], "action": "executed", "status": item.get("status")} for item in executed or [])
|
|
434
|
+
trace.extend({"agent_id": item["agent_id"], "action": "blocked", "status": item.get("status")} for item in blocked or [])
|
|
435
|
+
return trace
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def infer_provider(capability: dict[str, Any]) -> str | None:
|
|
439
|
+
requires = capability.get("requires") if isinstance(capability.get("requires"), dict) else {}
|
|
440
|
+
providers = requires.get("providers") if isinstance(requires.get("providers"), list) else []
|
|
441
|
+
if providers and isinstance(providers[0], dict):
|
|
442
|
+
return str(providers[0].get("id") or "") or None
|
|
443
|
+
integration = capability.get("integration") if isinstance(capability.get("integration"), dict) else {}
|
|
444
|
+
repository = str(integration.get("repository") or "")
|
|
445
|
+
agent_id = str(capability.get("agent_id") or "")
|
|
446
|
+
if "azure" in repository or agent_id == "azure-devops-orchestrator":
|
|
447
|
+
return "azure-devops"
|
|
448
|
+
if "topdesk" in repository or agent_id == "topdesk-orchestrator":
|
|
449
|
+
return "topdesk"
|
|
450
|
+
if "cloudwatch" in repository:
|
|
451
|
+
return "aws-cloudwatch"
|
|
452
|
+
if "elasticsearch" in repository:
|
|
453
|
+
return "elasticsearch"
|
|
454
|
+
if "sqlserver" in repository:
|
|
455
|
+
return "sqlserver"
|
|
456
|
+
if "postgres" in repository:
|
|
457
|
+
return "postgres"
|
|
458
|
+
if "/aws/" in repository or "aws-" in repository:
|
|
459
|
+
return "aws"
|
|
460
|
+
return None
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def attach_local_llm_agent_contract(registry: dict[str, Any], model_plan: dict[str, Any]) -> dict[str, Any]:
|
|
464
|
+
plan = dict(model_plan)
|
|
465
|
+
agent = (registry.get("agents") or {}).get(LOCAL_LLM_OPERATOR_AGENT_ID)
|
|
466
|
+
select_capability = find_capability(registry, LOCAL_LLM_OPERATOR_AGENT_ID, "select-local-worker") or {}
|
|
467
|
+
delegate_capability = find_capability(registry, LOCAL_LLM_OPERATOR_AGENT_ID, "delegate-operational-task") or {}
|
|
468
|
+
plan["operator_agent"] = slim_agent(agent) if isinstance(agent, dict) else {
|
|
469
|
+
"id": LOCAL_LLM_OPERATOR_AGENT_ID,
|
|
470
|
+
"name": "Local LLM Operator",
|
|
471
|
+
"kind": "runtime-agent",
|
|
472
|
+
}
|
|
473
|
+
plan["selection_capability"] = {
|
|
474
|
+
"agent_id": LOCAL_LLM_OPERATOR_AGENT_ID,
|
|
475
|
+
"capability_id": "select-local-worker",
|
|
476
|
+
"capability": select_capability.get("id") or "local-llm-operator.select-local-worker",
|
|
477
|
+
}
|
|
478
|
+
plan["delegation_capability"] = {
|
|
479
|
+
"agent_id": LOCAL_LLM_OPERATOR_AGENT_ID,
|
|
480
|
+
"capability_id": "delegate-operational-task",
|
|
481
|
+
"capability": delegate_capability.get("id") or "local-llm-operator.delegate-operational-task",
|
|
482
|
+
}
|
|
483
|
+
return plan
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def runtime_agent(registry: dict[str, Any], agent_id: str, *, fallback: dict[str, Any]) -> dict[str, Any]:
|
|
487
|
+
agent = (registry.get("agents") or {}).get(agent_id)
|
|
488
|
+
if isinstance(agent, dict):
|
|
489
|
+
public = slim_agent(agent)
|
|
490
|
+
public["role"] = fallback.get("role")
|
|
491
|
+
return public
|
|
492
|
+
return dict(fallback)
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def slim_agent(agent: dict[str, Any]) -> dict[str, Any]:
|
|
496
|
+
return {
|
|
497
|
+
"id": agent.get("id"),
|
|
498
|
+
"name": agent.get("name"),
|
|
499
|
+
"kind": agent.get("kind") or "specialist-agent",
|
|
500
|
+
"purpose": agent.get("purpose"),
|
|
501
|
+
"path": agent.get("path"),
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def normalize(prompt: str) -> str:
|
|
506
|
+
return " ".join(strip_accents(prompt).lower().split())
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def strip_accents(value: str) -> str:
|
|
510
|
+
return "".join(char for char in unicodedata.normalize("NFKD", value) if not unicodedata.combining(char))
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def expanded_tokens(value: str) -> set[str]:
|
|
514
|
+
tokens = {
|
|
515
|
+
token
|
|
516
|
+
for token in re.findall(r"[a-z0-9][a-z0-9._-]*", normalize(value))
|
|
517
|
+
if len(token) >= 3 and token not in STOP_WORDS
|
|
518
|
+
}
|
|
519
|
+
expanded = set(tokens)
|
|
520
|
+
for token in tokens:
|
|
521
|
+
expanded.update(TOKEN_ALIASES.get(token, set()))
|
|
522
|
+
if token.endswith("s") and len(token) > 4:
|
|
523
|
+
expanded.add(token[:-1])
|
|
524
|
+
return expanded
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def weighted_overlap(prompt_tokens: set[str], candidate_tokens: set[str]) -> int:
|
|
528
|
+
score = 0
|
|
529
|
+
for token in prompt_tokens:
|
|
530
|
+
if token in candidate_tokens:
|
|
531
|
+
score += 2
|
|
532
|
+
continue
|
|
533
|
+
if any(token in candidate or candidate in token for candidate in candidate_tokens if len(candidate) >= 5):
|
|
534
|
+
score += 1
|
|
535
|
+
return score
|
|
@@ -168,6 +168,8 @@ def record_exchange(
|
|
|
168
168
|
"ok": result.get("ok"),
|
|
169
169
|
"backend": result.get("llm_backend") or backend,
|
|
170
170
|
"requires_llm": result.get("requires_llm"),
|
|
171
|
+
"execution_plan": summarize_execution_plan(result.get("execution_plan")),
|
|
172
|
+
"orchestration_trace": result.get("orchestration_trace") or [],
|
|
171
173
|
"token_estimate": token_delta,
|
|
172
174
|
}
|
|
173
175
|
with (path / "messages.jsonl").open("a", encoding="utf-8") as file:
|
|
@@ -189,6 +191,41 @@ def record_exchange(
|
|
|
189
191
|
return public_session(state, active=True)
|
|
190
192
|
|
|
191
193
|
|
|
194
|
+
def summarize_execution_plan(plan: Any) -> dict[str, Any] | None:
|
|
195
|
+
if not isinstance(plan, dict):
|
|
196
|
+
return None
|
|
197
|
+
return {
|
|
198
|
+
"kind": plan.get("kind"),
|
|
199
|
+
"status": plan.get("status"),
|
|
200
|
+
"coordinator_agent": (plan.get("coordinator_agent") or {}).get("id") if isinstance(plan.get("coordinator_agent"), dict) else None,
|
|
201
|
+
"domain_agent": (plan.get("domain_agent") or {}).get("id") if isinstance(plan.get("domain_agent"), dict) else None,
|
|
202
|
+
"specialist_tasks": [
|
|
203
|
+
{
|
|
204
|
+
"agent_id": task.get("agent_id"),
|
|
205
|
+
"capability_id": task.get("capability_id"),
|
|
206
|
+
"status": task.get("status"),
|
|
207
|
+
}
|
|
208
|
+
for task in plan.get("specialist_tasks") or []
|
|
209
|
+
if isinstance(task, dict)
|
|
210
|
+
],
|
|
211
|
+
"configuration_tasks": [
|
|
212
|
+
{
|
|
213
|
+
"agent_id": task.get("agent_id"),
|
|
214
|
+
"provider": task.get("provider"),
|
|
215
|
+
"status": task.get("status"),
|
|
216
|
+
}
|
|
217
|
+
for task in plan.get("configuration_tasks") or []
|
|
218
|
+
if isinstance(task, dict)
|
|
219
|
+
],
|
|
220
|
+
"review_task": {
|
|
221
|
+
"agent_id": (plan.get("review_task") or {}).get("agent_id"),
|
|
222
|
+
"status": (plan.get("review_task") or {}).get("status"),
|
|
223
|
+
}
|
|
224
|
+
if isinstance(plan.get("review_task"), dict)
|
|
225
|
+
else None,
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
|
|
192
229
|
def build_contextual_prompt(session_id: str, prompt: str) -> str:
|
|
193
230
|
exchanges = recent_exchanges(session_id, limit=RECENT_CONTEXT_EXCHANGES)
|
|
194
231
|
if not exchanges:
|
|
@@ -15,6 +15,9 @@ ENV_VAR_NAME_PATTERN = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
|
|
|
15
15
|
|
|
16
16
|
PROVIDER_CONFIG_ENV: dict[str, dict[str, str]] = {
|
|
17
17
|
"azure-devops": {
|
|
18
|
+
"AZURE_DEVOPS_ORG": "AZURE_DEVOPS_ORG",
|
|
19
|
+
"AZURE_DEVOPS_PROJECT": "AZURE_DEVOPS_PROJECT",
|
|
20
|
+
"AZURE_DEVOPS_API_VERSION": "AZURE_DEVOPS_API_VERSION",
|
|
18
21
|
"org": "AZURE_DEVOPS_ORG",
|
|
19
22
|
"organization": "AZURE_DEVOPS_ORG",
|
|
20
23
|
"project": "AZURE_DEVOPS_PROJECT",
|
|
@@ -223,8 +226,9 @@ def apply_source_to_args(source: dict[str, Any] | None, agent_id: str, capabilit
|
|
|
223
226
|
config = source.get("config") or {}
|
|
224
227
|
result = list(args)
|
|
225
228
|
if agent_id == "azure-devops-orchestrator" and capability_id == "read-card":
|
|
226
|
-
|
|
227
|
-
|
|
229
|
+
project = config.get("project") or config.get("AZURE_DEVOPS_PROJECT")
|
|
230
|
+
if project and not has_arg(result, "--project"):
|
|
231
|
+
result.extend(["--project", str(project)])
|
|
228
232
|
if config.get("fixture") and not has_arg(result, "--fixture"):
|
|
229
233
|
result.extend(["--fixture", str(config["fixture"])])
|
|
230
234
|
return result
|