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.
Files changed (87) hide show
  1. package/README.md +2 -2
  2. package/package.json +1 -1
  3. package/runtime/README.md +74 -5
  4. package/runtime/agents/README.md +8 -0
  5. package/runtime/agents/execution-reviewer/AGENTS.md +8 -0
  6. package/runtime/agents/execution-reviewer/README.md +13 -0
  7. package/runtime/agents/execution-reviewer/agent.yaml +25 -0
  8. package/runtime/agents/execution-reviewer/capabilities/review-agent-result/capability.yaml +15 -0
  9. package/runtime/agents/execution-reviewer/capabilities/review-agent-result/decision-rules.md +6 -0
  10. package/runtime/agents/execution-reviewer/capabilities/review-agent-result/workflow.md +7 -0
  11. package/runtime/agents/execution-reviewer/capabilities/review-final-output/capability.yaml +15 -0
  12. package/runtime/agents/execution-reviewer/capabilities/review-final-output/decision-rules.md +6 -0
  13. package/runtime/agents/execution-reviewer/capabilities/review-final-output/workflow.md +7 -0
  14. package/runtime/agents/execution-reviewer/capabilities/review-plan/capability.yaml +15 -0
  15. package/runtime/agents/execution-reviewer/capabilities/review-plan/decision-rules.md +6 -0
  16. package/runtime/agents/execution-reviewer/capabilities/review-plan/workflow.md +7 -0
  17. package/runtime/agents/execution-reviewer/infra/README.md +4 -0
  18. package/runtime/agents/execution-reviewer/knowledge/context.md +6 -0
  19. package/runtime/agents/execution-reviewer/knowledge/system.md +18 -0
  20. package/runtime/agents/execution-reviewer/templates/README.md +4 -0
  21. package/runtime/agents/local-llm-operator/AGENTS.md +8 -0
  22. package/runtime/agents/local-llm-operator/README.md +12 -0
  23. package/runtime/agents/local-llm-operator/agent.yaml +26 -0
  24. package/runtime/agents/local-llm-operator/capabilities/delegate-operational-task/capability.yaml +15 -0
  25. package/runtime/agents/local-llm-operator/capabilities/delegate-operational-task/decision-rules.md +6 -0
  26. package/runtime/agents/local-llm-operator/capabilities/delegate-operational-task/workflow.md +7 -0
  27. package/runtime/agents/local-llm-operator/capabilities/inspect-local-models/capability.yaml +15 -0
  28. package/runtime/agents/local-llm-operator/capabilities/inspect-local-models/decision-rules.md +5 -0
  29. package/runtime/agents/local-llm-operator/capabilities/inspect-local-models/workflow.md +6 -0
  30. package/runtime/agents/local-llm-operator/capabilities/select-local-worker/capability.yaml +15 -0
  31. package/runtime/agents/local-llm-operator/capabilities/select-local-worker/decision-rules.md +6 -0
  32. package/runtime/agents/local-llm-operator/capabilities/select-local-worker/workflow.md +7 -0
  33. package/runtime/agents/local-llm-operator/infra/README.md +4 -0
  34. package/runtime/agents/local-llm-operator/knowledge/context.md +6 -0
  35. package/runtime/agents/local-llm-operator/knowledge/system.md +18 -0
  36. package/runtime/agents/local-llm-operator/templates/README.md +4 -0
  37. package/runtime/agents/provider-configurator/AGENTS.md +8 -0
  38. package/runtime/agents/provider-configurator/README.md +5 -0
  39. package/runtime/agents/provider-configurator/agent.yaml +25 -0
  40. package/runtime/agents/provider-configurator/capabilities/collect-provider-credentials/capability.yaml +15 -0
  41. package/runtime/agents/provider-configurator/capabilities/collect-provider-credentials/decision-rules.md +6 -0
  42. package/runtime/agents/provider-configurator/capabilities/collect-provider-credentials/workflow.md +7 -0
  43. package/runtime/agents/provider-configurator/capabilities/configure-provider-source/capability.yaml +15 -0
  44. package/runtime/agents/provider-configurator/capabilities/configure-provider-source/decision-rules.md +6 -0
  45. package/runtime/agents/provider-configurator/capabilities/configure-provider-source/workflow.md +8 -0
  46. package/runtime/agents/provider-configurator/capabilities/validate-provider-readiness/capability.yaml +15 -0
  47. package/runtime/agents/provider-configurator/capabilities/validate-provider-readiness/decision-rules.md +6 -0
  48. package/runtime/agents/provider-configurator/capabilities/validate-provider-readiness/workflow.md +7 -0
  49. package/runtime/agents/provider-configurator/infra/README.md +5 -0
  50. package/runtime/agents/provider-configurator/knowledge/context.md +8 -0
  51. package/runtime/agents/provider-configurator/knowledge/system.md +19 -0
  52. package/runtime/agents/provider-configurator/templates/README.md +4 -0
  53. package/runtime/agents/task-orchestrator/AGENTS.md +8 -0
  54. package/runtime/agents/task-orchestrator/README.md +7 -0
  55. package/runtime/agents/task-orchestrator/agent.yaml +31 -0
  56. package/runtime/agents/task-orchestrator/capabilities/coordinate-execution/capability.yaml +15 -0
  57. package/runtime/agents/task-orchestrator/capabilities/coordinate-execution/decision-rules.md +6 -0
  58. package/runtime/agents/task-orchestrator/capabilities/coordinate-execution/workflow.md +7 -0
  59. package/runtime/agents/task-orchestrator/capabilities/plan-task/capability.yaml +15 -0
  60. package/runtime/agents/task-orchestrator/capabilities/plan-task/decision-rules.md +6 -0
  61. package/runtime/agents/task-orchestrator/capabilities/plan-task/workflow.md +8 -0
  62. package/runtime/agents/task-orchestrator/capabilities/select-specialists/capability.yaml +15 -0
  63. package/runtime/agents/task-orchestrator/capabilities/select-specialists/decision-rules.md +6 -0
  64. package/runtime/agents/task-orchestrator/capabilities/select-specialists/workflow.md +7 -0
  65. package/runtime/agents/task-orchestrator/infra/README.md +5 -0
  66. package/runtime/agents/task-orchestrator/knowledge/context.md +9 -0
  67. package/runtime/agents/task-orchestrator/knowledge/system.md +20 -0
  68. package/runtime/agents/task-orchestrator/templates/README.md +4 -0
  69. package/runtime/cli/README.md +76 -11
  70. package/runtime/cli/aikit/__init__.py +1 -1
  71. package/runtime/cli/aikit/agent_executor.py +66 -0
  72. package/runtime/cli/aikit/agent_registry.py +80 -0
  73. package/runtime/cli/aikit/app_home.py +87 -2
  74. package/runtime/cli/aikit/audit.py +44 -0
  75. package/runtime/cli/aikit/control_router.py +298 -0
  76. package/runtime/cli/aikit/decision_store.py +17 -0
  77. package/runtime/cli/aikit/diagnostics.py +2 -1
  78. package/runtime/cli/aikit/execution_reviewer.py +174 -0
  79. package/runtime/cli/aikit/install.py +4 -3
  80. package/runtime/cli/aikit/local_llm_operator.py +112 -0
  81. package/runtime/cli/aikit/lock.py +4 -1
  82. package/runtime/cli/aikit/main.py +355 -80
  83. package/runtime/cli/aikit/model_router.py +13 -4
  84. package/runtime/cli/aikit/orchestrator.py +535 -0
  85. package/runtime/cli/aikit/sessions.py +37 -0
  86. package/runtime/cli/aikit/sources.py +6 -2
  87. package/runtime/cli/aikit/wizard_state.py +503 -0
@@ -0,0 +1,174 @@
1
+ """Runtime execution for the execution-reviewer agent."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from cli.aikit.llm import BACKENDS, candidate_backend_ids, doctor_backend, invoke_agent_prompt, load_config
8
+ from cli.aikit.review_gate import mark_reviewed
9
+
10
+
11
+ PREFERRED_REVIEW_BACKENDS = ("claude-code", "codex-cli")
12
+ REVIEW_OK_MARKER = "REVIEW OK"
13
+ REVIEW_BLOCKED_MARKER = "REVIEW BLOCKED"
14
+
15
+
16
+ def enforce_execution_review(
17
+ *,
18
+ prompt: str,
19
+ result: dict[str, Any],
20
+ review_gate: dict[str, Any],
21
+ execution_plan: dict[str, Any] | None = None,
22
+ producer_backend: str | None = None,
23
+ ) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any]]:
24
+ """Run a second concrete review when required and apply the gate outcome."""
25
+ if not review_gate.get("required"):
26
+ review_result = {
27
+ "kind": "execution-review",
28
+ "agent_id": "execution-reviewer",
29
+ "capability_id": "review-final-output",
30
+ "status": "not-required",
31
+ "ok": True,
32
+ }
33
+ return result, review_gate, review_result
34
+
35
+ response = str(result.get("response") or result.get("message") or "")
36
+ review_backend = select_review_backend(producer_backend=producer_backend, preferred=review_gate.get("preferred_reviewers") or [])
37
+ if not review_backend:
38
+ review_result = needs_review("No configured independent reviewer backend is available.", producer_backend=producer_backend)
39
+ return block_for_review(result, review_gate, review_result)
40
+
41
+ review_prompt = build_review_prompt(prompt=prompt, response=response, review_gate=review_gate, execution_plan=execution_plan)
42
+ invoked = invoke_agent_prompt(
43
+ review_prompt,
44
+ review_backend,
45
+ public_name="Execution Reviewer",
46
+ allow_fallback=False,
47
+ )
48
+ decision = parse_review_decision(str(invoked.get("response") or ""))
49
+ review_result = {
50
+ "kind": "execution-review",
51
+ "agent_id": "execution-reviewer",
52
+ "capability_id": "review-final-output",
53
+ "capability": "execution-reviewer.review-final-output",
54
+ "status": review_status(invoked=invoked, decision=decision),
55
+ "ok": bool(invoked.get("ok")) and decision == "approved",
56
+ "llm_backend": invoked.get("llm_backend"),
57
+ "producer_backend": producer_backend,
58
+ "decision": decision,
59
+ "response": invoked.get("response"),
60
+ "message": invoked.get("message"),
61
+ "attempts": invoked.get("llm_backend_attempts"),
62
+ }
63
+ if not invoked.get("ok"):
64
+ return block_for_review(result, review_gate, review_result)
65
+ if decision == "blocked":
66
+ review_result["message"] = "Execution reviewer blocked the proposed final output."
67
+ return block_for_review(result, review_gate, review_result)
68
+ if decision != "approved":
69
+ review_result["message"] = "Execution reviewer did not return REVIEW OK."
70
+ return block_for_review(result, review_gate, review_result)
71
+ reviewed_gate = mark_reviewed(review_gate, reviewer=str(invoked.get("llm_backend") or review_backend), notes=str(invoked.get("response") or "Reviewed."))
72
+ return result, reviewed_gate, review_result
73
+
74
+
75
+ def select_review_backend(*, producer_backend: str | None, preferred: list[str]) -> str | None:
76
+ config = load_config()
77
+ candidates = candidate_backend_ids(config=config, allow_fallback=True)
78
+ preferred_order = [item for item in list(preferred or []) + list(PREFERRED_REVIEW_BACKENDS) if item in BACKENDS]
79
+ ordered = [item for item in preferred_order if item in candidates]
80
+ ordered.extend(item for item in candidates if item not in ordered and item != "ollama")
81
+ for backend_id in ordered:
82
+ if backend_id == producer_backend:
83
+ continue
84
+ backend = doctor_backend(BACKENDS[backend_id], config)
85
+ if backend.get("status") == "ok":
86
+ return backend_id
87
+ return None
88
+
89
+
90
+ def build_review_prompt(
91
+ *,
92
+ prompt: str,
93
+ response: str,
94
+ review_gate: dict[str, Any],
95
+ execution_plan: dict[str, Any] | None,
96
+ ) -> str:
97
+ plan_summary = {}
98
+ if isinstance(execution_plan, dict):
99
+ plan_summary = {
100
+ "status": execution_plan.get("status"),
101
+ "coordinator": (execution_plan.get("coordinator_agent") or {}).get("id") if isinstance(execution_plan.get("coordinator_agent"), dict) else None,
102
+ "specialist_tasks": [
103
+ {
104
+ "agent_id": task.get("agent_id"),
105
+ "capability_id": task.get("capability_id"),
106
+ "status": task.get("status"),
107
+ }
108
+ for task in execution_plan.get("specialist_tasks") or []
109
+ if isinstance(task, dict)
110
+ ],
111
+ }
112
+ return "\n".join(
113
+ [
114
+ "Revise a entrega final do Agent DevKit antes da conclusao.",
115
+ "Verifique aderencia ao pedido, riscos, lacunas, evidencias, uso indevido de credenciais e necessidade de bloquear.",
116
+ "Responda com uma decisao objetiva: REVIEW OK ou REVIEW BLOCKED, seguida de justificativa curta.",
117
+ "",
118
+ f"Motivo do review gate: {review_gate.get('reason')}",
119
+ f"Prompt original: {prompt}",
120
+ f"Resumo do plano: {plan_summary}",
121
+ "",
122
+ "Resposta final proposta:",
123
+ response,
124
+ ]
125
+ )
126
+
127
+
128
+ def parse_review_decision(response: str) -> str:
129
+ upper = response.upper()
130
+ if REVIEW_BLOCKED_MARKER in upper:
131
+ return "blocked"
132
+ if REVIEW_OK_MARKER in upper:
133
+ return "approved"
134
+ return "unstructured"
135
+
136
+
137
+ def review_status(*, invoked: dict[str, Any], decision: str) -> str:
138
+ if not invoked.get("ok"):
139
+ return str(invoked.get("status", "failed"))
140
+ if decision == "blocked":
141
+ return "blocked"
142
+ if decision != "approved":
143
+ return "needs-review"
144
+ return "ok"
145
+
146
+
147
+ def needs_review(message: str, *, producer_backend: str | None) -> dict[str, Any]:
148
+ return {
149
+ "kind": "execution-review",
150
+ "agent_id": "execution-reviewer",
151
+ "capability_id": "review-final-output",
152
+ "capability": "execution-reviewer.review-final-output",
153
+ "status": "needs-review",
154
+ "ok": False,
155
+ "producer_backend": producer_backend,
156
+ "message": message,
157
+ }
158
+
159
+
160
+ def block_for_review(
161
+ result: dict[str, Any],
162
+ review_gate: dict[str, Any],
163
+ review_result: dict[str, Any],
164
+ ) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any]]:
165
+ blocked = dict(result)
166
+ blocked["status"] = "needs-review"
167
+ blocked["ok"] = False
168
+ blocked["exit_code"] = 2
169
+ blocked["message"] = review_result.get("message") or "Execution requires review before completion."
170
+ gate = dict(review_gate)
171
+ gate["status"] = "needs-review"
172
+ gate["reviewer"] = None
173
+ gate["notes"] = review_result.get("message")
174
+ return blocked, gate, review_result
@@ -9,7 +9,7 @@ from pathlib import Path
9
9
  from typing import Any
10
10
 
11
11
  from cli.aikit import __version__
12
- from cli.aikit.lock import lock_path, write_lock
12
+ from cli.aikit.lock import GLOBAL_RUNTIME_DIR, PROJECT_RUNTIME_DIR, lock_path, write_lock
13
13
 
14
14
 
15
15
  HOST_ALIASES = {
@@ -46,7 +46,7 @@ def install_runtime(
46
46
  hosts = resolve_hosts(host)
47
47
  root = root.resolve()
48
48
  base = resolve_base(scope, target=target, home=home)
49
- runtime_dir = base / ".ai-devkit"
49
+ runtime_dir = base / (GLOBAL_RUNTIME_DIR if scope == "global" else PROJECT_RUNTIME_DIR)
50
50
  bin_dir = runtime_dir / "bin"
51
51
  config_path = runtime_dir / "config.yaml"
52
52
  runtime_lock_path = lock_path(base, scope)
@@ -256,7 +256,8 @@ def write_runtime_config(path: Path, root: Path, scope: str, hosts: tuple[str, .
256
256
 
257
257
 
258
258
  def next_steps(scope: str, hosts: tuple[str, ...]) -> list[str]:
259
- steps = ["Add `.ai-devkit/bin` to PATH when you want to call the installed `agent` directly."]
259
+ runtime_dir_name = GLOBAL_RUNTIME_DIR if scope == "global" else PROJECT_RUNTIME_DIR
260
+ steps = [f"Add `{runtime_dir_name}/bin` to PATH when you want to call the installed `agent` directly."]
260
261
  steps.append("Run `agent doctor --json` to validate the local runtime.")
261
262
  if "codex" in hosts:
262
263
  steps.append("Restart or reload Codex so it can discover the AI DevKit plugin/skill.")
@@ -0,0 +1,112 @@
1
+ """Runtime execution for the local-llm-operator agent."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from cli.aikit.llm import invoke_agent_prompt
8
+ from cli.aikit.memory import redact_secrets
9
+
10
+
11
+ FORBIDDEN_DELEGATION_MARKERS = (
12
+ "aprove",
13
+ "aprovar",
14
+ "reprove",
15
+ "reprovar",
16
+ "deploy",
17
+ "delete",
18
+ "excluir",
19
+ "execute escrita",
20
+ "permissao",
21
+ "permissão",
22
+ )
23
+
24
+
25
+ def maybe_delegate_local_llm(prompt: str, model_plan: dict[str, Any]) -> dict[str, Any]:
26
+ """Execute a bounded operational task with Ollama when the model plan selected it."""
27
+ delegation = model_plan.get("delegation") if isinstance(model_plan.get("delegation"), dict) else {}
28
+ if not model_plan.get("local_llm_selected") or not delegation.get("selected"):
29
+ return skipped("not-selected", "Local LLM delegation was not selected for this prompt.", model_plan=model_plan)
30
+ lowered = prompt.lower()
31
+ if any(marker in lowered for marker in FORBIDDEN_DELEGATION_MARKERS):
32
+ return skipped("forbidden", "Prompt contains an action that local LLM workers cannot execute.", model_plan=model_plan)
33
+ delegated_prompt = build_delegated_prompt(prompt, model_plan)
34
+ result = invoke_agent_prompt(
35
+ delegated_prompt,
36
+ "ollama",
37
+ public_name="Local LLM Operator",
38
+ allow_fallback=False,
39
+ )
40
+ payload = {
41
+ "kind": "local-llm-execution",
42
+ "agent_id": "local-llm-operator",
43
+ "capability_id": "delegate-operational-task",
44
+ "capability": "local-llm-operator.delegate-operational-task",
45
+ "status": "ok" if result.get("ok") else result.get("status", "failed"),
46
+ "ok": bool(result.get("ok")),
47
+ "llm_backend": result.get("llm_backend"),
48
+ "model_provider": "ollama",
49
+ "delegated_prompt": redact_secrets(delegated_prompt),
50
+ "response": result.get("response"),
51
+ "result": sanitize_llm_result(result),
52
+ "requires_review": True,
53
+ }
54
+ if not result.get("ok"):
55
+ payload["message"] = result.get("message") or "Local LLM delegation failed."
56
+ return payload
57
+
58
+
59
+ def build_delegated_prompt(prompt: str, model_plan: dict[str, Any]) -> str:
60
+ forbidden = ", ".join((model_plan.get("delegation") or {}).get("forbidden_actions") or [])
61
+ return "\n".join(
62
+ [
63
+ "Execute apenas a parte operacional da tarefa abaixo.",
64
+ "Nao tome decisao final, nao aprove, nao escreva externamente e nao revise a entrega final.",
65
+ f"Acoes proibidas: {forbidden}.",
66
+ "Retorne um resumo estruturado, evidencias extraidas, lacunas e confianca.",
67
+ "",
68
+ "Tarefa original:",
69
+ redact_secrets(prompt),
70
+ ]
71
+ )
72
+
73
+
74
+ def enrich_prompt_with_local_result(prompt: str, local_execution: dict[str, Any]) -> str:
75
+ if not local_execution.get("ok") or not local_execution.get("response"):
76
+ return prompt
77
+ return "\n".join(
78
+ [
79
+ prompt,
80
+ "",
81
+ "Contexto operacional produzido pelo local-llm-operator/Ollama:",
82
+ str(local_execution["response"]),
83
+ "",
84
+ "Use esse contexto apenas como apoio. A decisao, resposta final e revisao continuam sob responsabilidade do coordenador.",
85
+ ]
86
+ )
87
+
88
+
89
+ def skipped(reason: str, message: str, *, model_plan: dict[str, Any]) -> dict[str, Any]:
90
+ return {
91
+ "kind": "local-llm-execution",
92
+ "agent_id": "local-llm-operator",
93
+ "capability_id": "delegate-operational-task",
94
+ "capability": "local-llm-operator.delegate-operational-task",
95
+ "status": "skipped",
96
+ "ok": False,
97
+ "reason": reason,
98
+ "message": message,
99
+ "model_provider": model_plan.get("local_llm_provider") or "ollama",
100
+ "requires_review": bool(model_plan.get("local_llm_recommended") or model_plan.get("local_llm_selected")),
101
+ }
102
+
103
+
104
+ def sanitize_llm_result(result: dict[str, Any]) -> dict[str, Any]:
105
+ return {
106
+ "status": result.get("status"),
107
+ "ok": result.get("ok"),
108
+ "llm_backend": result.get("llm_backend"),
109
+ "llm_backend_attempts": result.get("llm_backend_attempts"),
110
+ "message": result.get("message"),
111
+ "exit_code": result.get("exit_code"),
112
+ }
@@ -14,11 +14,14 @@ from cli.aikit import __version__
14
14
 
15
15
  PROJECT_LOCK_NAME = "ai-devkit.lock"
16
16
  GLOBAL_LOCK_NAME = "runtime.lock"
17
+ PROJECT_RUNTIME_DIR = ".ai-devkit"
18
+ GLOBAL_RUNTIME_DIR = ".agent-devkit"
17
19
 
18
20
 
19
21
  def lock_path(base: Path, scope: str) -> Path:
20
22
  name = GLOBAL_LOCK_NAME if scope == "global" else PROJECT_LOCK_NAME
21
- return base / ".ai-devkit" / name
23
+ runtime_dir = GLOBAL_RUNTIME_DIR if scope == "global" else PROJECT_RUNTIME_DIR
24
+ return base / runtime_dir / name
22
25
 
23
26
 
24
27
  def write_lock(