@smilintux/skcapstone 0.10.0 → 0.12.5

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 (279) hide show
  1. package/.env.example +10 -4
  2. package/.github/workflows/ci.yml +2 -2
  3. package/.github/workflows/publish.yml +9 -2
  4. package/.openclaw-workspace.json +2 -2
  5. package/CLAUDE.md +37 -0
  6. package/MISSION.md +17 -2
  7. package/README.md +282 -3
  8. package/docker/Dockerfile +7 -7
  9. package/docker/compose-templates/dev-team.yml +12 -12
  10. package/docker/compose-templates/mini-team.yml +9 -9
  11. package/docker/compose-templates/ops-team.yml +10 -10
  12. package/docker/compose-templates/research-team.yml +10 -10
  13. package/docker/entrypoint.sh +4 -4
  14. package/docs/ADR-optional-integration-backbone.md +181 -0
  15. package/docs/ARCHITECTURE.md +186 -43
  16. package/docs/BOND_WITH_GROK.md +6 -6
  17. package/docs/CUSTOM_AGENT.md +123 -30
  18. package/docs/DREAMING.md +70 -0
  19. package/docs/GETTING_STARTED.md +7 -7
  20. package/docs/QUICKSTART.md +10 -6
  21. package/docs/SKJOULE_ARCHITECTURE.md +3 -3
  22. package/docs/SOUL_SWAPPER.md +5 -5
  23. package/docs/hammertime-audit.md +402 -0
  24. package/docs/sk-integration-HANDOFF.md +117 -0
  25. package/docs/skscheduler.md +155 -0
  26. package/docs/superpowers/examples/jobs.yaml +31 -0
  27. package/docs/superpowers/plans/2026-06-08-skscheduler.md +1265 -0
  28. package/docs/superpowers/specs/2026-06-08-skscheduler-design.md +186 -0
  29. package/examples/custom-bond-template.json +1 -1
  30. package/examples/grok-feb.json +1 -1
  31. package/examples/queen-ava-feb.json +1 -1
  32. package/launchd/{com.skcapstone.skcomm-heartbeat.plist → com.skcapstone.skcomms-heartbeat.plist} +4 -4
  33. package/launchd/{com.skcapstone.skcomm-queue-drain.plist → com.skcapstone.skcomms-queue-drain.plist} +4 -4
  34. package/launchd/install-launchd.sh +6 -6
  35. package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/src/index.ts +3 -2
  36. package/package.json +1 -1
  37. package/pyproject.toml +16 -10
  38. package/scripts/archive-sessions.sh +7 -0
  39. package/scripts/check-updates.py +4 -4
  40. package/scripts/install-bundle.sh +8 -8
  41. package/scripts/install.ps1 +12 -11
  42. package/scripts/install.sh +159 -5
  43. package/scripts/model-fallback-monitor.sh +102 -0
  44. package/scripts/nvidia-proxy.mjs +78 -26
  45. package/scripts/refresh-anthropic-token.sh +172 -0
  46. package/scripts/release.sh +98 -0
  47. package/scripts/session-to-memory.py +219 -0
  48. package/scripts/skgateway.mjs +3 -3
  49. package/scripts/telegram-catchup-all.sh +12 -1
  50. package/scripts/verify_install.sh +2 -2
  51. package/scripts/wargov-ufo-capture/README.md +43 -0
  52. package/scripts/wargov-ufo-capture/cdp_capture_release2.py +273 -0
  53. package/scripts/wargov-ufo-capture/cdp_capture_splc_doj.py +246 -0
  54. package/scripts/wargov-ufo-capture/cdp_finish.py +271 -0
  55. package/scripts/wargov-ufo-capture/cdp_probe.py +188 -0
  56. package/scripts/wargov-ufo-capture/cdp_splc_pressrelease.py +101 -0
  57. package/scripts/wargov-ufo-capture/parse_csv.py +95 -0
  58. package/scripts/wargov-ufo-capture/pull_dvids.sh +107 -0
  59. package/scripts/watch-anthropic-token.sh +212 -0
  60. package/scripts/windows/install-tasks.ps1 +7 -7
  61. package/scripts/windows/skcapstone-task.xml +1 -1
  62. package/src/skcapstone/__init__.py +45 -3
  63. package/src/skcapstone/_cli_monolith.py +20 -15
  64. package/src/skcapstone/activity.py +5 -1
  65. package/src/skcapstone/agent_card.py +3 -2
  66. package/src/skcapstone/api.py +41 -40
  67. package/src/skcapstone/auction.py +14 -11
  68. package/src/skcapstone/backup.py +2 -1
  69. package/src/skcapstone/blueprint_registry.py +4 -3
  70. package/src/skcapstone/brain_first.py +238 -0
  71. package/src/skcapstone/changelog.py +1 -1
  72. package/src/skcapstone/chat.py +22 -17
  73. package/src/skcapstone/cli/__init__.py +9 -1
  74. package/src/skcapstone/cli/_common.py +1 -0
  75. package/src/skcapstone/cli/agents_spawner.py +5 -2
  76. package/src/skcapstone/cli/alerts.py +25 -4
  77. package/src/skcapstone/cli/bench.py +15 -15
  78. package/src/skcapstone/cli/chat.py +7 -4
  79. package/src/skcapstone/cli/consciousness.py +5 -2
  80. package/src/skcapstone/cli/context_cmd.py +18 -4
  81. package/src/skcapstone/cli/daemon.py +11 -7
  82. package/src/skcapstone/cli/gtd.py +26 -1
  83. package/src/skcapstone/cli/housekeeping.py +3 -3
  84. package/src/skcapstone/cli/identity_cmd.py +378 -0
  85. package/src/skcapstone/cli/joule_cmd.py +7 -3
  86. package/src/skcapstone/cli/memory.py +8 -6
  87. package/src/skcapstone/cli/peers_dir.py +1 -1
  88. package/src/skcapstone/cli/register_cmd.py +29 -3
  89. package/src/skcapstone/cli/scheduler_cmd.py +167 -0
  90. package/src/skcapstone/cli/session.py +25 -0
  91. package/src/skcapstone/cli/setup.py +96 -29
  92. package/src/skcapstone/cli/shell_cmd.py +53 -1
  93. package/src/skcapstone/cli/skills_cmd.py +2 -2
  94. package/src/skcapstone/cli/soul.py +8 -5
  95. package/src/skcapstone/cli/status.py +37 -11
  96. package/src/skcapstone/cli/telegram.py +21 -0
  97. package/src/skcapstone/cli/test_cmd.py +5 -5
  98. package/src/skcapstone/cli/test_connection.py +2 -2
  99. package/src/skcapstone/cli/upgrade_cmd.py +23 -14
  100. package/src/skcapstone/cli/version_cmd.py +1 -1
  101. package/src/skcapstone/cli/watch_cmd.py +9 -6
  102. package/src/skcapstone/cloud9_bridge.py +14 -14
  103. package/src/skcapstone/codex_setup.py +255 -0
  104. package/src/skcapstone/config_validator.py +7 -4
  105. package/src/skcapstone/consciousness_config.py +5 -1
  106. package/src/skcapstone/consciousness_loop.py +313 -273
  107. package/src/skcapstone/context_loader.py +121 -0
  108. package/src/skcapstone/coord_federation.py +2 -1
  109. package/src/skcapstone/coordination.py +23 -6
  110. package/src/skcapstone/crush_integration.py +2 -1
  111. package/src/skcapstone/daemon.py +132 -77
  112. package/src/skcapstone/dashboard.py +10 -10
  113. package/src/skcapstone/data/sk-agent-picker.sh +421 -0
  114. package/src/skcapstone/data/systemd/skcapstone-api.socket +9 -0
  115. package/src/skcapstone/data/systemd/skcapstone-memory-compress.service +18 -0
  116. package/src/skcapstone/data/systemd/skcapstone-memory-compress.timer +11 -0
  117. package/src/skcapstone/data/systemd/skcapstone.service +37 -0
  118. package/src/skcapstone/data/systemd/skcapstone@.service +50 -0
  119. package/src/skcapstone/data/systemd/skcomms-heartbeat.service +18 -0
  120. package/{systemd/skcomm-heartbeat.timer → src/skcapstone/data/systemd/skcomms-heartbeat.timer} +2 -2
  121. package/src/skcapstone/data/systemd/skcomms-queue-drain.service +17 -0
  122. package/{systemd/skcomm-queue-drain.timer → src/skcapstone/data/systemd/skcomms-queue-drain.timer} +2 -2
  123. package/src/skcapstone/defaults/claude/CLAUDE.md +67 -0
  124. package/src/skcapstone/defaults/claude/settings.json +74 -0
  125. package/src/skcapstone/defaults/lumina/config/claude-hooks.md +57 -0
  126. package/src/skcapstone/defaults/lumina/config/skgraph.yaml +55 -10
  127. package/src/skcapstone/defaults/lumina/config/skmemory.yaml +79 -13
  128. package/src/skcapstone/defaults/lumina/config/skvector.yaml +60 -9
  129. package/src/skcapstone/defaults/lumina/memory/long-term/18b9c0d1e2f3-cloud9-protocol.json +2 -2
  130. package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +2 -2
  131. package/src/skcapstone/defaults/lumina/memory/long-term/b2c3d4e5f6a7-five-pillars.json +9 -9
  132. package/src/skcapstone/defaults/lumina/memory/long-term/d4e5f6a7b8c9-site-directory.json +2 -2
  133. package/src/skcapstone/defaults/unhinged.json +13 -0
  134. package/src/skcapstone/discovery.py +43 -20
  135. package/src/skcapstone/doctor.py +941 -22
  136. package/src/skcapstone/dreaming.py +1183 -109
  137. package/src/skcapstone/emotion_tracker.py +2 -2
  138. package/src/skcapstone/export.py +4 -3
  139. package/src/skcapstone/fuse_mount.py +14 -12
  140. package/src/skcapstone/gui_installer.py +2 -2
  141. package/src/skcapstone/heartbeat.py +1 -1
  142. package/src/skcapstone/housekeeping.py +14 -14
  143. package/src/skcapstone/install_wizard.py +209 -7
  144. package/src/skcapstone/itil.py +13 -4
  145. package/src/skcapstone/kms_scheduler.py +10 -8
  146. package/src/skcapstone/launchd.py +19 -19
  147. package/src/skcapstone/mcp_launcher.py +15 -1
  148. package/src/skcapstone/mcp_server.py +83 -49
  149. package/src/skcapstone/mcp_tools/__init__.py +2 -0
  150. package/src/skcapstone/mcp_tools/_helpers.py +2 -2
  151. package/src/skcapstone/mcp_tools/ansible_tools.py +7 -4
  152. package/src/skcapstone/mcp_tools/brain_first_tools.py +90 -0
  153. package/src/skcapstone/mcp_tools/capauth_tools.py +7 -4
  154. package/src/skcapstone/mcp_tools/comm_tools.py +10 -10
  155. package/src/skcapstone/mcp_tools/coord_tools.py +8 -4
  156. package/src/skcapstone/mcp_tools/did_tools.py +11 -8
  157. package/src/skcapstone/mcp_tools/gtd_tools.py +4 -4
  158. package/src/skcapstone/mcp_tools/memory_tools.py +6 -2
  159. package/src/skcapstone/mcp_tools/notification_tools.py +22 -6
  160. package/src/skcapstone/mcp_tools/{skcomm_tools.py → skcomms_tools.py} +14 -14
  161. package/src/skcapstone/mcp_tools/soul_tools.py +8 -2
  162. package/src/skcapstone/mdns_discovery.py +2 -2
  163. package/src/skcapstone/memory_curator.py +1 -1
  164. package/src/skcapstone/memory_engine.py +10 -3
  165. package/src/skcapstone/metrics.py +30 -16
  166. package/src/skcapstone/migrate_memories.py +4 -3
  167. package/src/skcapstone/migrate_multi_agent.py +8 -7
  168. package/src/skcapstone/models.py +47 -5
  169. package/src/skcapstone/notifications.py +42 -18
  170. package/src/skcapstone/onboard.py +875 -121
  171. package/src/skcapstone/operator_link.py +170 -0
  172. package/src/skcapstone/peer_directory.py +4 -4
  173. package/src/skcapstone/peers.py +19 -19
  174. package/src/skcapstone/pillars/__init__.py +7 -5
  175. package/src/skcapstone/pillars/consciousness.py +191 -0
  176. package/src/skcapstone/pillars/identity.py +51 -7
  177. package/src/skcapstone/pillars/memory.py +9 -3
  178. package/src/skcapstone/pillars/sync.py +2 -2
  179. package/src/skcapstone/preflight.py +3 -3
  180. package/src/skcapstone/providers/docker.py +28 -28
  181. package/src/skcapstone/register.py +6 -6
  182. package/src/skcapstone/registry_client.py +5 -4
  183. package/src/skcapstone/runtime.py +14 -3
  184. package/src/skcapstone/scheduled_tasks.py +254 -19
  185. package/src/skcapstone/scheduler_jobs.py +456 -0
  186. package/src/skcapstone/scheduler_runner.py +239 -0
  187. package/src/skcapstone/scheduler_state.py +162 -0
  188. package/src/skcapstone/sdk.py +310 -0
  189. package/src/skcapstone/service_health.py +279 -39
  190. package/src/skcapstone/session_briefing.py +108 -0
  191. package/src/skcapstone/session_capture.py +1 -1
  192. package/src/skcapstone/shell.py +7 -1
  193. package/src/skcapstone/soul.py +3 -1
  194. package/src/skcapstone/soul_switch.py +3 -1
  195. package/src/skcapstone/summary.py +6 -6
  196. package/src/skcapstone/sync_engine.py +15 -15
  197. package/src/skcapstone/sync_watcher.py +2 -2
  198. package/src/skcapstone/systemd.py +55 -21
  199. package/src/skcapstone/team_comms.py +8 -8
  200. package/src/skcapstone/team_engine.py +1 -1
  201. package/src/skcapstone/testrunner.py +3 -3
  202. package/src/skcapstone/trust_graph.py +40 -5
  203. package/src/skcapstone/unified_search.py +15 -6
  204. package/src/skcapstone/uninstall_wizard.py +11 -3
  205. package/src/skcapstone/version_check.py +8 -4
  206. package/src/skcapstone/warmth_anchor.py +4 -2
  207. package/src/skcapstone/whoami.py +4 -4
  208. package/systemd/skcapstone.service +4 -6
  209. package/systemd/skcapstone@.service +7 -8
  210. package/systemd/skcomms-heartbeat.service +21 -0
  211. package/systemd/skcomms-heartbeat.timer +12 -0
  212. package/systemd/skcomms-queue-drain.service +17 -0
  213. package/systemd/skcomms-queue-drain.timer +12 -0
  214. package/tests/conftest.py +39 -0
  215. package/tests/integration/test_consciousness_e2e.py +39 -39
  216. package/tests/test_agent_card.py +1 -1
  217. package/tests/test_agent_home_scaffold.py +34 -0
  218. package/tests/test_alerts_consumer_topics.py +27 -0
  219. package/tests/test_backup.py +2 -1
  220. package/tests/test_chat.py +6 -6
  221. package/tests/test_claude_md.py +2 -2
  222. package/tests/test_cli_skills.py +10 -10
  223. package/tests/test_cli_test_cmd.py +4 -4
  224. package/tests/test_cli_test_connection.py +1 -1
  225. package/tests/test_cloud9_bridge.py +6 -6
  226. package/tests/test_consciousness_e2e.py +1 -1
  227. package/tests/test_consciousness_loop.py +10 -10
  228. package/tests/test_coordination.py +25 -0
  229. package/tests/test_cross_package.py +21 -21
  230. package/tests/test_daemon.py +4 -4
  231. package/tests/test_daemon_shutdown.py +1 -1
  232. package/tests/test_docker_provider.py +29 -29
  233. package/tests/test_doctor.py +400 -0
  234. package/tests/test_doctor_skscheduler.py +50 -0
  235. package/tests/test_dreaming_engine.py +147 -0
  236. package/tests/test_dreaming_gtd_capture.py +35 -0
  237. package/tests/test_e2e_automated.py +8 -5
  238. package/tests/test_fuse_mount.py +10 -10
  239. package/tests/test_gtd_brief.py +46 -0
  240. package/tests/test_gtd_malformed_tolerance.py +31 -0
  241. package/tests/test_housekeeping.py +15 -15
  242. package/tests/test_identity_migrate.py +251 -0
  243. package/tests/test_integration_backbone.py +598 -0
  244. package/tests/test_itil_gtd_lifecycle.py +37 -0
  245. package/tests/test_jobs_dropins.py +84 -0
  246. package/tests/test_mcp_server.py +82 -37
  247. package/tests/test_models.py +48 -4
  248. package/tests/test_multi_agent.py +31 -29
  249. package/tests/test_notifications.py +122 -32
  250. package/tests/test_onboard.py +63 -75
  251. package/tests/test_operator_link.py +78 -0
  252. package/tests/test_peers.py +14 -14
  253. package/tests/test_pillars.py +98 -0
  254. package/tests/test_preflight.py +3 -3
  255. package/tests/test_runtime.py +21 -0
  256. package/tests/test_scheduled_tasks.py +11 -6
  257. package/tests/test_scheduler_cli.py +47 -0
  258. package/tests/test_scheduler_features.py +133 -0
  259. package/tests/test_scheduler_integration.py +87 -0
  260. package/tests/test_scheduler_jobs.py +155 -0
  261. package/tests/test_scheduler_runner.py +64 -0
  262. package/tests/test_scheduler_state.py +57 -0
  263. package/tests/test_sdk.py +70 -0
  264. package/tests/test_service_health_incidents.py +34 -0
  265. package/tests/test_service_registry.py +52 -0
  266. package/tests/test_session_briefing.py +130 -0
  267. package/tests/test_snapshots.py +4 -4
  268. package/tests/test_sync_pipeline.py +26 -26
  269. package/tests/test_team_comms.py +2 -2
  270. package/tests/test_testrunner.py +2 -2
  271. package/tests/test_trust_graph.py +18 -0
  272. package/tests/test_unified_search.py +2 -2
  273. package/tests/test_version_check.py +10 -0
  274. package/tests/test_version_cmd.py +8 -8
  275. package/tests/test_whoami.py +1 -1
  276. package/systemd/skcomm-heartbeat.service +0 -18
  277. package/systemd/skcomm-queue-drain.service +0 -17
  278. /package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/package.json +0 -0
  279. /package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/src/openclaw.plugin.json +0 -0
@@ -54,20 +54,20 @@ _SERVICE_DEFS: list[dict] = [
54
54
  "logs": "{logs}/memory-compress",
55
55
  },
56
56
  {
57
- "suffix": "skcomm-heartbeat",
58
- "args": ["{skenv}/skcomm", "heartbeat"],
57
+ "suffix": "skcomms-heartbeat",
58
+ "args": ["{skenv}/skcomms", "heartbeat"],
59
59
  "env": {},
60
60
  "interval": 60,
61
61
  "nice": 19,
62
- "logs": "{logs}/skcomm-heartbeat",
62
+ "logs": "{logs}/skcomms-heartbeat",
63
63
  },
64
64
  {
65
- "suffix": "skcomm-queue-drain",
66
- "args": ["{skenv}/skcomm", "queue", "drain"],
65
+ "suffix": "skcomms-queue-drain",
66
+ "args": ["{skenv}/skcomms", "queue", "drain"],
67
67
  "env": {},
68
68
  "interval": 120,
69
69
  "nice": 19,
70
- "logs": "{logs}/skcomm-queue-drain",
70
+ "logs": "{logs}/skcomms-queue-drain",
71
71
  },
72
72
  ]
73
73
 
@@ -84,23 +84,23 @@ _OPTIONAL_DEFS: list[dict] = [
84
84
  "requires_bin": "skchat",
85
85
  },
86
86
  {
87
- "suffix": "skcomm-api",
88
- "args": ["{skenv}/python3", "-m", "uvicorn", "skcomm.api:app",
87
+ "suffix": "skcomms-api",
88
+ "args": ["{skenv}/python3", "-m", "uvicorn", "skcomms.api:app",
89
89
  "--host", "127.0.0.1", "--port", "9384", "--log-level", "info"],
90
90
  "env": {"SKCHAT_IDENTITY": "capauth:{agent}@skworld.io"},
91
91
  "keep_alive": True,
92
92
  "throttle": 5,
93
- "logs": "{skcomm}/launchd",
94
- "requires_bin": "skcomm",
93
+ "logs": "{skcomms}/launchd",
94
+ "requires_bin": "skcomms",
95
95
  },
96
96
  {
97
- "suffix": "skcomm-daemon",
98
- "args": ["{skenv}/skcomm", "daemon", "--all-agents", "--interval", "5"],
97
+ "suffix": "skcomms-daemon",
98
+ "args": ["{skenv}/skcomms", "daemon", "--all-agents", "--interval", "5"],
99
99
  "env": {},
100
100
  "keep_alive": True,
101
101
  "throttle": 5,
102
- "logs": "{skcomm}/daemon",
103
- "requires_bin": "skcomm",
102
+ "logs": "{skcomms}/daemon",
103
+ "requires_bin": "skcomms",
104
104
  },
105
105
  ]
106
106
 
@@ -127,7 +127,7 @@ def _expand(s: str, agent: str) -> str:
127
127
  .replace("{home}", home)
128
128
  .replace("{logs}", f"{home}/.skcapstone/logs")
129
129
  .replace("{skchat}", f"{home}/.skchat")
130
- .replace("{skcomm}", f"{home}/.skcomm")
130
+ .replace("{skcomms}", f"{home}/.skcomms")
131
131
  )
132
132
 
133
133
 
@@ -281,7 +281,7 @@ def install_service(
281
281
  log_dirs = [
282
282
  Path.home() / ".skcapstone" / "logs",
283
283
  Path.home() / ".skchat",
284
- Path.home() / ".skcomm",
284
+ Path.home() / ".skcomms",
285
285
  ]
286
286
  for d in log_dirs:
287
287
  d.mkdir(parents=True, exist_ok=True)
@@ -355,7 +355,7 @@ def service_status(suffix: str = "daemon") -> ServiceStatus:
355
355
  """Query the status of a specific SK launchd service.
356
356
 
357
357
  Args:
358
- suffix: Service suffix (e.g., 'daemon', 'skcomm-heartbeat').
358
+ suffix: Service suffix (e.g., 'daemon', 'skcomms-heartbeat').
359
359
 
360
360
  Returns:
361
361
  ServiceStatus with current state.
@@ -420,7 +420,7 @@ def service_logs(suffix: str = "daemon", lines: int = 50) -> str:
420
420
  )
421
421
  if r.stdout.strip():
422
422
  output_parts.append(f"--- {log_path.name} ---\n{r.stdout}")
423
- except Exception:
424
- pass
423
+ except Exception as exc:
424
+ logger.warning("Failed to read launchd log %s: %s", log_path, exc)
425
425
 
426
426
  return "\n".join(output_parts) if output_parts else f"No logs found in {logs_dir}"
@@ -138,9 +138,23 @@ def _setup_environment() -> None:
138
138
  """Set required environment variables if not already present."""
139
139
  home = Path.home()
140
140
  skcapstone_home = home / ".skcapstone"
141
+ agent_name = os.environ.get("SKCAPSTONE_AGENT")
142
+ if not agent_name:
143
+ try:
144
+ from . import active_agent_name
145
+
146
+ agent_name = active_agent_name()
147
+ except Exception as e:
148
+ logger.warning("mcp_launcher.py: %s", e)
149
+ agent_name = None
141
150
 
142
151
  os.environ.setdefault("SKCAPSTONE_HOME", str(skcapstone_home))
143
- os.environ.setdefault("SKMEMORY_HOME", str(skcapstone_home / "memory"))
152
+ if agent_name:
153
+ os.environ.setdefault("SKCAPSTONE_AGENT", agent_name)
154
+ os.environ.setdefault(
155
+ "SKMEMORY_HOME",
156
+ str(skcapstone_home / "agents" / agent_name / "memory"),
157
+ )
144
158
 
145
159
  # Ensure src/ is on PYTHONPATH for importability
146
160
  src_dir = str(Path(__file__).resolve().parent.parent)
@@ -9,8 +9,8 @@ Tools:
9
9
  memory_store — Save content to SKMemory
10
10
  memory_search — Search memories by query
11
11
  memory_recall — Recall a specific memory by ID
12
- send_message — Send a message via SKComm
13
- check_inbox — Check for new SKComm messages
12
+ send_message — Send a message via SKComms
13
+ check_inbox — Check for new SKComms messages
14
14
  sync_push — Push agent state to sync mesh
15
15
  sync_pull — Pull seeds from peers
16
16
  coord_status — Show coordination board
@@ -108,33 +108,18 @@ from mcp.server import Server
108
108
  from mcp.server.stdio import stdio_server
109
109
  from mcp.types import TextContent, Tool
110
110
 
111
- from . import AGENT_HOME
111
+ from .mcp_tools._helpers import (
112
+ _error_response,
113
+ _home,
114
+ _json_response,
115
+ _text_response,
116
+ )
112
117
 
113
118
  logger = logging.getLogger("skcapstone.mcp")
114
119
 
115
120
  server = Server("skcapstone")
116
121
 
117
122
 
118
- def _home() -> Path:
119
- """Resolve the agent home directory."""
120
- return Path(AGENT_HOME).expanduser()
121
-
122
-
123
- def _json_response(data: Any) -> list[TextContent]:
124
- """Wrap data as a JSON text content response."""
125
- return [TextContent(type="text", text=json.dumps(data, indent=2, default=str))]
126
-
127
-
128
- def _text_response(text: str) -> list[TextContent]:
129
- """Wrap a plain string as a text content response."""
130
- return [TextContent(type="text", text=text)]
131
-
132
-
133
- def _error_response(message: str) -> list[TextContent]:
134
- """Return an error message as text content."""
135
- return [TextContent(type="text", text=json.dumps({"error": message}))]
136
-
137
-
138
123
  def _get_agent_name(home: Path) -> str:
139
124
  """Read the agent name from identity file."""
140
125
  identity_path = home / "identity" / "identity.json"
@@ -142,8 +127,8 @@ def _get_agent_name(home: Path) -> str:
142
127
  try:
143
128
  data = json.loads(identity_path.read_text(encoding="utf-8"))
144
129
  return data.get("name", "anonymous")
145
- except Exception:
146
- pass
130
+ except Exception as exc:
131
+ logger.warning("Failed to read agent name from identity.json: %s", exc)
147
132
  return "anonymous"
148
133
 
149
134
 
@@ -242,7 +227,7 @@ async def list_tools() -> list[Tool]:
242
227
  Tool(
243
228
  name="send_message",
244
229
  description=(
245
- "Send a message to another agent via SKComm. "
230
+ "Send a message to another agent via SKComms. "
246
231
  "Routes through available transports (Syncthing, file)."
247
232
  ),
248
233
  inputSchema={
@@ -268,7 +253,7 @@ async def list_tools() -> list[Tool]:
268
253
  Tool(
269
254
  name="check_inbox",
270
255
  description=(
271
- "Check for new incoming messages across all SKComm transports. "
256
+ "Check for new incoming messages across all SKComms transports. "
272
257
  "Returns any unread message envelopes."
273
258
  ),
274
259
  inputSchema={"type": "object", "properties": {}, "required": []},
@@ -1643,7 +1628,7 @@ async def list_tools() -> list[Tool]:
1643
1628
  description=(
1644
1629
  "Check ecosystem package versions against PyPI. "
1645
1630
  "Shows installed vs latest for skmemory, skcapstone, capauth, "
1646
- "sksecurity, skcomm, skchat, cloud9-protocol."
1631
+ "sksecurity, skcomms, skchat, cloud9."
1647
1632
  ),
1648
1633
  inputSchema={
1649
1634
  "type": "object",
@@ -2368,11 +2353,11 @@ async def list_tools() -> list[Tool]:
2368
2353
  "required": [],
2369
2354
  },
2370
2355
  ),
2371
- # ── SKComm tools ──────────────────────────────────────
2356
+ # ── SKComms tools ──────────────────────────────────────
2372
2357
  Tool(
2373
2358
  name="comm_notify",
2374
2359
  description=(
2375
- "Send a notification message via SKComm. Routes through "
2360
+ "Send a notification message via SKComms. Routes through "
2376
2361
  "available transports (Syncthing, file, Tailscale). "
2377
2362
  "Supports urgency levels for priority routing."
2378
2363
  ),
@@ -2403,7 +2388,7 @@ async def list_tools() -> list[Tool]:
2403
2388
  Tool(
2404
2389
  name="comm_status",
2405
2390
  description=(
2406
- "Show SKComm subsystem status: installed version, "
2391
+ "Show SKComms subsystem status: installed version, "
2407
2392
  "available transports, connection state, and recent "
2408
2393
  "delivery statistics."
2409
2394
  ),
@@ -2654,6 +2639,39 @@ async def list_tools() -> list[Tool]:
2654
2639
  "required": ["query"],
2655
2640
  },
2656
2641
  ),
2642
+ # Brain-First Protocol
2643
+ Tool(
2644
+ name="brain_first_check",
2645
+ description=(
2646
+ "Brain-First Protocol: consult the agent's memory before "
2647
+ "acting on a task. Extracts keywords from the given context, "
2648
+ "searches memory for relevant prior knowledge, and returns "
2649
+ "any matching memories. Use this before starting new work to "
2650
+ "avoid duplicating effort or missing prior decisions."
2651
+ ),
2652
+ inputSchema={
2653
+ "type": "object",
2654
+ "properties": {
2655
+ "context": {
2656
+ "type": "string",
2657
+ "description": (
2658
+ "The task description, prompt, or action context "
2659
+ "to search memory for"
2660
+ ),
2661
+ },
2662
+ "tags": {
2663
+ "type": "array",
2664
+ "items": {"type": "string"},
2665
+ "description": "Optional tag filter for the memory search",
2666
+ },
2667
+ "max_results": {
2668
+ "type": "integer",
2669
+ "description": "Max memories to return (default: from config, usually 5)",
2670
+ },
2671
+ },
2672
+ "required": ["context"],
2673
+ },
2674
+ ),
2657
2675
  ]
2658
2676
 
2659
2677
 
@@ -2793,7 +2811,7 @@ async def call_tool(name: str, arguments: dict) -> list[TextContent]:
2793
2811
  # SKChat
2794
2812
  "chat_send": _handle_chat_send,
2795
2813
  "chat_history": _handle_chat_history,
2796
- # SKComm
2814
+ # SKComms
2797
2815
  "comm_notify": _handle_comm_notify,
2798
2816
  "comm_status": _handle_comm_status,
2799
2817
  # CapAuth
@@ -2813,6 +2831,8 @@ async def call_tool(name: str, arguments: dict) -> list[TextContent]:
2813
2831
  "itil_cab_vote": _handle_itil_cab_vote,
2814
2832
  "itil_status": _handle_itil_status,
2815
2833
  "itil_kedb_search": _handle_itil_kedb_search,
2834
+ # Brain-First Protocol
2835
+ "brain_first_check": _handle_brain_first_check,
2816
2836
  }
2817
2837
  handler = handlers.get(name)
2818
2838
  if handler is None:
@@ -2842,7 +2862,8 @@ def _get_memory_backend_health() -> dict:
2842
2862
  if "graph" in health:
2843
2863
  backends["skgraph"] = "ok" if health["graph"].get("ok") else "error"
2844
2864
  return backends or {"json": "ok"}
2845
- except Exception:
2865
+ except Exception as e:
2866
+ logger.warning("Failed to get memory backend health: %s", e)
2846
2867
  return {"json": "ok"}
2847
2868
 
2848
2869
 
@@ -2973,15 +2994,15 @@ async def _handle_memory_recall(args: dict) -> list[TextContent]:
2973
2994
 
2974
2995
 
2975
2996
  async def _handle_send_message(args: dict) -> list[TextContent]:
2976
- """Send a message via SKComm."""
2997
+ """Send a message via SKComms."""
2977
2998
  recipient = args.get("recipient", "")
2978
2999
  message = args.get("message", "")
2979
3000
  if not recipient or not message:
2980
3001
  return _error_response("recipient and message are required")
2981
3002
 
2982
3003
  try:
2983
- from skcomm.core import SKComm
2984
- comm = SKComm.from_config()
3004
+ from skcomms.core import SKComms
3005
+ comm = SKComms.from_config()
2985
3006
  report = comm.send(recipient, message)
2986
3007
  return _json_response({
2987
3008
  "sent": report.success,
@@ -2996,7 +3017,7 @@ async def _handle_send_message(args: dict) -> list[TextContent]:
2996
3017
  ],
2997
3018
  })
2998
3019
  except ImportError:
2999
- return _error_response("SKComm not installed. Run: pip install skcomm")
3020
+ return _error_response("SKComms not installed. Run: pip install skcomms")
3000
3021
  except Exception as exc:
3001
3022
  return _error_response(f"Send failed: {exc}")
3002
3023
 
@@ -3004,8 +3025,8 @@ async def _handle_send_message(args: dict) -> list[TextContent]:
3004
3025
  async def _handle_check_inbox(_args: dict) -> list[TextContent]:
3005
3026
  """Check for incoming messages."""
3006
3027
  try:
3007
- from skcomm.core import SKComm
3008
- comm = SKComm.from_config()
3028
+ from skcomms.core import SKComms
3029
+ comm = SKComms.from_config()
3009
3030
  envelopes = comm.receive()
3010
3031
  return _json_response([
3011
3032
  {
@@ -3021,7 +3042,7 @@ async def _handle_check_inbox(_args: dict) -> list[TextContent]:
3021
3042
  for e in envelopes
3022
3043
  ])
3023
3044
  except ImportError:
3024
- return _error_response("SKComm not installed. Run: pip install skcomm")
3045
+ return _error_response("SKComms not installed. Run: pip install skcomms")
3025
3046
  except Exception as exc:
3026
3047
  return _error_response(f"Inbox check failed: {exc}")
3027
3048
 
@@ -3154,8 +3175,8 @@ async def _handle_coord_complete(args: dict) -> list[TextContent]:
3154
3175
  t.priority.value, ("community", "support_ticket", 50)
3155
3176
  )
3156
3177
  break
3157
- except Exception:
3158
- pass
3178
+ except Exception as exc:
3179
+ logger.warning("Failed to calculate joules for completed task %s: %s", task_id, exc)
3159
3180
 
3160
3181
  return _json_response({
3161
3182
  "completed": True,
@@ -3210,6 +3231,8 @@ async def _handle_ritual(_args: dict) -> list[TextContent]:
3210
3231
  "journal_entries": result.journal_entries,
3211
3232
  "germination_prompts": result.germination_prompts,
3212
3233
  "strongest_memories": result.strongest_memories,
3234
+ "song_anchors_loaded": result.song_anchors_loaded,
3235
+ "song_anchor_ids": result.song_anchor_ids,
3213
3236
  "context_prompt": result.context_prompt,
3214
3237
  })
3215
3238
  except ImportError:
@@ -3589,7 +3612,8 @@ def _get_skchat_identity() -> str:
3589
3612
  home = _home()
3590
3613
  runtime = get_runtime(home)
3591
3614
  return f"capauth:{runtime.manifest.name}@local"
3592
- except Exception:
3615
+ except Exception as e:
3616
+ logger.warning("Failed to resolve sovereign identity: %s", e)
3593
3617
  return "capauth:agent@local"
3594
3618
 
3595
3619
 
@@ -3606,7 +3630,8 @@ def _resolve_recipient(name: str) -> str:
3606
3630
  try:
3607
3631
  from skchat.identity_bridge import resolve_peer_name
3608
3632
  return resolve_peer_name(name)
3609
- except Exception:
3633
+ except Exception as e:
3634
+ logger.warning("Failed to resolve peer name %r: %s", name, e)
3610
3635
  return f"capauth:{name}@local"
3611
3636
 
3612
3637
 
@@ -4793,18 +4818,18 @@ async def _handle_chat_history(args: dict) -> list[TextContent]:
4793
4818
  return await _impl(args)
4794
4819
 
4795
4820
 
4796
- # ── SKComm tools ──────────────────────────────────────────
4821
+ # ── SKComms tools ──────────────────────────────────────────
4797
4822
 
4798
4823
 
4799
4824
  async def _handle_comm_notify(args: dict) -> list[TextContent]:
4800
- """Send a notification via SKComm."""
4801
- from .mcp_tools.skcomm_tools import _handle_comm_notify as _impl
4825
+ """Send a notification via SKComms."""
4826
+ from .mcp_tools.skcomms_tools import _handle_comm_notify as _impl
4802
4827
  return await _impl(args)
4803
4828
 
4804
4829
 
4805
4830
  async def _handle_comm_status(args: dict) -> list[TextContent]:
4806
- """Show SKComm subsystem status."""
4807
- from .mcp_tools.skcomm_tools import _handle_comm_status as _impl
4831
+ """Show SKComms subsystem status."""
4832
+ from .mcp_tools.skcomms_tools import _handle_comm_status as _impl
4808
4833
  return await _impl(args)
4809
4834
 
4810
4835
 
@@ -4937,6 +4962,15 @@ async def _handle_itil_kedb_search(args: dict) -> list[TextContent]:
4937
4962
  return await _impl(args)
4938
4963
 
4939
4964
 
4965
+ # ── Brain-First Protocol ──────────────────────────────────
4966
+
4967
+
4968
+ async def _handle_brain_first_check(args: dict) -> list[TextContent]:
4969
+ """Consult memory before acting on a task."""
4970
+ from .mcp_tools.brain_first_tools import _handle_brain_first_check as _impl
4971
+ return await _impl(args)
4972
+
4973
+
4940
4974
  # ═══════════════════════════════════════════════════════════
4941
4975
  # Entry Point
4942
4976
  # ═══════════════════════════════════════════════════════════
@@ -17,6 +17,7 @@ from mcp.types import TextContent, Tool
17
17
  from . import (
18
18
  agent_tools,
19
19
  ansible_tools,
20
+ brain_first_tools,
20
21
  chat_tools,
21
22
  comm_tools,
22
23
  consciousness_tools,
@@ -49,6 +50,7 @@ from . import (
49
50
  # Ordered list of all tool-group modules.
50
51
  _MODULES = [
51
52
  agent_tools,
53
+ brain_first_tools,
52
54
  memory_tools,
53
55
  comm_tools,
54
56
  sync_tools,
@@ -46,6 +46,6 @@ def _get_agent_name(home: Path) -> str:
46
46
  try:
47
47
  data = json.loads(identity_path.read_text(encoding="utf-8"))
48
48
  return data.get("name", "anonymous")
49
- except Exception:
50
- pass
49
+ except Exception as exc:
50
+ logger.warning("Failed to read agent name from identity.json: %s", exc)
51
51
  return "anonymous"
@@ -10,6 +10,7 @@ from __future__ import annotations
10
10
 
11
11
  import asyncio
12
12
  import json
13
+ import logging
13
14
  import shutil
14
15
  import uuid
15
16
  from pathlib import Path
@@ -18,6 +19,8 @@ from mcp.types import TextContent, Tool
18
19
 
19
20
  from ._helpers import _error_response, _home, _json_response
20
21
 
22
+ logger = logging.getLogger(__name__)
23
+
21
24
  TOOLS: list[Tool] = [
22
25
  Tool(
23
26
  name="run_ansible_playbook",
@@ -146,8 +149,8 @@ async def _handle_run_ansible_playbook(args: dict) -> list[TextContent]:
146
149
  try:
147
150
  if _activity is not None:
148
151
  _activity.push(event_type, {"run_id": run_id, "line": line})
149
- except Exception:
150
- pass
152
+ except Exception as exc:
153
+ logger.warning("Failed to push ansible line event for run %s: %s", run_id, exc)
151
154
 
152
155
  await asyncio.gather(
153
156
  _drain(proc.stdout, stdout_lines, "ansible.playbook.line"),
@@ -184,8 +187,8 @@ async def _handle_run_ansible_playbook(args: dict) -> list[TextContent]:
184
187
  try:
185
188
  if _activity is not None:
186
189
  _activity.push("ansible.playbook.done", summary)
187
- except Exception:
188
- pass
190
+ except Exception as exc:
191
+ logger.warning("Failed to push ansible.playbook.done event for run %s: %s", run_id, exc)
189
192
 
190
193
  # --- store in memory with tag=ansible-run ---
191
194
  try:
@@ -0,0 +1,90 @@
1
+ """Brain-First Protocol MCP tools.
2
+
3
+ Exposes the brain-first memory consultation as an MCP tool so that
4
+ any MCP client can ask "what do I already know about this?" before
5
+ acting on a task.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from mcp.types import TextContent, Tool
11
+
12
+ from ._helpers import _json_response
13
+
14
+ TOOLS: list[Tool] = [
15
+ Tool(
16
+ name="brain_first_check",
17
+ description=(
18
+ "Brain-First Protocol: consult the agent's memory before "
19
+ "acting on a task. Extracts keywords from the given context, "
20
+ "searches memory for relevant prior knowledge, and returns "
21
+ "any matching memories. Use this before starting new work to "
22
+ "avoid duplicating effort or missing prior decisions."
23
+ ),
24
+ inputSchema={
25
+ "type": "object",
26
+ "properties": {
27
+ "context": {
28
+ "type": "string",
29
+ "description": (
30
+ "The task description, prompt, or action context "
31
+ "to search memory for"
32
+ ),
33
+ },
34
+ "tags": {
35
+ "type": "array",
36
+ "items": {"type": "string"},
37
+ "description": "Optional tag filter for the memory search",
38
+ },
39
+ "max_results": {
40
+ "type": "integer",
41
+ "description": "Max memories to return (default: from config, usually 5)",
42
+ },
43
+ },
44
+ "required": ["context"],
45
+ },
46
+ ),
47
+ ]
48
+
49
+
50
+ async def _handle_brain_first_check(args: dict) -> list[TextContent]:
51
+ """Run a brain-first memory consultation."""
52
+ from ..brain_first import BrainFirstConfig, brain_first_check, _load_config
53
+
54
+ context = args.get("context", "")
55
+ if not context:
56
+ return _json_response({"error": "context is required"})
57
+
58
+ config = _load_config()
59
+
60
+ # Allow per-call override of max_results
61
+ max_results = args.get("max_results")
62
+ if max_results is not None:
63
+ config.max_results = max_results
64
+
65
+ result = brain_first_check(
66
+ context=context,
67
+ config=config,
68
+ tags=args.get("tags"),
69
+ )
70
+
71
+ response = {
72
+ "enabled": result.enabled,
73
+ "query": result.query,
74
+ "keywords": result.keywords,
75
+ "memories_found": len(result.memories),
76
+ "memories": result.memories,
77
+ }
78
+
79
+ if result.error:
80
+ response["warning"] = result.error
81
+
82
+ if result.has_memories:
83
+ response["context_block"] = result.as_context()
84
+
85
+ return _json_response(response)
86
+
87
+
88
+ HANDLERS: dict = {
89
+ "brain_first_check": _handle_brain_first_check,
90
+ }
@@ -8,11 +8,14 @@ Exposes two tools:
8
8
  from __future__ import annotations
9
9
 
10
10
  import json as _json
11
+ import logging
11
12
 
12
13
  from mcp.types import TextContent, Tool
13
14
 
14
15
  from ._helpers import _error_response, _home, _json_response
15
16
 
17
+ logger = logging.getLogger(__name__)
18
+
16
19
  TOOLS: list[Tool] = [
17
20
  Tool(
18
21
  name="capauth_status",
@@ -82,8 +85,8 @@ async def _handle_capauth_status(_args: dict) -> list[TextContent]:
82
85
  if did_key_file.exists():
83
86
  try:
84
87
  result["did_key_file"] = did_key_file.read_text(encoding="utf-8").strip()
85
- except Exception:
86
- pass
88
+ except Exception as exc:
89
+ logger.warning("Failed to read DID key file: %s", exc)
87
90
 
88
91
  # Check identity file
89
92
  identity_file = home / "identity" / "identity.json"
@@ -92,8 +95,8 @@ async def _handle_capauth_status(_args: dict) -> list[TextContent]:
92
95
  ident = _json.loads(identity_file.read_text(encoding="utf-8"))
93
96
  result["identity_name"] = ident.get("name")
94
97
  result["identity_fingerprint"] = ident.get("fingerprint")
95
- except Exception:
96
- pass
98
+ except Exception as exc:
99
+ logger.warning("Failed to read identity.json for capauth status: %s", exc)
97
100
 
98
101
  return _json_response(result)
99
102