agent-devkit 0.2.0 → 0.3.0

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 (172) hide show
  1. package/README.md +48 -6
  2. package/bin/agent.mjs +133 -7
  3. package/package.json +1 -1
  4. package/runtime/README.md +187 -5
  5. package/runtime/agent +31 -5
  6. package/runtime/agents/README.md +18 -0
  7. package/runtime/agents/contribution-reviewer/AGENTS.md +8 -0
  8. package/runtime/agents/contribution-reviewer/README.md +8 -0
  9. package/runtime/agents/contribution-reviewer/agent.yaml +40 -0
  10. package/runtime/agents/contribution-reviewer/capabilities/plan-contribution-pr/capability.yaml +27 -0
  11. package/runtime/agents/contribution-reviewer/capabilities/plan-contribution-pr/decision-rules.md +5 -0
  12. package/runtime/agents/contribution-reviewer/capabilities/plan-contribution-pr/workflow.md +6 -0
  13. package/runtime/agents/contribution-reviewer/capabilities/review-contribution/capability.yaml +25 -0
  14. package/runtime/agents/contribution-reviewer/capabilities/review-contribution/decision-rules.md +5 -0
  15. package/runtime/agents/contribution-reviewer/capabilities/review-contribution/workflow.md +5 -0
  16. package/runtime/agents/contribution-reviewer/capabilities/validate-local-contribution/capability.yaml +26 -0
  17. package/runtime/agents/contribution-reviewer/capabilities/validate-local-contribution/decision-rules.md +5 -0
  18. package/runtime/agents/contribution-reviewer/capabilities/validate-local-contribution/workflow.md +6 -0
  19. package/runtime/agents/contribution-reviewer/infra/README.md +6 -0
  20. package/runtime/agents/contribution-reviewer/knowledge/context.md +8 -0
  21. package/runtime/agents/contribution-reviewer/knowledge/system.md +8 -0
  22. package/runtime/agents/contribution-reviewer/templates/README.md +3 -0
  23. package/runtime/agents/knowledge-author/AGENTS.md +7 -0
  24. package/runtime/agents/knowledge-author/README.md +7 -0
  25. package/runtime/agents/knowledge-author/agent.yaml +37 -0
  26. package/runtime/agents/knowledge-author/capabilities/create-knowledge-snapshot/capability.yaml +30 -0
  27. package/runtime/agents/knowledge-author/capabilities/create-knowledge-snapshot/decision-rules.md +6 -0
  28. package/runtime/agents/knowledge-author/capabilities/create-knowledge-snapshot/workflow.md +7 -0
  29. package/runtime/agents/knowledge-author/infra/.gitkeep +1 -0
  30. package/runtime/agents/knowledge-author/knowledge/context.md +4 -0
  31. package/runtime/agents/knowledge-author/knowledge/system.md +4 -0
  32. package/runtime/agents/knowledge-author/templates/.gitkeep +1 -0
  33. package/runtime/agents/knowledge-curator/AGENTS.md +7 -0
  34. package/runtime/agents/knowledge-curator/README.md +6 -0
  35. package/runtime/agents/knowledge-curator/agent.yaml +37 -0
  36. package/runtime/agents/knowledge-curator/capabilities/curate-knowledge-base/capability.yaml +29 -0
  37. package/runtime/agents/knowledge-curator/capabilities/curate-knowledge-base/decision-rules.md +6 -0
  38. package/runtime/agents/knowledge-curator/capabilities/curate-knowledge-base/workflow.md +7 -0
  39. package/runtime/agents/knowledge-curator/infra/.gitkeep +1 -0
  40. package/runtime/agents/knowledge-curator/knowledge/context.md +4 -0
  41. package/runtime/agents/knowledge-curator/knowledge/system.md +4 -0
  42. package/runtime/agents/knowledge-curator/templates/.gitkeep +1 -0
  43. package/runtime/agents/knowledge-infra-builder/AGENTS.md +8 -0
  44. package/runtime/agents/knowledge-infra-builder/README.md +8 -0
  45. package/runtime/agents/knowledge-infra-builder/agent.yaml +38 -0
  46. package/runtime/agents/knowledge-infra-builder/capabilities/create-knowledge-base/capability.yaml +30 -0
  47. package/runtime/agents/knowledge-infra-builder/capabilities/create-knowledge-base/decision-rules.md +6 -0
  48. package/runtime/agents/knowledge-infra-builder/capabilities/create-knowledge-base/workflow.md +7 -0
  49. package/runtime/agents/knowledge-infra-builder/infra/.gitkeep +1 -0
  50. package/runtime/agents/knowledge-infra-builder/knowledge/context.md +4 -0
  51. package/runtime/agents/knowledge-infra-builder/knowledge/system.md +4 -0
  52. package/runtime/agents/knowledge-infra-builder/templates/.gitkeep +1 -0
  53. package/runtime/agents/knowledge-owner/AGENTS.md +7 -0
  54. package/runtime/agents/knowledge-owner/README.md +6 -0
  55. package/runtime/agents/knowledge-owner/agent.yaml +37 -0
  56. package/runtime/agents/knowledge-owner/capabilities/publish-knowledge-snapshot/capability.yaml +28 -0
  57. package/runtime/agents/knowledge-owner/capabilities/publish-knowledge-snapshot/decision-rules.md +6 -0
  58. package/runtime/agents/knowledge-owner/capabilities/publish-knowledge-snapshot/workflow.md +7 -0
  59. package/runtime/agents/knowledge-owner/infra/.gitkeep +1 -0
  60. package/runtime/agents/knowledge-owner/knowledge/context.md +4 -0
  61. package/runtime/agents/knowledge-owner/knowledge/system.md +4 -0
  62. package/runtime/agents/knowledge-owner/templates/.gitkeep +1 -0
  63. package/runtime/agents/knowledge-reviewer/AGENTS.md +7 -0
  64. package/runtime/agents/knowledge-reviewer/README.md +7 -0
  65. package/runtime/agents/knowledge-reviewer/agent.yaml +36 -0
  66. package/runtime/agents/knowledge-reviewer/capabilities/review-knowledge-snapshot/capability.yaml +26 -0
  67. package/runtime/agents/knowledge-reviewer/capabilities/review-knowledge-snapshot/decision-rules.md +6 -0
  68. package/runtime/agents/knowledge-reviewer/capabilities/review-knowledge-snapshot/workflow.md +7 -0
  69. package/runtime/agents/knowledge-reviewer/infra/.gitkeep +1 -0
  70. package/runtime/agents/knowledge-reviewer/knowledge/context.md +4 -0
  71. package/runtime/agents/knowledge-reviewer/knowledge/system.md +4 -0
  72. package/runtime/agents/knowledge-reviewer/templates/.gitkeep +1 -0
  73. package/runtime/agents/local-memory-manager/AGENTS.md +5 -0
  74. package/runtime/agents/local-memory-manager/README.md +7 -0
  75. package/runtime/agents/local-memory-manager/agent.yaml +38 -0
  76. package/runtime/agents/local-memory-manager/capabilities/curate-local-memory/capability.yaml +19 -0
  77. package/runtime/agents/local-memory-manager/capabilities/curate-local-memory/decision-rules.md +5 -0
  78. package/runtime/agents/local-memory-manager/capabilities/curate-local-memory/workflow.md +6 -0
  79. package/runtime/agents/local-memory-manager/capabilities/inspect-local-memory/capability.yaml +19 -0
  80. package/runtime/agents/local-memory-manager/capabilities/inspect-local-memory/decision-rules.md +5 -0
  81. package/runtime/agents/local-memory-manager/capabilities/inspect-local-memory/workflow.md +5 -0
  82. package/runtime/agents/local-memory-manager/infra/.gitkeep +1 -0
  83. package/runtime/agents/local-memory-manager/knowledge/context.md +4 -0
  84. package/runtime/agents/local-memory-manager/knowledge/system.md +4 -0
  85. package/runtime/agents/local-memory-manager/templates/.gitkeep +1 -0
  86. package/runtime/agents/memory-sync-manager/AGENTS.md +7 -0
  87. package/runtime/agents/memory-sync-manager/README.md +7 -0
  88. package/runtime/agents/memory-sync-manager/agent.yaml +37 -0
  89. package/runtime/agents/memory-sync-manager/capabilities/plan-memory-backup/capability.yaml +29 -0
  90. package/runtime/agents/memory-sync-manager/capabilities/plan-memory-backup/decision-rules.md +6 -0
  91. package/runtime/agents/memory-sync-manager/capabilities/plan-memory-backup/workflow.md +7 -0
  92. package/runtime/agents/memory-sync-manager/infra/.gitkeep +1 -0
  93. package/runtime/agents/memory-sync-manager/knowledge/context.md +4 -0
  94. package/runtime/agents/memory-sync-manager/knowledge/system.md +4 -0
  95. package/runtime/agents/memory-sync-manager/templates/.gitkeep +1 -0
  96. package/runtime/agents/shared-memory-curator/AGENTS.md +5 -0
  97. package/runtime/agents/shared-memory-curator/README.md +6 -0
  98. package/runtime/agents/shared-memory-curator/agent.yaml +38 -0
  99. package/runtime/agents/shared-memory-curator/capabilities/create-shared-memory/capability.yaml +19 -0
  100. package/runtime/agents/shared-memory-curator/capabilities/create-shared-memory/decision-rules.md +5 -0
  101. package/runtime/agents/shared-memory-curator/capabilities/create-shared-memory/workflow.md +5 -0
  102. package/runtime/agents/shared-memory-curator/capabilities/publish-shared-submission/capability.yaml +19 -0
  103. package/runtime/agents/shared-memory-curator/capabilities/publish-shared-submission/decision-rules.md +5 -0
  104. package/runtime/agents/shared-memory-curator/capabilities/publish-shared-submission/workflow.md +5 -0
  105. package/runtime/agents/shared-memory-curator/capabilities/review-shared-submission/capability.yaml +19 -0
  106. package/runtime/agents/shared-memory-curator/capabilities/review-shared-submission/decision-rules.md +5 -0
  107. package/runtime/agents/shared-memory-curator/capabilities/review-shared-submission/workflow.md +5 -0
  108. package/runtime/agents/shared-memory-curator/infra/.gitkeep +1 -0
  109. package/runtime/agents/shared-memory-curator/knowledge/context.md +5 -0
  110. package/runtime/agents/shared-memory-curator/knowledge/system.md +4 -0
  111. package/runtime/agents/shared-memory-curator/templates/.gitkeep +1 -0
  112. package/runtime/cli/README.md +35 -4
  113. package/runtime/cli/aikit/__init__.py +1 -1
  114. package/runtime/cli/aikit/agent_registry.py +4 -2
  115. package/runtime/cli/aikit/agentic_commands.py +158 -0
  116. package/runtime/cli/aikit/app_home.py +1 -0
  117. package/runtime/cli/aikit/audit.py +16 -6
  118. package/runtime/cli/aikit/catalog.py +278 -8
  119. package/runtime/cli/aikit/cli_dispatch.py +489 -13
  120. package/runtime/cli/aikit/cli_parser.py +145 -7
  121. package/runtime/cli/aikit/contribution.py +132 -2
  122. package/runtime/cli/aikit/doctor_runtime.py +85 -0
  123. package/runtime/cli/aikit/eval.py +356 -10
  124. package/runtime/cli/aikit/human_output.py +310 -4
  125. package/runtime/cli/aikit/interactive_wizard.py +148 -0
  126. package/runtime/cli/aikit/knowledge_base.py +1067 -0
  127. package/runtime/cli/aikit/llm.py +12 -4
  128. package/runtime/cli/aikit/local_artifacts.py +444 -0
  129. package/runtime/cli/aikit/local_llm.py +161 -0
  130. package/runtime/cli/aikit/main.py +15 -0
  131. package/runtime/cli/aikit/mcp_manifest.py +798 -0
  132. package/runtime/cli/aikit/mcp_tools.py +643 -5
  133. package/runtime/cli/aikit/memory.py +405 -0
  134. package/runtime/cli/aikit/mini_brain.py +20 -1
  135. package/runtime/cli/aikit/natural_prompt_runtime.py +125 -1
  136. package/runtime/cli/aikit/ollama.py +64 -15
  137. package/runtime/cli/aikit/onboarding.py +551 -0
  138. package/runtime/cli/aikit/output.py +67 -0
  139. package/runtime/cli/aikit/prompt_injection.py +12 -1
  140. package/runtime/cli/aikit/roadmap_cli.py +1 -1
  141. package/runtime/cli/aikit/secrets.py +3 -2
  142. package/runtime/cli/aikit/setup_wizard_payload.py +3 -0
  143. package/runtime/cli/aikit/shared_memory.py +415 -0
  144. package/runtime/cli/aikit/specialist_readiness.py +152 -0
  145. package/runtime/cli/aikit/tasks.py +104 -1
  146. package/runtime/cli/aikit/team.py +380 -0
  147. package/runtime/cli/aikit/toolchain.py +7 -2
  148. package/runtime/cli/aikit/workflows.py +115 -14
  149. package/runtime/providers/knowledge-github.yaml +40 -0
  150. package/runtime/providers/knowledge-google-drive.yaml +32 -0
  151. package/runtime/providers/knowledge-local.yaml +26 -0
  152. package/runtime/providers/knowledge-notion.yaml +32 -0
  153. package/runtime/providers/knowledge-obsidian.yaml +24 -0
  154. package/runtime/providers/knowledge-onedrive.yaml +36 -0
  155. package/runtime/providers/knowledge-s3.yaml +45 -0
  156. package/runtime/providers/knowledge-sharepoint.yaml +39 -0
  157. package/runtime/providers/knowledge-supabase.yaml +43 -0
  158. package/runtime/providers/knowledge-vector.yaml +39 -0
  159. package/runtime/requirements.txt +6 -0
  160. package/runtime/scripts/docker-cli-qa.sh +453 -0
  161. package/runtime/scripts/release-catalog-snapshot.json +55 -4
  162. package/runtime/scripts/release-gate.py +54 -13
  163. package/runtime/tooling/toolchain.yaml +92 -0
  164. package/runtime/vendor/skills/napkin/napkin.md +21 -7
  165. package/runtime/workflows/azure-card-analysis/README.md +3 -0
  166. package/runtime/workflows/azure-card-analysis/workflow.yaml +30 -0
  167. package/runtime/workflows/daily-pr-review/README.md +3 -0
  168. package/runtime/workflows/daily-pr-review/workflow.yaml +31 -0
  169. package/runtime/workflows/incident-analysis/README.md +3 -0
  170. package/runtime/workflows/incident-analysis/workflow.yaml +33 -0
  171. package/runtime/workflows/release-prep/README.md +3 -0
  172. package/runtime/workflows/release-prep/workflow.yaml +30 -0
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import json
6
+ import os
6
7
  import platform
7
8
  import shutil
8
9
  import subprocess
@@ -10,17 +11,31 @@ import urllib.error
10
11
  import urllib.request
11
12
  from typing import Any
12
13
 
14
+ from cli.aikit.app_home import config_path
15
+
13
16
 
14
17
  OLLAMA_TIMEOUT_SECONDS = 120
15
18
  DEFAULT_BASE_URL = "http://localhost:11434"
19
+ DEFAULT_RECOMMENDED_MODELS = [
20
+ {"name": "qwen3:0.6b", "family": "mini-brain", "recommended_for": "setup help, short summaries and lightweight intent classification"},
21
+ {"name": "qwen2.5-coder", "family": "coding", "recommended_for": "operational code reading and generation"},
22
+ {"name": "deepseek-coder", "family": "coding", "recommended_for": "code analysis and mechanical refactors"},
23
+ {"name": "deepseek-r1", "family": "reasoning", "recommended_for": "local reasoning drafts with mandatory review"},
24
+ {"name": "llama3.2", "family": "general", "recommended_for": "general local summaries and classification"},
25
+ {"name": "mistral", "family": "general", "recommended_for": "lightweight operational summaries"},
26
+ {"name": "gemma", "family": "classification", "recommended_for": "small extraction and classification tasks"},
27
+ ]
16
28
 
17
29
 
18
30
  def ollama_status(*, base_url: str = DEFAULT_BASE_URL) -> dict[str, Any]:
31
+ base_url = os.environ.get("OLLAMA_BASE_URL") or base_url
19
32
  binary = shutil.which("ollama")
20
33
  version = command_output(["ollama", "--version"]) if binary else None
21
34
  daemon = daemon_status(base_url) if binary else {"status": "unknown", "message": "Ollama binary is not installed."}
22
35
  models = list_local_models(binary_available=bool(binary))
23
- status = "ok" if binary else "missing"
36
+ status = "missing"
37
+ if binary:
38
+ status = "ok" if daemon.get("status") == "ok" else "partial"
24
39
  return {
25
40
  "kind": "ollama-status",
26
41
  "status": status,
@@ -52,10 +67,12 @@ def ollama_pull(model: str | None, *, yes: bool = False, dry_run: bool = False)
52
67
  binary = shutil.which("ollama")
53
68
  command = ["ollama", "pull", model]
54
69
  if dry_run or not yes:
70
+ needs_confirmation = not dry_run and not yes
55
71
  return {
56
72
  "kind": "ollama-pull",
57
73
  "status": "planned" if dry_run else "needs-confirmation",
58
74
  "ok": bool(dry_run),
75
+ "exit_code": 2 if needs_confirmation else 0,
59
76
  "model": model,
60
77
  "binary": binary,
61
78
  "command": command,
@@ -91,10 +108,12 @@ def ollama_pull(model: str | None, *, yes: bool = False, dry_run: bool = False)
91
108
  def ollama_update(*, yes: bool = False, dry_run: bool = False) -> dict[str, Any]:
92
109
  command = update_command()
93
110
  if dry_run or not yes:
111
+ needs_confirmation = not dry_run and not yes
94
112
  return {
95
113
  "kind": "ollama-update",
96
114
  "status": "planned" if dry_run else "needs-confirmation",
97
115
  "ok": bool(dry_run),
116
+ "exit_code": 2 if needs_confirmation else 0,
98
117
  "command": command,
99
118
  "dry_run": dry_run,
100
119
  "yes": yes,
@@ -142,26 +161,56 @@ def parse_ollama_list(output: str) -> list[dict[str, Any]]:
142
161
 
143
162
 
144
163
  def recommended_models(*, installed: set[str]) -> list[dict[str, Any]]:
145
- catalog = [
146
- ("qwen3:0.6b", "mini-brain", "setup help, short summaries and lightweight intent classification"),
147
- ("qwen2.5-coder", "coding", "operational code reading and generation"),
148
- ("deepseek-coder", "coding", "code analysis and mechanical refactors"),
149
- ("deepseek-r1", "reasoning", "local reasoning drafts with mandatory review"),
150
- ("llama3.2", "general", "general local summaries and classification"),
151
- ("mistral", "general", "lightweight operational summaries"),
152
- ("gemma", "classification", "small extraction and classification tasks"),
153
- ]
164
+ catalog = model_catalog()
154
165
  return [
155
166
  {
156
- "name": name,
157
- "family": family,
158
- "recommended_for": recommended_for,
159
- "installed": any(item == name or item.startswith(f"{name}:") for item in installed),
167
+ "name": item["name"],
168
+ "family": item["family"],
169
+ "recommended_for": item["recommended_for"],
170
+ "installed": any(model == item["name"] or model.startswith(f"{item['name']}:") for model in installed),
171
+ "source": item["source"],
160
172
  }
161
- for name, family, recommended_for in catalog
173
+ for item in catalog
162
174
  ]
163
175
 
164
176
 
177
+ def model_catalog() -> list[dict[str, str]]:
178
+ items = {item["name"]: {**item, "source": "default"} for item in DEFAULT_RECOMMENDED_MODELS}
179
+ for item in configured_model_catalog():
180
+ name = str(item.get("name") or "").strip()
181
+ if not name:
182
+ continue
183
+ items[name] = {
184
+ "name": name,
185
+ "family": str(item.get("family") or "custom").strip() or "custom",
186
+ "recommended_for": str(item.get("recommended_for") or item.get("purpose") or "configured local model").strip(),
187
+ "source": "config",
188
+ }
189
+ return list(items.values())
190
+
191
+
192
+ def configured_model_catalog() -> list[dict[str, Any]]:
193
+ path = config_path()
194
+ if not path.exists():
195
+ return []
196
+ try:
197
+ payload = json.loads(path.read_text(encoding="utf-8"))
198
+ except (OSError, json.JSONDecodeError):
199
+ return []
200
+ if not isinstance(payload, dict):
201
+ return []
202
+ local_llm = payload.get("local_llm") if isinstance(payload.get("local_llm"), dict) else {}
203
+ mini_brain = payload.get("mini_brain") if isinstance(payload.get("mini_brain"), dict) else {}
204
+ for value in (
205
+ local_llm.get("recommended_models"),
206
+ local_llm.get("models"),
207
+ mini_brain.get("recommended_models"),
208
+ ):
209
+ if isinstance(value, list):
210
+ return [item for item in value if isinstance(item, dict)]
211
+ return []
212
+
213
+
165
214
  def daemon_status(base_url: str) -> dict[str, Any]:
166
215
  url = base_url.rstrip("/") + "/api/tags"
167
216
  try:
@@ -0,0 +1,551 @@
1
+ """First-run and no-argument onboarding for Agent DevKit."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from cli.aikit.agent_registry import load_agent_registry
9
+ from cli.aikit.app_home import app_home_status
10
+ from cli.aikit.llm import doctor_backends, list_backends
11
+ from cli.aikit.memory import ensure_memory
12
+ from cli.aikit.ollama import ollama_status
13
+ from cli.aikit.personality import load_personality
14
+ from cli.aikit.sessions import list_sessions, show_session
15
+ from cli.aikit.setup_wizard import setup_wizard
16
+ from cli.aikit.sources import list_sources, source_status
17
+ from cli.aikit.specialist_readiness import specialist_readiness
18
+ from cli.aikit.tasks import load_tasks, public_task, task_is_due, list_tasks
19
+
20
+
21
+ ONBOARDING_MODE_SCHEMA_VERSION = "agent-devkit.onboarding-plan/v1"
22
+
23
+
24
+ def onboarding_status(root: Path) -> dict[str, Any]:
25
+ """Return a local, deterministic startup snapshot for `agent` without args."""
26
+
27
+ home = app_home_status()
28
+ memory = ensure_memory()
29
+ personality = load_personality()
30
+ sessions = summarize_sessions()
31
+ setup = setup_wizard(root, dry_run=True, yes=False)
32
+ llms = summarize_llms()
33
+ ollama = ollama_status()
34
+ sources = summarize_sources()
35
+ tasks = summarize_tasks()
36
+ specialists = specialist_readiness(root)
37
+
38
+ blockers = startup_blockers(setup=setup, llms=llms)
39
+ suggestions = suggested_actions(
40
+ memory=memory,
41
+ personality=personality,
42
+ sessions=sessions,
43
+ setup=setup,
44
+ llms=llms,
45
+ ollama=ollama,
46
+ sources=sources,
47
+ tasks=tasks,
48
+ specialists=specialists,
49
+ )
50
+ status = "ready"
51
+ if blockers:
52
+ status = "needs-setup"
53
+ elif suggestions:
54
+ status = "needs-attention"
55
+
56
+ return {
57
+ "kind": "onboarding",
58
+ "schema_version": "agent-devkit.onboarding/v1",
59
+ "status": status,
60
+ "agent": {
61
+ "name": personality.get("agent_name") or "Agent DevKit",
62
+ "tone": personality.get("tone"),
63
+ "detail_level": personality.get("detail_level"),
64
+ "language": personality.get("language"),
65
+ "user_name": personality.get("user_name"),
66
+ },
67
+ "home": home,
68
+ "memory": {
69
+ "status": memory.get("status"),
70
+ "home": memory.get("home"),
71
+ "created": memory.get("created") or [],
72
+ "file_count": len(memory.get("files") or []),
73
+ },
74
+ "sessions": {
75
+ "status": sessions.get("status"),
76
+ "home": sessions.get("home"),
77
+ "active_session_id": sessions.get("active_session_id"),
78
+ "count": len(sessions.get("items") or []),
79
+ "active": sessions.get("active"),
80
+ "recent": sessions.get("recent") or [],
81
+ },
82
+ "llm": llms,
83
+ "ollama": summarize_ollama(ollama),
84
+ "toolchain": summarize_toolchain(setup.get("toolchain") or {}),
85
+ "sources": sources,
86
+ "tasks": tasks,
87
+ "specialists": summarize_specialists(specialists),
88
+ "blockers": blockers,
89
+ "suggested_actions": suggestions,
90
+ "onboarding_modes": onboarding_modes(),
91
+ "startup_flow": startup_flow(status=status, sessions=sessions, tasks=tasks, blockers=blockers),
92
+ "assistant_prompt": assistant_prompt(status=status, sessions=sessions, tasks=tasks, blockers=blockers, actions=suggestions),
93
+ }
94
+
95
+
96
+ def onboarding_plan(root: Path, mode: str) -> dict[str, Any]:
97
+ selected_mode = normalize_onboarding_mode(mode)
98
+ status = onboarding_status(root)
99
+ registry = load_agent_registry(root)
100
+ minimal_steps = [
101
+ plan_step(
102
+ "personality",
103
+ "Configurar nome publico, usuario, idioma, tom e nivel de detalhe.",
104
+ "agent setup personality",
105
+ write_policy="local_config_write",
106
+ ),
107
+ plan_step(
108
+ "coordinator-llm",
109
+ "Registrar Claude Code, Codex CLI ou API como coordenador/planejador/revisor.",
110
+ "agent llm list",
111
+ write_policy="local_config_write",
112
+ ),
113
+ plan_step(
114
+ "mini-brain",
115
+ "Habilitar Qwen3-0.6B via Ollama para conversa simples, setup e tarefas operacionais leves.",
116
+ "agent setup mini-brain --dry-run",
117
+ write_policy="local_config_write",
118
+ model="qwen3:0.6b",
119
+ ),
120
+ plan_step(
121
+ "sessions-and-memory",
122
+ "Inicializar memoria local, sessoes, preferencias e identidade em .agent-devkit.",
123
+ "agent memory show",
124
+ write_policy="local_config_write",
125
+ ),
126
+ ]
127
+ complete_steps = minimal_steps + [
128
+ plan_step(
129
+ "toolchain",
130
+ "Revisar CLIs locais e planejar instalacao com opt-in por ferramenta.",
131
+ "agent toolchain doctor",
132
+ write_policy="confirm",
133
+ ),
134
+ plan_step(
135
+ "providers-and-sources",
136
+ "Preparar configuracao sob demanda para providers, sources e credenciais por referencia segura.",
137
+ "agent source list",
138
+ write_policy="local_config_write",
139
+ ),
140
+ plan_step(
141
+ "specialist-catalog",
142
+ "Validar catalogo completo e readiness de agentes especialistas por provider/source.",
143
+ "agent doctor",
144
+ write_policy="read_only",
145
+ ),
146
+ plan_step(
147
+ "local-automation-factory",
148
+ "Preparar criacao local de scripts, skills, knowledge e agentes personalizados em .agent-devkit.",
149
+ "agent local list",
150
+ write_policy="local_config_write",
151
+ ),
152
+ plan_step(
153
+ "tasks-and-notifications",
154
+ "Configurar tarefas agendadas e notificacoes locais com eventos de conclusao/bloqueio.",
155
+ "agent notifications doctor",
156
+ write_policy="local_config_write",
157
+ ),
158
+ plan_step(
159
+ "knowledge-and-shared-memory",
160
+ "Inicializar knowledge local e memoria compartilhada com curadoria pelo dono.",
161
+ "agent knowledge doctor",
162
+ write_policy="local_config_write",
163
+ ),
164
+ ]
165
+ steps = minimal_steps if selected_mode == "minimal" else complete_steps
166
+ return {
167
+ "kind": "onboarding-plan",
168
+ "schema_version": ONBOARDING_MODE_SCHEMA_VERSION,
169
+ "status": "planned",
170
+ "mode": selected_mode,
171
+ "external_actions_executed": False,
172
+ "agent": status.get("agent"),
173
+ "home": status.get("home"),
174
+ "agent_catalog": {
175
+ "agents": len(registry.get("agents") or {}),
176
+ "capabilities": len(registry.get("capabilities") or {}),
177
+ },
178
+ "steps": steps,
179
+ "next_steps": [step["command"] for step in steps],
180
+ }
181
+
182
+
183
+ def normalize_onboarding_mode(mode: str) -> str:
184
+ if mode in {"minimal", "complete"}:
185
+ return mode
186
+ raise ValueError(f"unsupported onboarding mode: {mode}")
187
+
188
+
189
+ def onboarding_modes() -> list[dict[str, Any]]:
190
+ return [
191
+ {
192
+ "id": "minimal",
193
+ "label": "Onboarding minimo",
194
+ "command": "agent onboard minimal",
195
+ "purpose": "Deixar o agente conversavel e utilizavel com identidade, coordenador LLM, mini-brain e memoria local.",
196
+ },
197
+ {
198
+ "id": "complete",
199
+ "label": "Onboarding completo",
200
+ "command": "agent onboard complete",
201
+ "purpose": "Revisar tambem toolchain, providers, fontes, catalogo, automacoes locais, tarefas, notificacoes e memorias compartilhadas.",
202
+ },
203
+ ]
204
+
205
+
206
+ def plan_step(
207
+ step_id: str,
208
+ purpose: str,
209
+ command: str,
210
+ *,
211
+ write_policy: str,
212
+ model: str | None = None,
213
+ ) -> dict[str, Any]:
214
+ payload = {
215
+ "id": step_id,
216
+ "status": "planned",
217
+ "purpose": purpose,
218
+ "command": command,
219
+ "write_policy": write_policy,
220
+ "external_write": False,
221
+ }
222
+ if model:
223
+ payload["model"] = model
224
+ return payload
225
+
226
+
227
+ def summarize_llms() -> dict[str, Any]:
228
+ backends = list_backends()
229
+ doctor = doctor_backends()
230
+ items = doctor.get("items") or []
231
+ usable = [item for item in items if item.get("status") == "ok" and item.get("id") != "ollama"]
232
+ ollama_item = next((item for item in items if item.get("id") == "ollama"), None)
233
+ preference = backends.get("preference") or {}
234
+ return {
235
+ "status": "ok" if usable else "missing",
236
+ "default": backends.get("default"),
237
+ "primary": preference.get("primary"),
238
+ "fallback_enabled": preference.get("fallback_enabled"),
239
+ "configured_count": len([item for item in backends.get("items") or [] if item.get("configured")]),
240
+ "usable_count": len(usable),
241
+ "backends": [
242
+ {
243
+ "id": item.get("id"),
244
+ "status": item.get("status"),
245
+ "configured": item.get("configured"),
246
+ "kind": item.get("kind"),
247
+ }
248
+ for item in items
249
+ ],
250
+ "ollama_backend": {
251
+ "status": ollama_item.get("status") if isinstance(ollama_item, dict) else None,
252
+ "configured": ollama_item.get("configured") if isinstance(ollama_item, dict) else None,
253
+ },
254
+ }
255
+
256
+
257
+ def summarize_ollama(payload: dict[str, Any]) -> dict[str, Any]:
258
+ return {
259
+ "status": payload.get("status"),
260
+ "binary": payload.get("binary"),
261
+ "version": payload.get("version"),
262
+ "daemon": payload.get("daemon"),
263
+ "model_count": payload.get("model_count", 0),
264
+ "install_plan": payload.get("install_plan"),
265
+ }
266
+
267
+
268
+ def summarize_toolchain(payload: dict[str, Any]) -> dict[str, Any]:
269
+ items = payload.get("items") or []
270
+ return {
271
+ "status": payload.get("status"),
272
+ "platform": payload.get("platform"),
273
+ "required_missing": payload.get("required_missing") or [],
274
+ "optional_missing": payload.get("optional_missing") or [],
275
+ "ok_count": len([item for item in items if item.get("status") == "ok"]),
276
+ "missing_count": len([item for item in items if item.get("status") == "missing"]),
277
+ }
278
+
279
+
280
+ def summarize_sources() -> dict[str, Any]:
281
+ sources = list_sources()
282
+ try:
283
+ status = source_status()
284
+ except ValueError:
285
+ status = {"status": "missing", "items": []}
286
+ return {
287
+ "status": status.get("status"),
288
+ "count": len(sources.get("items") or []),
289
+ "defaults": sources.get("defaults") or {},
290
+ "stored_secret": bool(sources.get("stored_secret")),
291
+ }
292
+
293
+
294
+ def summarize_tasks() -> dict[str, Any]:
295
+ tasks = list_tasks()
296
+ raw = load_tasks()
297
+ raw_items = [item for item in raw.get("tasks") or [] if isinstance(item, dict)]
298
+ items = tasks.get("items") or []
299
+ enabled = [item for item in items if item.get("status") == "enabled"]
300
+ due = [public_task(item) for item in raw_items if item.get("status") == "enabled" and task_is_due(item)]
301
+ pending = [item for item in enabled if item.get("run_count", 0) == 0]
302
+ return {
303
+ "status": tasks.get("status"),
304
+ "count": len(items),
305
+ "enabled_count": len(enabled),
306
+ "pending_count": len(pending),
307
+ "due_count": len(due),
308
+ "due": due[:5],
309
+ "pending": pending[:5],
310
+ "path": tasks.get("path"),
311
+ }
312
+
313
+
314
+ def summarize_specialists(payload: dict[str, Any]) -> dict[str, Any]:
315
+ return {
316
+ "status": payload.get("status"),
317
+ "agents_total": payload.get("agents_total"),
318
+ "capabilities_total": payload.get("capabilities_total"),
319
+ "agents_with_provider_requirements": payload.get("agents_with_provider_requirements"),
320
+ "ready_agents": payload.get("ready_agents"),
321
+ "partial_agents": payload.get("partial_agents"),
322
+ "needs_setup_agents": payload.get("needs_setup_agents"),
323
+ "configured_providers": payload.get("configured_providers") or [],
324
+ "missing_providers": payload.get("missing_providers") or [],
325
+ "next_steps": payload.get("next_steps") or [],
326
+ }
327
+
328
+
329
+ def startup_flow(
330
+ *,
331
+ status: str,
332
+ sessions: dict[str, Any],
333
+ tasks: dict[str, Any],
334
+ blockers: list[dict[str, str]],
335
+ ) -> list[dict[str, str]]:
336
+ steps = [
337
+ {"id": "validate-home", "status": "done", "label": "Validar home local e memoria"},
338
+ {"id": "validate-setup", "status": "attention" if blockers else "done", "label": "Validar setup minimo"},
339
+ {
340
+ "id": "check-session",
341
+ "status": "active" if sessions.get("active") else "empty",
342
+ "label": "Verificar conversa ou analise em andamento",
343
+ },
344
+ {
345
+ "id": "check-tasks",
346
+ "status": "due" if tasks.get("due_count") else ("pending" if tasks.get("pending_count") else "empty"),
347
+ "label": "Verificar tarefas pendentes e dados a retornar",
348
+ },
349
+ {"id": "ask-next-action", "status": status, "label": "Responder ou perguntar o proximo objetivo"},
350
+ ]
351
+ return steps
352
+
353
+
354
+ def assistant_prompt(
355
+ *,
356
+ status: str,
357
+ sessions: dict[str, Any],
358
+ tasks: dict[str, Any],
359
+ blockers: list[dict[str, str]],
360
+ actions: list[dict[str, str]],
361
+ ) -> str:
362
+ if blockers:
363
+ return "Preciso concluir o setup minimo antes de executar tarefas. Posso iniciar o onboarding minimo ou completo."
364
+ if tasks.get("due_count"):
365
+ return "Encontrei tarefas agendadas prontas para revisao. Posso listar, executar em dry-run ou aguardar sua instrucao."
366
+ if tasks.get("pending_count"):
367
+ pending = tasks.get("pending") if isinstance(tasks.get("pending"), list) else []
368
+ first = pending[0] if pending and isinstance(pending[0], dict) else {}
369
+ title = first.get("title") or first.get("id") or "tarefa pendente"
370
+ active = sessions.get("active") if isinstance(sessions.get("active"), dict) else None
371
+ if active:
372
+ session_title = active.get("title") or active.get("id")
373
+ return f"Existe uma sessao ativa: {session_title}. Tambem encontrei tarefa pendente: {title}. Posso continuar a conversa, listar tarefas ou executar a pendencia em dry-run."
374
+ return f"Encontrei tarefa pendente: {title}. Posso listar, executar em dry-run ou receber uma nova tarefa."
375
+ active = sessions.get("active") if isinstance(sessions.get("active"), dict) else None
376
+ if active:
377
+ title = active.get("title") or active.get("id")
378
+ return f"Existe uma sessao ativa: {title}. Posso continuar essa conversa, mostrar status ou iniciar uma nova tarefa."
379
+ if actions:
380
+ return "Estou pronto. Posso continuar o onboarding, configurar ferramentas sob demanda ou receber uma tarefa em linguagem natural."
381
+ return "Estou pronto. O que voce quer fazer agora?"
382
+
383
+
384
+ def startup_blockers(*, setup: dict[str, Any], llms: dict[str, Any]) -> list[dict[str, str]]:
385
+ blockers: list[dict[str, str]] = []
386
+ toolchain = setup.get("toolchain") or {}
387
+ for item in toolchain.get("required_missing") or []:
388
+ blockers.append(
389
+ {
390
+ "id": f"toolchain.{item}",
391
+ "message": f"Required toolchain item is missing: {item}.",
392
+ "command": f"agent toolchain install {item} --dry-run",
393
+ }
394
+ )
395
+ if llms.get("usable_count", 0) < 1:
396
+ blockers.append(
397
+ {
398
+ "id": "llm.unavailable",
399
+ "message": "No usable coordinator LLM backend was detected.",
400
+ "command": "agent llm doctor",
401
+ }
402
+ )
403
+ return blockers
404
+
405
+
406
+ def suggested_actions(
407
+ *,
408
+ memory: dict[str, Any],
409
+ personality: dict[str, Any],
410
+ sessions: dict[str, Any],
411
+ setup: dict[str, Any],
412
+ llms: dict[str, Any],
413
+ ollama: dict[str, Any],
414
+ sources: dict[str, Any],
415
+ tasks: dict[str, Any],
416
+ specialists: dict[str, Any],
417
+ ) -> list[dict[str, str]]:
418
+ actions: list[dict[str, str]] = []
419
+ due_tasks = tasks.get("due") if isinstance(tasks.get("due"), list) else []
420
+ pending_tasks = tasks.get("pending") if isinstance(tasks.get("pending"), list) else []
421
+ first_due = due_tasks[0] if due_tasks and isinstance(due_tasks[0], dict) else None
422
+ first_pending = pending_tasks[0] if pending_tasks and isinstance(pending_tasks[0], dict) else None
423
+ if first_due and first_due.get("id"):
424
+ actions.append(
425
+ {
426
+ "id": "tasks.run-due",
427
+ "label": f"Run due task: {first_due.get('title') or first_due.get('id')}",
428
+ "command": f"agent task run {first_due['id']} --dry-run",
429
+ }
430
+ )
431
+ if first_pending and first_pending.get("id"):
432
+ actions.append(
433
+ {
434
+ "id": "tasks.run-pending",
435
+ "label": f"Run pending task: {first_pending.get('title') or first_pending.get('id')}",
436
+ "command": f"agent task run {first_pending['id']} --dry-run",
437
+ }
438
+ )
439
+ actions.append(
440
+ {
441
+ "id": "tasks.review",
442
+ "label": "Review local scheduled and manual tasks",
443
+ "command": "agent task list",
444
+ }
445
+ )
446
+ if memory.get("created"):
447
+ actions.append(
448
+ {
449
+ "id": "memory.review",
450
+ "label": "Review local memory files",
451
+ "command": "agent memory show",
452
+ }
453
+ )
454
+ if not personality.get("user_name"):
455
+ actions.append(
456
+ {
457
+ "id": "personality.setup",
458
+ "label": "Configure agent name, user name and response style",
459
+ "command": "agent setup personality",
460
+ }
461
+ )
462
+ if llms.get("usable_count", 0) < 1:
463
+ actions.append(
464
+ {
465
+ "id": "llm.configure",
466
+ "label": "Configure a coordinator LLM backend",
467
+ "command": "agent llm doctor",
468
+ }
469
+ )
470
+ if ollama.get("status") == "missing":
471
+ actions.append(
472
+ {
473
+ "id": "ollama.install",
474
+ "label": "Inspect local Ollama mini-brain setup",
475
+ "command": "agent ollama status",
476
+ }
477
+ )
478
+ if (setup.get("toolchain") or {}).get("optional_missing"):
479
+ actions.append(
480
+ {
481
+ "id": "toolchain.optional",
482
+ "label": "Review optional local tools",
483
+ "command": "agent toolchain doctor",
484
+ }
485
+ )
486
+ if sources.get("count", 0) < 1:
487
+ actions.append(
488
+ {
489
+ "id": "sources.configure",
490
+ "label": "Add reusable project/provider sources when needed",
491
+ "command": "agent source list",
492
+ }
493
+ )
494
+ if specialists.get("status") in {"needs-setup", "partial"}:
495
+ next_steps = specialists.get("next_steps") if isinstance(specialists.get("next_steps"), list) else []
496
+ actions.append(
497
+ {
498
+ "id": "specialists.readiness",
499
+ "label": "Review specialist agent provider readiness",
500
+ "command": str(next_steps[0] if next_steps else "agent doctor"),
501
+ }
502
+ )
503
+ if tasks.get("count", 0) < 1:
504
+ actions.append(
505
+ {
506
+ "id": "tasks.create",
507
+ "label": "Create scheduled or recurring tasks when useful",
508
+ "command": "agent task list",
509
+ }
510
+ )
511
+ if not sessions.get("active_session_id"):
512
+ actions.append(
513
+ {
514
+ "id": "session.start",
515
+ "label": "Start by sending a natural-language task",
516
+ "command": "agent \"o que voce consegue fazer aqui?\"",
517
+ }
518
+ )
519
+ return dedupe_actions(actions)
520
+
521
+
522
+ def dedupe_actions(actions: list[dict[str, str]]) -> list[dict[str, str]]:
523
+ seen: set[str] = set()
524
+ deduped: list[dict[str, str]] = []
525
+ for action in actions:
526
+ action_id = action.get("id")
527
+ if not action_id or action_id in seen:
528
+ continue
529
+ seen.add(action_id)
530
+ deduped.append(action)
531
+ return deduped
532
+
533
+
534
+ def summarize_sessions() -> dict[str, Any]:
535
+ sessions = list_sessions()
536
+ active_id = sessions.get("active_session_id")
537
+ active = None
538
+ if active_id:
539
+ try:
540
+ payload = show_session(str(active_id))
541
+ active = payload.get("session")
542
+ if isinstance(active, dict):
543
+ active["summary"] = payload.get("summary")
544
+ active["recent_messages"] = payload.get("recent_messages") or []
545
+ except ValueError:
546
+ active = None
547
+ return {
548
+ **sessions,
549
+ "active": active,
550
+ "recent": (sessions.get("items") or [])[:5],
551
+ }