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
@@ -14,11 +14,16 @@ from typing import Any
14
14
 
15
15
  from cli.aikit.aliases import add_alias, list_aliases, remove_alias, sync_aliases
16
16
  from cli.aikit import __version__
17
+ from cli.aikit.agent_executor import execute_plan_tasks
18
+ from cli.aikit.app_home import app_home_status, migrate_default_home
17
19
  from cli.aikit.audit import export_audit, list_audits, record_audit, show_audit
18
20
  from cli.aikit.calendar import calendar_list, calendar_summary, calendar_today, calendar_tomorrow, configure_calendar
19
21
  from cli.aikit.configuration_orchestrator import provider_wizard_from_requirement
20
- from cli.aikit.decision_store import list_decisions, reset_decisions, set_decision
22
+ from cli.aikit.control_router import dispatch_natural_control_prompt as route_natural_control_prompt
23
+ from cli.aikit.control_router import plan_natural_control_prompt
24
+ from cli.aikit.decision_store import forget_decision, list_decisions, reset_decisions, set_decision
21
25
  from cli.aikit.diagnostics import build_diagnostics
26
+ from cli.aikit.execution_reviewer import enforce_execution_review
22
27
  from cli.aikit.fallback import evaluate_provider_requirements
23
28
  from cli.aikit.github_pr import pr_create_automation, pr_inspect, pr_list_review_requests, pr_review
24
29
  from cli.aikit.guardrails import evaluate_execution_guardrails
@@ -35,10 +40,17 @@ from cli.aikit.llm import (
35
40
  from cli.aikit.memory import memory_path_payload, napkin_context, record_usage, reset_memory, show_memory
36
41
  from cli.aikit.model_router import build_model_plan
37
42
  from cli.aikit.ollama import ollama_models, ollama_pull, ollama_status, ollama_update
43
+ from cli.aikit.local_llm_operator import enrich_prompt_with_local_result, maybe_delegate_local_llm
44
+ from cli.aikit.orchestrator import (
45
+ attach_source_to_primary_task,
46
+ build_execution_plan,
47
+ mark_plan_after_execution,
48
+ mark_review_task,
49
+ )
38
50
  from cli.aikit.personality import load_personality, reset_personality, setup_personality, update_personality
39
51
  from cli.aikit.permissions import grant_permission, revoke_permission, show_permissions
40
52
  from cli.aikit.provider_wizard import missing_source_wizard
41
- from cli.aikit.review_gate import build_review_gate, mark_reviewed
53
+ from cli.aikit.review_gate import build_review_gate
42
54
  from cli.aikit.sessions import (
43
55
  build_contextual_prompt,
44
56
  get_or_create_session,
@@ -85,6 +97,14 @@ from cli.aikit.sources import (
85
97
  source_env,
86
98
  source_status,
87
99
  )
100
+ from cli.aikit.wizard_state import (
101
+ WizardStateError,
102
+ answer_wizard,
103
+ cancel_wizard,
104
+ create_provider_wizard,
105
+ list_wizards,
106
+ show_wizard,
107
+ )
88
108
 
89
109
 
90
110
  DEFAULT_ROOT = Path(__file__).resolve().parents[2]
@@ -122,6 +142,7 @@ DETERMINISTIC_COMMANDS = (
122
142
  "decisions",
123
143
  "ollama",
124
144
  "install",
145
+ "wizard",
125
146
  )
126
147
  LLM_COMMANDS = ("agent",)
127
148
 
@@ -144,6 +165,8 @@ def main(argv: list[str] | None = None, *, prog: str | None = None) -> int:
144
165
 
145
166
  if result is None:
146
167
  return 0
168
+ if not getattr(args, "json", False):
169
+ result = maybe_run_interactive_wizard(result)
147
170
  maybe_record_cli_audit(args, result=result, error=None)
148
171
 
149
172
  if getattr(args, "json", False):
@@ -223,6 +246,14 @@ def build_parser(prog: str | None = None) -> argparse.ArgumentParser:
223
246
  source_parser.add_argument("--default-for-agent", action="append", default=[], help="agent id that should use this source by default")
224
247
  source_parser.add_argument("--set-default", action="store_true", help="set as default source for its provider")
225
248
 
249
+ wizard_parser = subparsers.add_parser("wizard", help="continue agentic setup wizards")
250
+ wizard_parser.add_argument("--json", action="store_true", default=argparse.SUPPRESS, help=argparse.SUPPRESS)
251
+ wizard_parser.add_argument("action", nargs="?", default="list", choices=["list", "show", "answer", "cancel"])
252
+ wizard_parser.add_argument("wizard_id", nargs="?")
253
+ wizard_parser.add_argument("answer", nargs="*")
254
+ wizard_parser.add_argument("--status", help="filter wizard list by status")
255
+ wizard_parser.add_argument("--no-run", action="store_true", help="do not resume the original prompt after completing a wizard")
256
+
226
257
  memory_parser = subparsers.add_parser("memory", help="inspect or reset local AI DevKit memory")
227
258
  memory_parser.add_argument("--json", action="store_true", default=argparse.SUPPRESS, help=argparse.SUPPRESS)
228
259
  memory_parser.add_argument("action", nargs="?", default="show", choices=["show", "path", "reset"])
@@ -320,7 +351,8 @@ def build_parser(prog: str | None = None) -> argparse.ArgumentParser:
320
351
 
321
352
  config_parser = subparsers.add_parser("config", help="inspect local Agent DevKit configuration")
322
353
  config_parser.add_argument("--json", action="store_true", default=argparse.SUPPRESS, help=argparse.SUPPRESS)
323
- config_parser.add_argument("action", nargs="?", default="show", choices=["show", "path"])
354
+ config_parser.add_argument("action", nargs="?", default="show", choices=["show", "path", "migrate-home"])
355
+ config_parser.add_argument("--dry-run", action="store_true", help="plan home migration without moving files")
324
356
 
325
357
  for command_name, help_text in (
326
358
  ("tools", "manage enabled local tools"),
@@ -440,6 +472,8 @@ def dispatch(args: argparse.Namespace) -> dict[str, Any] | None:
440
472
  return dispatch_credential(args)
441
473
  if command == "source":
442
474
  return dispatch_source(args)
475
+ if command == "wizard":
476
+ return dispatch_wizard(args)
443
477
  if command == "memory":
444
478
  return dispatch_memory(args)
445
479
  if command == "personality":
@@ -607,12 +641,15 @@ def dispatch_config(args: argparse.Namespace) -> dict[str, Any]:
607
641
  from cli.aikit.llm import config_path
608
642
 
609
643
  if args.action == "path":
610
- return {"kind": "config", "status": "ok", "path": str(config_path())}
644
+ return {"kind": "config", "status": "ok", "path": str(config_path()), "home": app_home_status()}
645
+ if args.action == "migrate-home":
646
+ return migrate_default_home(dry_run=effective_dry_run(args))
611
647
  if args.action == "show":
612
648
  return {
613
649
  "kind": "config",
614
650
  "status": "ok",
615
651
  "path": str(config_path()),
652
+ "home": app_home_status(),
616
653
  "decisions": list_decisions(),
617
654
  "llm": llm_preference(),
618
655
  "ollama": ollama_status(),
@@ -655,7 +692,7 @@ def dispatch_decisions(args: argparse.Namespace) -> dict[str, Any]:
655
692
  if not args.item_id:
656
693
  raise DevKitError("decisions forget requires an item id")
657
694
  category = args.category or "tools"
658
- return set_decision(category, args.item_id, "available", reason="decision forgotten")
695
+ return forget_decision(category, args.item_id)
659
696
  except ValueError as exc:
660
697
  raise DevKitError(str(exc)) from exc
661
698
  raise DevKitError(f"unsupported decisions action: {args.action}")
@@ -776,6 +813,117 @@ def dispatch_source(args: argparse.Namespace) -> dict[str, Any]:
776
813
  raise DevKitError(f"unsupported source action: {args.action}")
777
814
 
778
815
 
816
+ def dispatch_wizard(args: argparse.Namespace) -> dict[str, Any]:
817
+ try:
818
+ if args.action == "list":
819
+ return list_wizards(status=args.status)
820
+ if args.action == "show":
821
+ require_id(args.wizard_id, "wizard show")
822
+ return show_wizard(args.wizard_id)
823
+ if args.action == "cancel":
824
+ require_id(args.wizard_id, "wizard cancel")
825
+ return cancel_wizard(args.wizard_id)
826
+ if args.action == "answer":
827
+ require_id(args.wizard_id, "wizard answer")
828
+ answer = " ".join(args.answer or []).strip()
829
+ if not answer:
830
+ raise DevKitError("wizard answer requires an answer")
831
+ payload = answer_wizard(args.wizard_id, answer)
832
+ if payload.get("status") == "completed" and not args.no_run:
833
+ resume_prompt = payload.get("resume_prompt") or (payload.get("wizard") or {}).get("resume_prompt")
834
+ if resume_prompt:
835
+ payload["resumed_prompt"] = True
836
+ payload["resume_result"] = resume_agent_prompt(str(resume_prompt))
837
+ else:
838
+ payload["resumed_prompt"] = False
839
+ else:
840
+ payload.setdefault("resumed_prompt", False)
841
+ return payload
842
+ except WizardStateError as exc:
843
+ raise DevKitError(str(exc)) from exc
844
+ except SourceRegistryError as exc:
845
+ raise DevKitError(str(exc)) from exc
846
+ raise DevKitError(f"unsupported wizard action: {args.action}")
847
+
848
+
849
+ def maybe_run_interactive_wizard(result: dict[str, Any]) -> dict[str, Any]:
850
+ if not sys.stdin.isatty() or not sys.stdout.isatty():
851
+ return result
852
+ wizard = result.get("setup_wizard") if isinstance(result.get("setup_wizard"), dict) else None
853
+ if not wizard or not wizard.get("wizard_id") or result.get("status") != "needs-input":
854
+ return result
855
+ print(result.get("message") or "O agente precisa de configuracao antes de continuar.")
856
+ return run_interactive_wizard(str(wizard["wizard_id"]))
857
+
858
+
859
+ def run_interactive_wizard(wizard_id: str) -> dict[str, Any]:
860
+ payload = show_wizard(wizard_id)
861
+ while True:
862
+ wizard = payload.get("wizard") if isinstance(payload.get("wizard"), dict) else {}
863
+ question = payload.get("next_question") or wizard.get("next_question")
864
+ if not isinstance(question, dict):
865
+ return payload
866
+ print_interactive_question(question)
867
+ try:
868
+ answer = input("> ")
869
+ except (EOFError, KeyboardInterrupt):
870
+ print()
871
+ return cancel_wizard(wizard_id, reason="interactive wizard interrupted")
872
+ if answer.strip().lower() in {"cancelar", "cancel", "sair", "exit"}:
873
+ return cancel_wizard(wizard_id, reason="interactive wizard cancelled by user")
874
+ try:
875
+ payload = answer_wizard(wizard_id, answer)
876
+ except WizardStateError as exc:
877
+ print(f"Erro: {exc}")
878
+ continue
879
+ if payload.get("status") == "completed":
880
+ resume_prompt = payload.get("resume_prompt") or (payload.get("wizard") or {}).get("resume_prompt")
881
+ if resume_prompt:
882
+ payload["resumed_prompt"] = True
883
+ payload["resume_result"] = resume_agent_prompt(str(resume_prompt))
884
+ else:
885
+ payload["resumed_prompt"] = False
886
+ return payload
887
+ if payload.get("status") in {"cancelled", "denied-by-user", "failed"}:
888
+ payload.setdefault("resumed_prompt", False)
889
+ return payload
890
+
891
+
892
+ def print_interactive_question(question: dict[str, Any]) -> None:
893
+ text = question.get("text") or "Informe a resposta."
894
+ print(f"\nPergunta: {text}")
895
+ if question.get("type") == "confirm":
896
+ print("[s/N]")
897
+ options = question.get("options")
898
+ if isinstance(options, list) and options:
899
+ print("Opcoes: " + ", ".join(str(item) for item in options))
900
+ if question.get("suggested_value"):
901
+ print(f"Sugestao: {question['suggested_value']}")
902
+ if question.get("env_ref_key"):
903
+ print("Informe o nome da variavel de ambiente, nao o valor da credencial.")
904
+ print("Digite 'cancelar' para interromper.")
905
+
906
+
907
+ def resume_agent_prompt(prompt: str) -> dict[str, Any]:
908
+ args = argparse.Namespace(
909
+ command="agent",
910
+ prompt=prompt.split(),
911
+ session_id=None,
912
+ new_session=False,
913
+ llm=None,
914
+ no_llm_fallback=False,
915
+ dry_run=False,
916
+ global_dry_run=False,
917
+ prog_name="agent",
918
+ json=True,
919
+ version=False,
920
+ sessions_shortcut=False,
921
+ )
922
+ result = agent_requires_llm(args)
923
+ result.pop("audit", None)
924
+ return result
925
+
926
+
779
927
  def dispatch_memory(args: argparse.Namespace) -> dict[str, Any]:
780
928
  if args.action == "show":
781
929
  return show_memory(ROOT, agent_id=args.agent_id, source_id=args.source_id)
@@ -1093,29 +1241,124 @@ def agent_requires_llm(args: argparse.Namespace) -> dict[str, Any]:
1093
1241
  return finalize_agent_session(natural_result, session, prompt, backend=args.llm)
1094
1242
  route = route_prompt(prompt)
1095
1243
  if route:
1096
- result = invoke_deterministic_route(prompt, route)
1244
+ result = invoke_agentic_route(prompt, route)
1097
1245
  return finalize_agent_session(result, session, prompt, backend=args.llm)
1098
1246
  contextual_prompt = build_contextual_prompt(str(session["id"]), prompt)
1099
- model_plan = build_model_plan(prompt)
1247
+ execution_plan = build_execution_plan(ROOT, prompt, dry_run=False)
1248
+ if execution_plan.get("configuration_tasks"):
1249
+ result = agentic_needs_input_from_plan(prompt, execution_plan)
1250
+ return finalize_agent_session(result, session, prompt, backend=args.llm)
1251
+ model_plan = execution_plan.get("model_plan") if isinstance(execution_plan.get("model_plan"), dict) else build_model_plan(prompt)
1252
+ review_gate = execution_plan.get("review_gate") if isinstance(execution_plan.get("review_gate"), dict) else build_review_gate(prompt, model_plan=model_plan)
1253
+ local_llm_execution = maybe_delegate_local_llm(prompt, model_plan)
1254
+ coordinator_prompt = enrich_prompt_with_local_result(contextual_prompt, local_llm_execution)
1100
1255
  result = invoke_agent_prompt(
1101
- contextual_prompt,
1256
+ coordinator_prompt,
1102
1257
  args.llm,
1103
1258
  public_name=name,
1104
1259
  allow_fallback=not args.no_llm_fallback,
1105
1260
  )
1106
- review_gate = build_review_gate(prompt, model_plan=model_plan)
1261
+ review_result = {
1262
+ "kind": "execution-review",
1263
+ "agent_id": "execution-reviewer",
1264
+ "capability_id": "review-final-output",
1265
+ "status": "not-run",
1266
+ "ok": False,
1267
+ }
1107
1268
  if result.get("ok"):
1108
- review_gate = mark_reviewed(review_gate, reviewer=str(result.get("llm_backend") or "coordinator"))
1269
+ result, review_gate, review_result = enforce_execution_review(
1270
+ prompt=prompt,
1271
+ result=result,
1272
+ review_gate=review_gate,
1273
+ execution_plan=execution_plan,
1274
+ producer_backend=str(result.get("llm_backend") or args.llm or ""),
1275
+ )
1276
+ if review_result.get("ok"):
1277
+ execution_plan = mark_review_task(execution_plan, reviewer=str(review_result.get("llm_backend") or "execution-reviewer"))
1278
+ elif review_gate.get("status") == "needs-review":
1279
+ execution_plan = mark_review_task_needs_review(execution_plan, review_result)
1109
1280
  result["model_plan"] = model_plan
1281
+ result["local_llm_execution"] = local_llm_execution
1110
1282
  result["review_gate"] = review_gate
1283
+ result["review_result"] = review_result
1284
+ result["review_task"] = execution_plan.get("review_task")
1285
+ result["execution_plan"] = execution_plan
1286
+ result["orchestration_trace"] = execution_plan.get("trace", [])
1111
1287
  result["prompt_length"] = len(prompt)
1112
1288
  result["session_context_applied"] = contextual_prompt != prompt
1289
+ result["local_context_applied"] = coordinator_prompt != contextual_prompt
1113
1290
  if result.get("response"):
1114
1291
  result["response"] = enforce_identity_response(str(result["response"]), prompt, name=name)
1115
1292
  result["identity"] = {"name": name, "source": "local"}
1116
1293
  return finalize_agent_session(result, session, prompt, backend=result.get("llm_backend") or args.llm)
1117
1294
 
1118
1295
 
1296
+ def mark_review_task_needs_review(execution_plan: dict[str, Any], review_result: dict[str, Any]) -> dict[str, Any]:
1297
+ task = dict(execution_plan.get("review_task") or {})
1298
+ if task:
1299
+ task["status"] = "needs-review"
1300
+ task["reviewer"] = None
1301
+ task["message"] = review_result.get("message")
1302
+ execution_plan["review_task"] = task
1303
+ return execution_plan
1304
+
1305
+
1306
+ def agentic_needs_input_from_plan(prompt: str, execution_plan: dict[str, Any]) -> dict[str, Any]:
1307
+ configuration_task = next(
1308
+ (task for task in execution_plan.get("configuration_tasks") or [] if isinstance(task, dict)),
1309
+ {},
1310
+ )
1311
+ wizard = configuration_task.get("setup_wizard") if isinstance(configuration_task.get("setup_wizard"), dict) else {}
1312
+ provider = configuration_task.get("provider") or wizard.get("provider")
1313
+ payload = {
1314
+ "kind": "agent",
1315
+ "status": "needs-input",
1316
+ "ok": False,
1317
+ "mode": "agentic-plan",
1318
+ "prompt_received": True,
1319
+ "prompt_length": len(prompt),
1320
+ "provider": provider,
1321
+ "source_provider": provider,
1322
+ "requires_source": True,
1323
+ "execution_plan": execution_plan,
1324
+ "orchestration_trace": execution_plan.get("trace", []),
1325
+ "setup_wizard": wizard,
1326
+ "next_question": wizard.get("next_question"),
1327
+ "message": wizard.get("message") or "O plano multiagente precisa de configuracao antes de executar.",
1328
+ "next_steps": [
1329
+ "Responda a pergunta do wizard para autorizar ou negar a configuracao deste provider.",
1330
+ "Depois de configurar a fonte, reexecute ou retome o mesmo prompt.",
1331
+ ],
1332
+ "exit_code": 2,
1333
+ }
1334
+ return persist_setup_wizard_payload(payload, execution_plan=execution_plan)
1335
+
1336
+
1337
+ def persist_setup_wizard_payload(
1338
+ payload: dict[str, Any],
1339
+ *,
1340
+ execution_plan: dict[str, Any] | None = None,
1341
+ route: dict[str, Any] | None = None,
1342
+ ) -> dict[str, Any]:
1343
+ wizard = payload.get("setup_wizard")
1344
+ if not isinstance(wizard, dict) or wizard.get("wizard_id"):
1345
+ return payload
1346
+ persisted = create_provider_wizard(
1347
+ wizard,
1348
+ execution_plan=execution_plan or payload.get("execution_plan"),
1349
+ route=route or payload.get("route"),
1350
+ )
1351
+ payload["setup_wizard"] = persisted
1352
+ payload["next_question"] = persisted.get("next_question")
1353
+ if execution_plan and execution_plan.get("configuration_tasks"):
1354
+ for task in execution_plan.get("configuration_tasks") or []:
1355
+ if isinstance(task, dict) and task.get("provider") == persisted.get("provider"):
1356
+ task["setup_wizard"] = persisted
1357
+ break
1358
+ payload.setdefault("wizard_id", persisted.get("wizard_id"))
1359
+ return payload
1360
+
1361
+
1119
1362
  def dispatch_natural_operational_prompt(prompt: str) -> dict[str, Any] | None:
1120
1363
  normalized = " ".join(prompt.lower().split())
1121
1364
  control_result = dispatch_natural_control_prompt(normalized)
@@ -1171,68 +1414,20 @@ def dispatch_natural_operational_prompt(prompt: str) -> dict[str, Any] | None:
1171
1414
 
1172
1415
 
1173
1416
  def dispatch_natural_control_prompt(normalized_prompt: str) -> dict[str, Any] | None:
1174
- if "decis" in normalized_prompt and any(marker in normalized_prompt for marker in ("mostre", "liste", "ver", "mostrar", "listar")):
1175
- return {
1176
- "kind": "agent",
1177
- "status": "ok",
1178
- "ok": True,
1179
- "mode": "control-center-route",
1180
- "requires_llm": False,
1181
- "response": "Estas sao as decisoes locais registradas.",
1182
- "result": list_decisions(),
1183
- }
1184
- control_targets = {
1185
- "ollama": ("tools", "ollama"),
1186
- "azure devops": ("tools", "azure-devops"),
1187
- "azure": ("tools", "azure-devops"),
1188
- "github": ("integrations", "github"),
1189
- "figma": ("tools", "figma"),
1190
- }
1191
- action: str | None = None
1192
- if any(marker in normalized_prompt for marker in ("desative", "desabilite", "disable", "bloqueie")):
1193
- action = "disable"
1194
- if any(marker in normalized_prompt for marker in ("reative", "ative", "habilite", "enable")):
1195
- action = "enable"
1196
- if not action:
1197
- return None
1198
- for marker, (category, item_id) in control_targets.items():
1199
- if marker in normalized_prompt:
1200
- state = "enabled" if action == "enable" else "disabled_by_user"
1201
- result = set_decision(category, item_id, state, reason=f"natural prompt: {action}")
1202
- return {
1203
- "kind": "agent",
1204
- "status": "ok",
1205
- "ok": True,
1206
- "mode": "control-center-route",
1207
- "requires_llm": False,
1208
- "response": f"{item_id} foi {'ativado' if state == 'enabled' else 'desativado'} para {category}.",
1209
- "result": {
1210
- "category": category,
1211
- "id": item_id,
1212
- "state": state,
1213
- "decision": result.get("item"),
1214
- },
1215
- }
1216
- if any(marker in normalized_prompt for marker in ("ferramentas", "tools")) and any(
1217
- marker in normalized_prompt for marker in ("mostre", "liste", "ver", "mostrar", "listar")
1218
- ):
1219
- return {
1220
- "kind": "agent",
1221
- "status": "ok",
1222
- "ok": True,
1223
- "mode": "control-center-route",
1224
- "requires_llm": False,
1225
- "response": "Estas sao as ferramentas com decisoes locais registradas.",
1226
- "result": list_decisions("tools"),
1227
- }
1228
- return None
1417
+ return route_natural_control_prompt(ROOT, normalized_prompt)
1229
1418
 
1230
1419
 
1231
1420
  def build_agent_dry_run_plan(prompt: str, args: argparse.Namespace) -> dict[str, Any]:
1232
1421
  normalized = " ".join(prompt.lower().split())
1422
+ control_plan = plan_natural_control_prompt(ROOT, normalized)
1423
+ if control_plan:
1424
+ control_plan["prompt_received"] = True
1425
+ control_plan["prompt_length"] = len(prompt)
1426
+ return control_plan
1233
1427
  route = route_prompt(prompt)
1234
- model_plan = build_model_plan(prompt, route=route)
1235
- review_gate = build_review_gate(prompt, route=route, model_plan=model_plan)
1428
+ execution_plan = build_execution_plan(ROOT, prompt, dry_run=True)
1429
+ model_plan = execution_plan.get("model_plan") if isinstance(execution_plan.get("model_plan"), dict) else build_model_plan(prompt, route=route)
1430
+ review_gate = execution_plan.get("review_gate") if isinstance(execution_plan.get("review_gate"), dict) else build_review_gate(prompt, route=route, model_plan=model_plan)
1236
1431
  plan: dict[str, Any] = {
1237
1432
  "kind": "agent",
1238
1433
  "status": "planned",
@@ -1251,6 +1446,8 @@ def build_agent_dry_run_plan(prompt: str, args: argparse.Namespace) -> dict[str,
1251
1446
  "permissions": [],
1252
1447
  "model_plan": model_plan,
1253
1448
  "review_gate": review_gate,
1449
+ "execution_plan": execution_plan,
1450
+ "orchestration_trace": execution_plan.get("trace", []),
1254
1451
  "response": "Dry-run: nenhuma chamada LLM ou escrita externa foi executada.",
1255
1452
  }
1256
1453
  if "agenda" in normalized:
@@ -1324,7 +1521,8 @@ def finalize_agent_session(
1324
1521
  return result
1325
1522
 
1326
1523
 
1327
- def invoke_deterministic_route(prompt: str, route: dict[str, Any]) -> dict[str, Any]:
1524
+ def invoke_agentic_route(prompt: str, route: dict[str, Any]) -> dict[str, Any]:
1525
+ execution_plan = build_execution_plan(ROOT, prompt, dry_run=False)
1328
1526
  try:
1329
1527
  source = resolve_source(
1330
1528
  provider=route.get("provider"),
@@ -1336,7 +1534,14 @@ def invoke_deterministic_route(prompt: str, route: dict[str, Any]) -> dict[str,
1336
1534
 
1337
1535
  if not source:
1338
1536
  wizard = missing_source_wizard(prompt, route, root=ROOT)
1339
- return {
1537
+ if execution_plan.get("configuration_tasks"):
1538
+ execution_plan["configuration_tasks"][0]["setup_wizard"] = wizard
1539
+ execution_plan["status"] = "needs-input"
1540
+ execution_plan["trace"] = [
1541
+ {"agent_id": "task-orchestrator", "action": "plan", "status": "needs-input"},
1542
+ {"agent_id": "provider-configurator", "action": "configure", "status": "waiting-for-user"},
1543
+ ]
1544
+ payload = {
1340
1545
  "kind": "agent",
1341
1546
  "status": "needs-input",
1342
1547
  "ok": False,
@@ -1347,6 +1552,8 @@ def invoke_deterministic_route(prompt: str, route: dict[str, Any]) -> dict[str,
1347
1552
  "prompt_length": len(prompt),
1348
1553
  "route": route,
1349
1554
  "napkin": napkin_context(ROOT, agent_id=route.get("agent_id")),
1555
+ "execution_plan": execution_plan,
1556
+ "orchestration_trace": execution_plan.get("trace", []),
1350
1557
  "setup_wizard": wizard,
1351
1558
  "next_question": wizard.get("next_question"),
1352
1559
  "message": wizard.get("message"),
@@ -1357,21 +1564,51 @@ def invoke_deterministic_route(prompt: str, route: dict[str, Any]) -> dict[str,
1357
1564
  ],
1358
1565
  "exit_code": 2,
1359
1566
  }
1567
+ return persist_setup_wizard_payload(payload, execution_plan=execution_plan, route=route)
1360
1568
 
1361
- agent = load_agent(str(route["agent_id"]))
1362
- capability_args = [*route.get("args", []), "--source", str(source["id"])]
1363
- result = run_capability(agent, str(route["capability_id"]), capability_args, capture_output=True)
1569
+ execution_plan = attach_source_to_primary_task(execution_plan)
1570
+ executed, blocked = execute_plan_tasks(
1571
+ execution_plan,
1572
+ load_agent=load_agent,
1573
+ run_capability=lambda agent, capability_id, capability_args: run_capability(agent, capability_id, capability_args, capture_output=True),
1574
+ )
1575
+ execution_plan = mark_plan_after_execution(execution_plan, executed, blocked)
1576
+ result = (executed[0].get("result") if executed else blocked[0].get("result") if blocked and isinstance(blocked[0].get("result"), dict) else {}) or {}
1364
1577
  response = result.get("stdout") or result.get("error") or ""
1365
1578
  record_usage(prompt, route=route, source_id=str(source["id"]))
1366
- model_plan = build_model_plan(prompt, route=route)
1367
- review_gate = build_review_gate(prompt, route=route, model_plan=model_plan)
1579
+ model_plan = execution_plan.get("model_plan") if isinstance(execution_plan.get("model_plan"), dict) else build_model_plan(prompt, route=route)
1580
+ review_gate = execution_plan.get("review_gate") if isinstance(execution_plan.get("review_gate"), dict) else build_review_gate(prompt, route=route, model_plan=model_plan)
1581
+ review_payload = {
1582
+ "kind": "agent",
1583
+ "status": execution_plan.get("status") if execution_plan.get("status") != "partial" else result.get("status"),
1584
+ "ok": bool(executed) and not blocked,
1585
+ "response": response,
1586
+ }
1587
+ review_result = {
1588
+ "kind": "execution-review",
1589
+ "agent_id": "execution-reviewer",
1590
+ "capability_id": "review-final-output",
1591
+ "status": "not-run",
1592
+ "ok": False,
1593
+ }
1368
1594
  if result.get("ok"):
1369
- review_gate = mark_reviewed(review_gate, reviewer="deterministic-coordinator")
1595
+ review_payload, review_gate, review_result = enforce_execution_review(
1596
+ prompt=prompt,
1597
+ result=review_payload,
1598
+ review_gate=review_gate,
1599
+ execution_plan=execution_plan,
1600
+ producer_backend="deterministic-capability",
1601
+ )
1602
+ if review_result.get("ok"):
1603
+ execution_plan = mark_review_task(execution_plan, reviewer=str(review_result.get("llm_backend") or "execution-reviewer"))
1604
+ elif review_gate.get("status") == "needs-review":
1605
+ execution_plan = mark_review_task_needs_review(execution_plan, review_result)
1370
1606
  return {
1371
1607
  "kind": "agent",
1372
- "status": result.get("status"),
1373
- "ok": result.get("ok", False),
1374
- "mode": "deterministic-route",
1608
+ "status": review_payload.get("status"),
1609
+ "ok": bool(review_payload.get("ok")),
1610
+ "mode": "agentic-route",
1611
+ "legacy_mode": "deterministic-route",
1375
1612
  "prompt_received": True,
1376
1613
  "prompt_length": len(prompt),
1377
1614
  "route": route,
@@ -1379,9 +1616,13 @@ def invoke_deterministic_route(prompt: str, route: dict[str, Any]) -> dict[str,
1379
1616
  "napkin": napkin_context(ROOT, agent_id=route.get("agent_id"), source_id=str(source["id"])),
1380
1617
  "model_plan": model_plan,
1381
1618
  "review_gate": review_gate,
1382
- "response": response,
1619
+ "review_result": review_result,
1620
+ "review_task": execution_plan.get("review_task"),
1621
+ "execution_plan": execution_plan,
1622
+ "orchestration_trace": execution_plan.get("trace", []),
1623
+ "response": review_payload.get("response") or response,
1383
1624
  "result": result,
1384
- "exit_code": result.get("exit_code", 0 if result.get("ok") else 1),
1625
+ "exit_code": review_payload.get("exit_code", result.get("exit_code", 0 if executed and not blocked else 1)),
1385
1626
  }
1386
1627
 
1387
1628
 
@@ -1601,6 +1842,7 @@ def run_capability(
1601
1842
  "Informe uma referencia segura de credencial por variavel de ambiente, arquivo ou cadeia nativa quando solicitado.",
1602
1843
  "Reexecute ou retome a mesma capability depois que a configuracao estiver salva.",
1603
1844
  ]
1845
+ payload = persist_setup_wizard_payload(payload)
1604
1846
  return payload
1605
1847
 
1606
1848
  runner_ref = (data.get("entrypoint", {}) or {}).get("runner")
@@ -1949,6 +2191,8 @@ def print_human(result: dict[str, Any]) -> None:
1949
2191
  print_source_configure(result)
1950
2192
  elif kind == "source-remove":
1951
2193
  print_source_remove(result)
2194
+ elif kind in {"wizards", "wizard"}:
2195
+ print_wizard(result)
1952
2196
  elif kind == "memory":
1953
2197
  print_memory(result)
1954
2198
  elif kind == "memory-path":
@@ -2131,6 +2375,10 @@ def print_agent_response(result: dict[str, Any]) -> None:
2131
2375
  print(f"\nPergunta: {question['text']}")
2132
2376
  if question.get("type") == "confirm":
2133
2377
  print("[s/N]")
2378
+ wizard = result.get("setup_wizard") if isinstance(result.get("setup_wizard"), dict) else {}
2379
+ if wizard.get("wizard_id"):
2380
+ print(f"\nWizard: {wizard['wizard_id']}")
2381
+ print(f"Responder: agent wizard answer {wizard['wizard_id']} <resposta>")
2134
2382
  if result.get("llm_backend"):
2135
2383
  print(f"Requested backend: {result['llm_backend']}")
2136
2384
  if result.get("next_steps"):
@@ -2139,6 +2387,33 @@ def print_agent_response(result: dict[str, Any]) -> None:
2139
2387
  print(f"- {step}")
2140
2388
 
2141
2389
 
2390
+ def print_wizard(result: dict[str, Any]) -> None:
2391
+ if result.get("kind") == "wizards":
2392
+ items = result.get("items") or []
2393
+ print(f"Wizards: {len(items)}")
2394
+ for item in items:
2395
+ print(f"- {item.get('wizard_id')} {item.get('status')} {item.get('provider')}")
2396
+ return
2397
+ wizard = result.get("wizard") if isinstance(result.get("wizard"), dict) else result.get("setup_wizard")
2398
+ if not isinstance(wizard, dict):
2399
+ print(json.dumps(result, ensure_ascii=False, indent=2))
2400
+ return
2401
+ print(f"Wizard {wizard.get('wizard_id')}: {wizard.get('status')}")
2402
+ print(f"Provider: {wizard.get('provider')}")
2403
+ question = result.get("next_question") or wizard.get("next_question")
2404
+ if isinstance(question, dict) and question.get("text"):
2405
+ print(f"Pergunta: {question['text']}")
2406
+ if question.get("type") == "confirm":
2407
+ print("[s/N]")
2408
+ print(f"Responder: agent wizard answer {wizard.get('wizard_id')} <resposta>")
2409
+ if result.get("source_result"):
2410
+ source = result["source_result"].get("source") or {}
2411
+ print(f"Source configurada: {source.get('id')}")
2412
+ if result.get("resumed_prompt"):
2413
+ resume = result.get("resume_result") or {}
2414
+ print(f"Prompt retomado: {resume.get('status')}")
2415
+
2416
+
2142
2417
  def print_sources(result: dict[str, Any]) -> None:
2143
2418
  print(f"Sources config: {result['config_path']}")
2144
2419
  if not result["items"]: