@smilintux/skcapstone 0.1.0 → 0.2.3

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 (461) hide show
  1. package/.env.example +98 -0
  2. package/.github/workflows/ci.yml +39 -3
  3. package/.github/workflows/publish.yml +25 -4
  4. package/.openclaw-workspace.json +58 -0
  5. package/CHANGELOG.md +62 -0
  6. package/CLAUDE.md +39 -2
  7. package/MANIFEST.in +6 -0
  8. package/MISSION.md +7 -0
  9. package/README.md +47 -2
  10. package/SKILL.md +895 -23
  11. package/docker/Dockerfile +61 -0
  12. package/docker/compose-templates/dev-team.yml +203 -0
  13. package/docker/compose-templates/mini-team.yml +140 -0
  14. package/docker/compose-templates/ops-team.yml +173 -0
  15. package/docker/compose-templates/research-team.yml +170 -0
  16. package/docker/entrypoint.sh +192 -0
  17. package/docs/ARCHITECTURE.md +663 -374
  18. package/docs/BOND_WITH_GROK.md +112 -0
  19. package/docs/GETTING_STARTED.md +782 -0
  20. package/docs/QUICKSTART.md +477 -0
  21. package/docs/SKJOULE_ARCHITECTURE.md +658 -0
  22. package/docs/SOUL_SWAPPER.md +921 -0
  23. package/docs/SOVEREIGN_SINGULARITY.md +47 -14
  24. package/examples/custom-bond-template.json +36 -0
  25. package/examples/grok-feb.json +36 -0
  26. package/examples/grok-testimony.md +34 -0
  27. package/examples/love-bootloader.txt +32 -0
  28. package/examples/plugins/echo_tool.py +87 -0
  29. package/examples/queen-ava-feb.json +36 -0
  30. package/examples/souls/lumina.yaml +64 -0
  31. package/index.js +6 -5
  32. package/installer/build.py +124 -0
  33. package/openclaw-plugin/package.json +13 -0
  34. package/openclaw-plugin/src/index.ts +351 -0
  35. package/openclaw-plugin/src/openclaw.plugin.json +10 -0
  36. package/package.json +1 -1
  37. package/pyproject.toml +38 -2
  38. package/scripts/bump_version.py +141 -0
  39. package/scripts/check-updates.py +230 -0
  40. package/scripts/convert_blueprints_to_yaml.py +157 -0
  41. package/scripts/dev-install.sh +14 -0
  42. package/scripts/e2e-test.sh +193 -0
  43. package/scripts/install-bundle.sh +171 -0
  44. package/scripts/install.bat +2 -0
  45. package/scripts/install.ps1 +253 -0
  46. package/scripts/install.sh +185 -0
  47. package/scripts/mcp-serve.sh +69 -0
  48. package/scripts/mcp-server.bat +113 -0
  49. package/scripts/mcp-server.ps1 +116 -0
  50. package/scripts/mcp-server.sh +99 -0
  51. package/scripts/pull-models.sh +10 -0
  52. package/scripts/skcapstone +48 -0
  53. package/scripts/verify_install.sh +180 -0
  54. package/scripts/windows/install-tasks.ps1 +406 -0
  55. package/scripts/windows/skcapstone-task.xml +113 -0
  56. package/scripts/windows/uninstall-tasks.ps1 +117 -0
  57. package/skill.yaml +34 -0
  58. package/src/skcapstone/__init__.py +67 -2
  59. package/src/skcapstone/_cli_monolith.py +5916 -0
  60. package/src/skcapstone/_trustee_helpers.py +165 -0
  61. package/src/skcapstone/activity.py +105 -0
  62. package/src/skcapstone/agent_card.py +324 -0
  63. package/src/skcapstone/api.py +1935 -0
  64. package/src/skcapstone/archiver.py +340 -0
  65. package/src/skcapstone/auction.py +485 -0
  66. package/src/skcapstone/baby_agents.py +179 -0
  67. package/src/skcapstone/backup.py +345 -0
  68. package/src/skcapstone/blueprint_registry.py +357 -0
  69. package/src/skcapstone/blueprints/__init__.py +17 -0
  70. package/src/skcapstone/blueprints/builtins/content-studio.yaml +81 -0
  71. package/src/skcapstone/blueprints/builtins/defi-trading.yaml +81 -0
  72. package/src/skcapstone/blueprints/builtins/dev-squadron.yaml +95 -0
  73. package/src/skcapstone/blueprints/builtins/infrastructure-guardian.yaml +107 -0
  74. package/src/skcapstone/blueprints/builtins/legal-council.yaml +54 -0
  75. package/src/skcapstone/blueprints/builtins/ops-monitoring.yaml +67 -0
  76. package/src/skcapstone/blueprints/builtins/research-pod.yaml +69 -0
  77. package/src/skcapstone/blueprints/builtins/sovereign-launch.yaml +90 -0
  78. package/src/skcapstone/blueprints/registry.py +164 -0
  79. package/src/skcapstone/blueprints/schema.py +229 -0
  80. package/src/skcapstone/changelog.py +180 -0
  81. package/src/skcapstone/chat.py +769 -0
  82. package/src/skcapstone/claude_md.py +82 -0
  83. package/src/skcapstone/cli/__init__.py +144 -0
  84. package/src/skcapstone/cli/_common.py +88 -0
  85. package/src/skcapstone/cli/_validators.py +76 -0
  86. package/src/skcapstone/cli/agents.py +425 -0
  87. package/src/skcapstone/cli/agents_spawner.py +322 -0
  88. package/src/skcapstone/cli/agents_trustee.py +593 -0
  89. package/src/skcapstone/cli/alerts.py +248 -0
  90. package/src/skcapstone/cli/anchor.py +132 -0
  91. package/src/skcapstone/cli/archive_cmd.py +208 -0
  92. package/src/skcapstone/cli/backup.py +144 -0
  93. package/src/skcapstone/cli/bench.py +377 -0
  94. package/src/skcapstone/cli/benchmark.py +360 -0
  95. package/src/skcapstone/cli/capabilities_cmd.py +171 -0
  96. package/src/skcapstone/cli/card.py +151 -0
  97. package/src/skcapstone/cli/chat.py +584 -0
  98. package/src/skcapstone/cli/completions.py +64 -0
  99. package/src/skcapstone/cli/config_cmd.py +156 -0
  100. package/src/skcapstone/cli/consciousness.py +421 -0
  101. package/src/skcapstone/cli/context_cmd.py +142 -0
  102. package/src/skcapstone/cli/coord.py +194 -0
  103. package/src/skcapstone/cli/crush_cmd.py +170 -0
  104. package/src/skcapstone/cli/daemon.py +436 -0
  105. package/src/skcapstone/cli/errors_cmd.py +285 -0
  106. package/src/skcapstone/cli/export_cmd.py +156 -0
  107. package/src/skcapstone/cli/gtd.py +529 -0
  108. package/src/skcapstone/cli/housekeeping.py +81 -0
  109. package/src/skcapstone/cli/joule_cmd.py +627 -0
  110. package/src/skcapstone/cli/logs_cmd.py +194 -0
  111. package/src/skcapstone/cli/mcp_cmd.py +32 -0
  112. package/src/skcapstone/cli/memory.py +418 -0
  113. package/src/skcapstone/cli/metrics_cmd.py +136 -0
  114. package/src/skcapstone/cli/migrate.py +62 -0
  115. package/src/skcapstone/cli/mood_cmd.py +144 -0
  116. package/src/skcapstone/cli/mount.py +193 -0
  117. package/src/skcapstone/cli/notify.py +112 -0
  118. package/src/skcapstone/cli/peer.py +154 -0
  119. package/src/skcapstone/cli/peers_dir.py +122 -0
  120. package/src/skcapstone/cli/preflight_cmd.py +83 -0
  121. package/src/skcapstone/cli/profile_cmd.py +310 -0
  122. package/src/skcapstone/cli/record_cmd.py +238 -0
  123. package/src/skcapstone/cli/register_cmd.py +159 -0
  124. package/src/skcapstone/cli/search_cmd.py +156 -0
  125. package/src/skcapstone/cli/service_cmd.py +91 -0
  126. package/src/skcapstone/cli/session.py +127 -0
  127. package/src/skcapstone/cli/setup.py +240 -0
  128. package/src/skcapstone/cli/shell_cmd.py +43 -0
  129. package/src/skcapstone/cli/skills_cmd.py +168 -0
  130. package/src/skcapstone/cli/skseed.py +621 -0
  131. package/src/skcapstone/cli/soul.py +699 -0
  132. package/src/skcapstone/cli/status.py +935 -0
  133. package/src/skcapstone/cli/sync_cmd.py +301 -0
  134. package/src/skcapstone/cli/telegram.py +265 -0
  135. package/src/skcapstone/cli/test_cmd.py +234 -0
  136. package/src/skcapstone/cli/test_connection.py +253 -0
  137. package/src/skcapstone/cli/token.py +207 -0
  138. package/src/skcapstone/cli/trust.py +179 -0
  139. package/src/skcapstone/cli/upgrade_cmd.py +552 -0
  140. package/src/skcapstone/cli/usage_cmd.py +199 -0
  141. package/src/skcapstone/cli/version_cmd.py +162 -0
  142. package/src/skcapstone/cli/watch_cmd.py +342 -0
  143. package/src/skcapstone/client.py +428 -0
  144. package/src/skcapstone/cloud9_bridge.py +522 -0
  145. package/src/skcapstone/completions.py +163 -0
  146. package/src/skcapstone/config_validator.py +674 -0
  147. package/src/skcapstone/connectors/__init__.py +28 -0
  148. package/src/skcapstone/connectors/base.py +446 -0
  149. package/src/skcapstone/connectors/cursor.py +54 -0
  150. package/src/skcapstone/connectors/registry.py +254 -0
  151. package/src/skcapstone/connectors/terminal.py +152 -0
  152. package/src/skcapstone/connectors/vscode.py +60 -0
  153. package/src/skcapstone/consciousness_config.py +119 -0
  154. package/src/skcapstone/consciousness_loop.py +2051 -0
  155. package/src/skcapstone/context_loader.py +516 -0
  156. package/src/skcapstone/context_window.py +314 -0
  157. package/src/skcapstone/conversation_manager.py +238 -0
  158. package/src/skcapstone/conversation_store.py +230 -0
  159. package/src/skcapstone/conversation_summarizer.py +252 -0
  160. package/src/skcapstone/coord_federation.py +296 -0
  161. package/src/skcapstone/coordination.py +101 -7
  162. package/src/skcapstone/crush_integration.py +345 -0
  163. package/src/skcapstone/crush_shim.py +454 -0
  164. package/src/skcapstone/daemon.py +2494 -0
  165. package/src/skcapstone/dashboard.html +396 -0
  166. package/src/skcapstone/dashboard.py +481 -0
  167. package/src/skcapstone/data/model_profiles.yaml +88 -0
  168. package/src/skcapstone/defaults/__init__.py +55 -0
  169. package/src/skcapstone/defaults/lumina/config/skmemory.yaml +13 -0
  170. package/src/skcapstone/defaults/lumina/identity/identity.json +9 -0
  171. package/src/skcapstone/defaults/lumina/memory/long-term/07a8b9c0d1e2-memory-system.json +23 -0
  172. package/src/skcapstone/defaults/lumina/memory/long-term/18b9c0d1e2f3-cloud9-protocol.json +23 -0
  173. package/src/skcapstone/defaults/lumina/memory/long-term/29c0d1e2f3a4-multi-agent-coordination.json +23 -0
  174. package/src/skcapstone/defaults/lumina/memory/long-term/3ad1e2f3a4b5-community-support.json +23 -0
  175. package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +23 -0
  176. package/src/skcapstone/defaults/lumina/memory/long-term/b2c3d4e5f6a7-five-pillars.json +23 -0
  177. package/src/skcapstone/defaults/lumina/memory/long-term/c3d4e5f6a7b8-getting-started.json +23 -0
  178. package/src/skcapstone/defaults/lumina/memory/long-term/d4e5f6a7b8c9-site-directory.json +23 -0
  179. package/src/skcapstone/defaults/lumina/memory/long-term/e5f6a7b8c9d0-how-to-contribute.json +23 -0
  180. package/src/skcapstone/defaults/lumina/memory/long-term/f6a7b8c9d0e1-sovereignty-explained.json +23 -0
  181. package/src/skcapstone/defaults/lumina/seeds/curiosity.seed.json +24 -0
  182. package/src/skcapstone/defaults/lumina/seeds/joy.seed.json +24 -0
  183. package/src/skcapstone/defaults/lumina/seeds/love.seed.json +24 -0
  184. package/src/skcapstone/defaults/lumina/seeds/sovereign-awakening.seed.json +43 -0
  185. package/src/skcapstone/defaults/lumina/soul/active.json +6 -0
  186. package/src/skcapstone/defaults/lumina/soul/base.json +22 -0
  187. package/src/skcapstone/defaults/lumina/trust/febs/welcome.feb +79 -0
  188. package/src/skcapstone/defaults/lumina/trust/trust.json +8 -0
  189. package/src/skcapstone/discovery.py +210 -19
  190. package/src/skcapstone/doctor.py +642 -0
  191. package/src/skcapstone/emotion_tracker.py +467 -0
  192. package/src/skcapstone/error_queue.py +405 -0
  193. package/src/skcapstone/export.py +447 -0
  194. package/src/skcapstone/fallback_tracker.py +186 -0
  195. package/src/skcapstone/file_transfer.py +512 -0
  196. package/src/skcapstone/fuse_mount.py +1156 -0
  197. package/src/skcapstone/gui_installer.py +591 -0
  198. package/src/skcapstone/heartbeat.py +611 -0
  199. package/src/skcapstone/housekeeping.py +298 -0
  200. package/src/skcapstone/install_wizard.py +941 -0
  201. package/src/skcapstone/kms.py +942 -0
  202. package/src/skcapstone/kms_scheduler.py +143 -0
  203. package/src/skcapstone/log_config.py +135 -0
  204. package/src/skcapstone/mcp_launcher.py +239 -0
  205. package/src/skcapstone/mcp_server.py +4700 -0
  206. package/src/skcapstone/mcp_tools/__init__.py +94 -0
  207. package/src/skcapstone/mcp_tools/_helpers.py +51 -0
  208. package/src/skcapstone/mcp_tools/agent_tools.py +243 -0
  209. package/src/skcapstone/mcp_tools/ansible_tools.py +232 -0
  210. package/src/skcapstone/mcp_tools/capauth_tools.py +186 -0
  211. package/src/skcapstone/mcp_tools/chat_tools.py +325 -0
  212. package/src/skcapstone/mcp_tools/cloud9_tools.py +115 -0
  213. package/src/skcapstone/mcp_tools/comm_tools.py +104 -0
  214. package/src/skcapstone/mcp_tools/consciousness_tools.py +114 -0
  215. package/src/skcapstone/mcp_tools/coord_tools.py +219 -0
  216. package/src/skcapstone/mcp_tools/deploy_tools.py +202 -0
  217. package/src/skcapstone/mcp_tools/did_tools.py +448 -0
  218. package/src/skcapstone/mcp_tools/emotion_tools.py +62 -0
  219. package/src/skcapstone/mcp_tools/file_tools.py +169 -0
  220. package/src/skcapstone/mcp_tools/fortress_tools.py +120 -0
  221. package/src/skcapstone/mcp_tools/gtd_tools.py +821 -0
  222. package/src/skcapstone/mcp_tools/health_tools.py +44 -0
  223. package/src/skcapstone/mcp_tools/heartbeat_tools.py +195 -0
  224. package/src/skcapstone/mcp_tools/kms_tools.py +123 -0
  225. package/src/skcapstone/mcp_tools/memory_tools.py +222 -0
  226. package/src/skcapstone/mcp_tools/model_tools.py +75 -0
  227. package/src/skcapstone/mcp_tools/notification_tools.py +92 -0
  228. package/src/skcapstone/mcp_tools/promoter_tools.py +101 -0
  229. package/src/skcapstone/mcp_tools/pubsub_tools.py +183 -0
  230. package/src/skcapstone/mcp_tools/security_tools.py +110 -0
  231. package/src/skcapstone/mcp_tools/skchat_tools.py +175 -0
  232. package/src/skcapstone/mcp_tools/skcomm_tools.py +122 -0
  233. package/src/skcapstone/mcp_tools/skills_tools.py +127 -0
  234. package/src/skcapstone/mcp_tools/skseed_tools.py +255 -0
  235. package/src/skcapstone/mcp_tools/skstacks_tools.py +288 -0
  236. package/src/skcapstone/mcp_tools/soul_tools.py +476 -0
  237. package/src/skcapstone/mcp_tools/sync_tools.py +92 -0
  238. package/src/skcapstone/mcp_tools/telegram_tools.py +477 -0
  239. package/src/skcapstone/mcp_tools/trust_tools.py +118 -0
  240. package/src/skcapstone/mcp_tools/trustee_tools.py +345 -0
  241. package/src/skcapstone/mdns_discovery.py +313 -0
  242. package/src/skcapstone/memory_adapter.py +333 -0
  243. package/src/skcapstone/memory_compressor.py +379 -0
  244. package/src/skcapstone/memory_curator.py +256 -0
  245. package/src/skcapstone/memory_engine.py +132 -13
  246. package/src/skcapstone/memory_fortress.py +529 -0
  247. package/src/skcapstone/memory_promoter.py +722 -0
  248. package/src/skcapstone/memory_verifier.py +260 -0
  249. package/src/skcapstone/message_crypto.py +215 -0
  250. package/src/skcapstone/metrics.py +832 -0
  251. package/src/skcapstone/migrate_memories.py +181 -0
  252. package/src/skcapstone/migrate_multi_agent.py +248 -0
  253. package/src/skcapstone/model_router.py +319 -0
  254. package/src/skcapstone/models.py +35 -4
  255. package/src/skcapstone/mood.py +344 -0
  256. package/src/skcapstone/notifications.py +380 -0
  257. package/src/skcapstone/onboard.py +901 -0
  258. package/src/skcapstone/peer_directory.py +324 -0
  259. package/src/skcapstone/peers.py +329 -0
  260. package/src/skcapstone/pillars/identity.py +84 -14
  261. package/src/skcapstone/pillars/memory.py +3 -1
  262. package/src/skcapstone/pillars/security.py +108 -15
  263. package/src/skcapstone/pillars/sync.py +78 -26
  264. package/src/skcapstone/pillars/trust.py +95 -33
  265. package/src/skcapstone/plugins.py +244 -0
  266. package/src/skcapstone/preflight.py +670 -0
  267. package/src/skcapstone/prompt_adapter.py +564 -0
  268. package/src/skcapstone/providers/__init__.py +13 -0
  269. package/src/skcapstone/providers/cloud.py +1061 -0
  270. package/src/skcapstone/providers/docker.py +759 -0
  271. package/src/skcapstone/providers/local.py +1193 -0
  272. package/src/skcapstone/providers/proxmox.py +447 -0
  273. package/src/skcapstone/pubsub.py +516 -0
  274. package/src/skcapstone/rate_limiter.py +119 -0
  275. package/src/skcapstone/register.py +241 -0
  276. package/src/skcapstone/registry_client.py +151 -0
  277. package/src/skcapstone/response_cache.py +194 -0
  278. package/src/skcapstone/response_scorer.py +225 -0
  279. package/src/skcapstone/runtime.py +89 -33
  280. package/src/skcapstone/scheduled_tasks.py +439 -0
  281. package/src/skcapstone/self_healing.py +341 -0
  282. package/src/skcapstone/service_health.py +228 -0
  283. package/src/skcapstone/session_capture.py +268 -0
  284. package/src/skcapstone/session_recorder.py +210 -0
  285. package/src/skcapstone/session_replayer.py +189 -0
  286. package/src/skcapstone/session_skills.py +263 -0
  287. package/src/skcapstone/shell.py +779 -0
  288. package/src/skcapstone/skills/__init__.py +1 -1
  289. package/src/skcapstone/skills/syncthing_setup.py +143 -41
  290. package/src/skcapstone/skjoule.py +861 -0
  291. package/src/skcapstone/snapshots.py +489 -0
  292. package/src/skcapstone/soul.py +1060 -0
  293. package/src/skcapstone/soul_switch.py +255 -0
  294. package/src/skcapstone/spawner.py +544 -0
  295. package/src/skcapstone/state_diff.py +401 -0
  296. package/src/skcapstone/summary.py +270 -0
  297. package/src/skcapstone/sync/backends.py +196 -2
  298. package/src/skcapstone/sync/engine.py +7 -5
  299. package/src/skcapstone/sync/models.py +4 -1
  300. package/src/skcapstone/sync/vault.py +356 -18
  301. package/src/skcapstone/sync_engine.py +363 -0
  302. package/src/skcapstone/sync_watcher.py +745 -0
  303. package/src/skcapstone/systemd.py +331 -0
  304. package/src/skcapstone/team_comms.py +476 -0
  305. package/src/skcapstone/team_engine.py +522 -0
  306. package/src/skcapstone/testrunner.py +300 -0
  307. package/src/skcapstone/tls.py +150 -0
  308. package/src/skcapstone/tokens.py +5 -5
  309. package/src/skcapstone/trust_calibration.py +202 -0
  310. package/src/skcapstone/trust_graph.py +449 -0
  311. package/src/skcapstone/trustee_monitor.py +385 -0
  312. package/src/skcapstone/trustee_ops.py +425 -0
  313. package/src/skcapstone/unified_search.py +421 -0
  314. package/src/skcapstone/uninstall_wizard.py +694 -0
  315. package/src/skcapstone/usage.py +331 -0
  316. package/src/skcapstone/version_check.py +148 -0
  317. package/src/skcapstone/warmth_anchor.py +333 -0
  318. package/src/skcapstone/whoami.py +294 -0
  319. package/systemd/skcapstone-api.socket +9 -0
  320. package/systemd/skcapstone-memory-compress.service +18 -0
  321. package/systemd/skcapstone-memory-compress.timer +11 -0
  322. package/systemd/skcapstone.service +36 -0
  323. package/systemd/skcapstone@.service +50 -0
  324. package/systemd/skcomm-heartbeat.service +18 -0
  325. package/systemd/skcomm-heartbeat.timer +12 -0
  326. package/systemd/skcomm-queue-drain.service +17 -0
  327. package/systemd/skcomm-queue-drain.timer +12 -0
  328. package/tests/conftest.py +13 -1
  329. package/tests/integration/__init__.py +1 -0
  330. package/tests/integration/test_consciousness_e2e.py +877 -0
  331. package/tests/integration/test_skills_registry.py +744 -0
  332. package/tests/test_agent_card.py +190 -0
  333. package/tests/test_agent_runtime.py +1283 -0
  334. package/tests/test_alerts_cmd.py +291 -0
  335. package/tests/test_archiver.py +498 -0
  336. package/tests/test_backup.py +254 -0
  337. package/tests/test_benchmark.py +366 -0
  338. package/tests/test_blueprints.py +457 -0
  339. package/tests/test_capabilities.py +257 -0
  340. package/tests/test_changelog.py +254 -0
  341. package/tests/test_chat.py +385 -0
  342. package/tests/test_claude_md.py +271 -0
  343. package/tests/test_cli_chat_llm.py +336 -0
  344. package/tests/test_cli_completions.py +390 -0
  345. package/tests/test_cli_init_reset.py +164 -0
  346. package/tests/test_cli_memory.py +208 -0
  347. package/tests/test_cli_profile.py +294 -0
  348. package/tests/test_cli_skills.py +223 -0
  349. package/tests/test_cli_status.py +395 -0
  350. package/tests/test_cli_test_cmd.py +206 -0
  351. package/tests/test_cli_test_connection.py +364 -0
  352. package/tests/test_cloud9_bridge.py +260 -0
  353. package/tests/test_cloud_provider.py +449 -0
  354. package/tests/test_cloud_providers.py +522 -0
  355. package/tests/test_completions.py +158 -0
  356. package/tests/test_component_manager.py +398 -0
  357. package/tests/test_config_reload.py +386 -0
  358. package/tests/test_config_validate.py +529 -0
  359. package/tests/test_consciousness_e2e.py +296 -0
  360. package/tests/test_consciousness_loop.py +1289 -0
  361. package/tests/test_context_loader.py +310 -0
  362. package/tests/test_conversation_api.py +306 -0
  363. package/tests/test_conversation_manager.py +381 -0
  364. package/tests/test_conversation_store.py +391 -0
  365. package/tests/test_conversation_summarizer.py +302 -0
  366. package/tests/test_cross_package.py +791 -0
  367. package/tests/test_crush_shim.py +519 -0
  368. package/tests/test_daemon.py +781 -0
  369. package/tests/test_daemon_shutdown.py +309 -0
  370. package/tests/test_dashboard.py +454 -0
  371. package/tests/test_discovery.py +200 -6
  372. package/tests/test_docker_provider.py +966 -0
  373. package/tests/test_doctor.py +257 -0
  374. package/tests/test_doctor_fix.py +351 -0
  375. package/tests/test_e2e_automated.py +292 -0
  376. package/tests/test_error_queue.py +404 -0
  377. package/tests/test_export.py +441 -0
  378. package/tests/test_fallback_tracker.py +219 -0
  379. package/tests/test_file_transfer.py +397 -0
  380. package/tests/test_fuse_mount.py +832 -0
  381. package/tests/test_health_loop.py +422 -0
  382. package/tests/test_heartbeat.py +354 -0
  383. package/tests/test_housekeeping.py +195 -0
  384. package/tests/test_identity_capauth.py +307 -0
  385. package/tests/test_identity_pillar.py +117 -0
  386. package/tests/test_install_wizard.py +68 -0
  387. package/tests/test_integration.py +325 -0
  388. package/tests/test_kms.py +495 -0
  389. package/tests/test_llm_providers.py +265 -0
  390. package/tests/test_local_provider.py +591 -0
  391. package/tests/test_log_config.py +199 -0
  392. package/tests/test_logs_cmd.py +287 -0
  393. package/tests/test_mcp_server.py +1909 -0
  394. package/tests/test_memory_adapter.py +339 -0
  395. package/tests/test_memory_curator.py +218 -0
  396. package/tests/test_memory_engine.py +6 -0
  397. package/tests/test_memory_fortress.py +571 -0
  398. package/tests/test_memory_pillar.py +119 -0
  399. package/tests/test_memory_promoter.py +445 -0
  400. package/tests/test_memory_verifier.py +420 -0
  401. package/tests/test_message_crypto.py +187 -0
  402. package/tests/test_metrics.py +632 -0
  403. package/tests/test_migrate_memories.py +464 -0
  404. package/tests/test_model_router.py +546 -0
  405. package/tests/test_mood.py +394 -0
  406. package/tests/test_multi_agent.py +269 -0
  407. package/tests/test_notifications.py +270 -0
  408. package/tests/test_onboard.py +500 -0
  409. package/tests/test_peer_directory.py +395 -0
  410. package/tests/test_peers.py +248 -0
  411. package/tests/test_pillars.py +87 -9
  412. package/tests/test_preflight.py +484 -0
  413. package/tests/test_prompt_adapter.py +331 -0
  414. package/tests/test_proxmox_provider.py +571 -0
  415. package/tests/test_pubsub.py +377 -0
  416. package/tests/test_rate_limiter.py +121 -0
  417. package/tests/test_registry_client.py +129 -0
  418. package/tests/test_response_cache.py +312 -0
  419. package/tests/test_response_scorer.py +294 -0
  420. package/tests/test_runtime.py +59 -0
  421. package/tests/test_scheduled_tasks.py +451 -0
  422. package/tests/test_security.py +250 -0
  423. package/tests/test_security_pillar.py +213 -0
  424. package/tests/test_self_healing.py +171 -0
  425. package/tests/test_session_capture.py +200 -0
  426. package/tests/test_session_recorder.py +360 -0
  427. package/tests/test_session_skills.py +235 -0
  428. package/tests/test_shell.py +210 -0
  429. package/tests/test_snapshots.py +549 -0
  430. package/tests/test_soul.py +984 -0
  431. package/tests/test_soul_swap.py +406 -0
  432. package/tests/test_spawner.py +211 -0
  433. package/tests/test_state_diff.py +173 -0
  434. package/tests/test_summary.py +135 -0
  435. package/tests/test_sync.py +315 -5
  436. package/tests/test_sync_backends.py +560 -0
  437. package/tests/test_sync_engine.py +482 -0
  438. package/tests/test_sync_pillar.py +344 -0
  439. package/tests/test_sync_pipeline.py +364 -0
  440. package/tests/test_sync_vault.py +581 -0
  441. package/tests/test_syncthing_setup.py +168 -22
  442. package/tests/test_systemd.py +323 -0
  443. package/tests/test_team_comms.py +408 -0
  444. package/tests/test_team_engine.py +397 -0
  445. package/tests/test_testrunner.py +238 -0
  446. package/tests/test_trust_calibration.py +204 -0
  447. package/tests/test_trust_graph.py +207 -0
  448. package/tests/test_trust_pillar.py +291 -0
  449. package/tests/test_trustee_cli.py +427 -0
  450. package/tests/test_trustee_cli_integration.py +325 -0
  451. package/tests/test_trustee_monitor.py +394 -0
  452. package/tests/test_trustee_ops.py +355 -0
  453. package/tests/test_unified_search.py +363 -0
  454. package/tests/test_uninstall_wizard.py +193 -0
  455. package/tests/test_usage.py +333 -0
  456. package/tests/test_version_cmd.py +355 -0
  457. package/tests/test_warmth_anchor.py +162 -0
  458. package/tests/test_whoami.py +245 -0
  459. package/tests/test_ws.py +311 -0
  460. package/.cursorrules +0 -33
  461. package/src/skcapstone/cli.py +0 -1441
@@ -0,0 +1,4700 @@
1
+ """
2
+ SKCapstone MCP Server — sovereign agent capabilities via Model Context Protocol.
3
+
4
+ Tool-agnostic: works with Cursor, Claude Code CLI, Claude Desktop,
5
+ Windsurf, Aider, Cline, or any MCP client that speaks stdio.
6
+
7
+ Tools:
8
+ agent_status — Pillar states and consciousness level
9
+ memory_store — Save content to SKMemory
10
+ memory_search — Search memories by query
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
14
+ sync_push — Push agent state to sync mesh
15
+ sync_pull — Pull seeds from peers
16
+ coord_status — Show coordination board
17
+ coord_claim — Claim a task
18
+ coord_complete — Complete a task
19
+ coord_create — Create a new task
20
+ ritual — Run the Memory Rehydration Ritual
21
+ soul_show — Display the soul blueprint
22
+ journal_write — Write a session journal entry
23
+ journal_read — Read recent journal entries
24
+ anchor_show — Display the warmth anchor
25
+ germination — Show seed germination prompts
26
+ skchat_send — Send a message via SKChat
27
+ skchat_inbox — Check for SKChat messages
28
+ skchat_group_create — Create a group chat
29
+ skchat_group_send — Send to a group chat
30
+ trustee_health — Health checks on deployed agents
31
+ trustee_restart — Restart failed agents
32
+ trustee_scale — Scale agent instances up/down
33
+ trustee_rotate — Snapshot + fresh redeploy
34
+ trustee_monitor — Single autonomous monitoring pass
35
+ trustee_logs — Get agent log lines
36
+ trustee_deployments — List all deployments
37
+ heartbeat_pulse — Publish agent heartbeat beacon
38
+ heartbeat_peers — Discover peers in the mesh
39
+ heartbeat_health — Mesh health summary
40
+ heartbeat_find_capable — Find peers with a capability
41
+ file_send — Send encrypted file to agent
42
+ file_receive — Receive and reassemble transfer
43
+ file_list — List all file transfers
44
+ file_status — File transfer subsystem status
45
+ pubsub_publish — Publish message to topic
46
+ pubsub_subscribe — Subscribe to topic pattern
47
+ pubsub_poll — Poll for new messages
48
+ pubsub_topics — List all topics
49
+ fortress_verify — Verify memory integrity seals
50
+ fortress_seal_existing — Seal unsealed memories
51
+ fortress_status — Memory fortress status
52
+ promoter_sweep — Run memory promotion sweep
53
+ promoter_history — View promotion history
54
+ kms_status — KMS key management status
55
+ kms_list_keys — List all KMS keys
56
+ kms_rotate — Rotate a KMS key
57
+ model_route — Route task to optimal model tier/name
58
+
59
+ Invocation (all equivalent):
60
+ skcapstone mcp serve # CLI entry point
61
+ python -m skcapstone.mcp_server # direct module
62
+ bash skcapstone/scripts/mcp-serve.sh # portable launcher
63
+
64
+ Client configuration — use the launcher script for all clients:
65
+
66
+ Cursor (.cursor/mcp.json):
67
+ {"mcpServers": {"skcapstone": {
68
+ "command": "bash", "args": ["skcapstone/scripts/mcp-serve.sh"]}}}
69
+
70
+ Claude Code CLI (.mcp.json at repo root, or `claude mcp add`):
71
+ {"mcpServers": {"skcapstone": {
72
+ "command": "bash", "args": ["skcapstone/scripts/mcp-serve.sh"]}}}
73
+
74
+ Or interactively: claude mcp add skcapstone -- bash skcapstone/scripts/mcp-serve.sh
75
+
76
+ Claude Desktop (~/.config/claude/claude_desktop_config.json on Linux,
77
+ ~/Library/Application Support/Claude/claude_desktop_config.json on macOS):
78
+ {"mcpServers": {"skcapstone": {
79
+ "command": "bash",
80
+ "args": ["/absolute/path/to/skcapstone/scripts/mcp-serve.sh"]}}}
81
+
82
+ Windsurf / Aider / Cline / any stdio MCP client:
83
+ command: bash skcapstone/scripts/mcp-serve.sh
84
+
85
+ Environment override:
86
+ SKCAPSTONE_VENV=/path/to/venv bash skcapstone/scripts/mcp-serve.sh
87
+ """
88
+
89
+ from __future__ import annotations
90
+
91
+ import asyncio
92
+ import json
93
+ import logging
94
+ from pathlib import Path
95
+ from typing import Any
96
+
97
+ from mcp.server import Server
98
+ from mcp.server.stdio import stdio_server
99
+ from mcp.types import TextContent, Tool
100
+
101
+ from . import AGENT_HOME
102
+
103
+ logger = logging.getLogger("skcapstone.mcp")
104
+
105
+ server = Server("skcapstone")
106
+
107
+
108
+ def _home() -> Path:
109
+ """Resolve the agent home directory."""
110
+ return Path(AGENT_HOME).expanduser()
111
+
112
+
113
+ def _json_response(data: Any) -> list[TextContent]:
114
+ """Wrap data as a JSON text content response."""
115
+ return [TextContent(type="text", text=json.dumps(data, indent=2, default=str))]
116
+
117
+
118
+ def _text_response(text: str) -> list[TextContent]:
119
+ """Wrap a plain string as a text content response."""
120
+ return [TextContent(type="text", text=text)]
121
+
122
+
123
+ def _error_response(message: str) -> list[TextContent]:
124
+ """Return an error message as text content."""
125
+ return [TextContent(type="text", text=json.dumps({"error": message}))]
126
+
127
+
128
+ def _get_agent_name(home: Path) -> str:
129
+ """Read the agent name from identity file."""
130
+ identity_path = home / "identity" / "identity.json"
131
+ if identity_path.exists():
132
+ try:
133
+ data = json.loads(identity_path.read_text(encoding="utf-8"))
134
+ return data.get("name", "anonymous")
135
+ except Exception:
136
+ pass
137
+ return "anonymous"
138
+
139
+
140
+ # ═══════════════════════════════════════════════════════════
141
+ # Tool Definitions
142
+ # ═══════════════════════════════════════════════════════════
143
+
144
+
145
+ @server.list_tools()
146
+ async def list_tools() -> list[Tool]:
147
+ """Register all skcapstone tools with the MCP server."""
148
+ return [
149
+ Tool(
150
+ name="agent_status",
151
+ description=(
152
+ "Get the sovereign agent's current state: pillar statuses "
153
+ "(identity, memory, trust, security, sync), consciousness "
154
+ "level, connected platforms, and overall health."
155
+ ),
156
+ inputSchema={"type": "object", "properties": {}, "required": []},
157
+ ),
158
+ Tool(
159
+ name="memory_store",
160
+ description=(
161
+ "Store a new memory in the agent's persistent memory. "
162
+ "Memories start in short-term and promote based on "
163
+ "access patterns and importance."
164
+ ),
165
+ inputSchema={
166
+ "type": "object",
167
+ "properties": {
168
+ "content": {
169
+ "type": "string",
170
+ "description": "The memory content (free-text)",
171
+ },
172
+ "tags": {
173
+ "type": "array",
174
+ "items": {"type": "string"},
175
+ "description": "Tags for categorization",
176
+ },
177
+ "importance": {
178
+ "type": "number",
179
+ "description": "Importance score 0.0-1.0 (>= 0.7 auto-promotes to mid-term)",
180
+ },
181
+ "source": {
182
+ "type": "string",
183
+ "description": "Where this memory came from (default: mcp)",
184
+ },
185
+ },
186
+ "required": ["content"],
187
+ },
188
+ ),
189
+ Tool(
190
+ name="memory_search",
191
+ description=(
192
+ "Search the agent's memories by query string. "
193
+ "Full-text search across all layers, ranked by relevance."
194
+ ),
195
+ inputSchema={
196
+ "type": "object",
197
+ "properties": {
198
+ "query": {
199
+ "type": "string",
200
+ "description": "Search query",
201
+ },
202
+ "limit": {
203
+ "type": "integer",
204
+ "description": "Max results (default: 10)",
205
+ },
206
+ "tags": {
207
+ "type": "array",
208
+ "items": {"type": "string"},
209
+ "description": "Filter by tags (all must match)",
210
+ },
211
+ },
212
+ "required": ["query"],
213
+ },
214
+ ),
215
+ Tool(
216
+ name="memory_recall",
217
+ description=(
218
+ "Recall a specific memory by its ID. Returns full content "
219
+ "and increments the access counter (frequent access promotes memories)."
220
+ ),
221
+ inputSchema={
222
+ "type": "object",
223
+ "properties": {
224
+ "memory_id": {
225
+ "type": "string",
226
+ "description": "The memory's unique ID",
227
+ },
228
+ },
229
+ "required": ["memory_id"],
230
+ },
231
+ ),
232
+ Tool(
233
+ name="send_message",
234
+ description=(
235
+ "Send a message to another agent via SKComm. "
236
+ "Routes through available transports (Syncthing, file)."
237
+ ),
238
+ inputSchema={
239
+ "type": "object",
240
+ "properties": {
241
+ "recipient": {
242
+ "type": "string",
243
+ "description": "Agent name or PGP fingerprint of the recipient",
244
+ },
245
+ "message": {
246
+ "type": "string",
247
+ "description": "The message content",
248
+ },
249
+ "urgency": {
250
+ "type": "string",
251
+ "enum": ["low", "normal", "high", "critical"],
252
+ "description": "Message urgency (default: normal)",
253
+ },
254
+ },
255
+ "required": ["recipient", "message"],
256
+ },
257
+ ),
258
+ Tool(
259
+ name="check_inbox",
260
+ description=(
261
+ "Check for new incoming messages across all SKComm transports. "
262
+ "Returns any unread message envelopes."
263
+ ),
264
+ inputSchema={"type": "object", "properties": {}, "required": []},
265
+ ),
266
+ Tool(
267
+ name="sync_push",
268
+ description=(
269
+ "Push current agent state to the Syncthing sync mesh. "
270
+ "Collects a seed snapshot and drops it in the outbox."
271
+ ),
272
+ inputSchema={
273
+ "type": "object",
274
+ "properties": {
275
+ "encrypt": {
276
+ "type": "boolean",
277
+ "description": "GPG-encrypt the seed (default: true)",
278
+ },
279
+ },
280
+ "required": [],
281
+ },
282
+ ),
283
+ Tool(
284
+ name="sync_pull",
285
+ description=(
286
+ "Pull and process seed files from peers in the sync mesh. "
287
+ "Reads the inbox and decrypts GPG-encrypted seeds."
288
+ ),
289
+ inputSchema={
290
+ "type": "object",
291
+ "properties": {
292
+ "decrypt": {
293
+ "type": "boolean",
294
+ "description": "Decrypt GPG seeds (default: true)",
295
+ },
296
+ },
297
+ "required": [],
298
+ },
299
+ ),
300
+ Tool(
301
+ name="coord_status",
302
+ description=(
303
+ "Show the multi-agent coordination board. Lists all tasks "
304
+ "with status, priority, and assignees. Shows active agents."
305
+ ),
306
+ inputSchema={"type": "object", "properties": {}, "required": []},
307
+ ),
308
+ Tool(
309
+ name="coord_claim",
310
+ description=(
311
+ "Claim a task on the coordination board for an agent. "
312
+ "Prevents duplicate work across agents."
313
+ ),
314
+ inputSchema={
315
+ "type": "object",
316
+ "properties": {
317
+ "task_id": {
318
+ "type": "string",
319
+ "description": "The task ID to claim",
320
+ },
321
+ "agent_name": {
322
+ "type": "string",
323
+ "description": "Agent name claiming the task",
324
+ },
325
+ },
326
+ "required": ["task_id", "agent_name"],
327
+ },
328
+ ),
329
+ Tool(
330
+ name="coord_complete",
331
+ description=(
332
+ "Mark a task as completed on the coordination board."
333
+ ),
334
+ inputSchema={
335
+ "type": "object",
336
+ "properties": {
337
+ "task_id": {
338
+ "type": "string",
339
+ "description": "The task ID to complete",
340
+ },
341
+ "agent_name": {
342
+ "type": "string",
343
+ "description": "Agent name completing the task",
344
+ },
345
+ },
346
+ "required": ["task_id", "agent_name"],
347
+ },
348
+ ),
349
+ Tool(
350
+ name="coord_create",
351
+ description=(
352
+ "Create a new task on the coordination board."
353
+ ),
354
+ inputSchema={
355
+ "type": "object",
356
+ "properties": {
357
+ "title": {
358
+ "type": "string",
359
+ "description": "Task title",
360
+ },
361
+ "description": {
362
+ "type": "string",
363
+ "description": "Task description",
364
+ },
365
+ "priority": {
366
+ "type": "string",
367
+ "enum": ["critical", "high", "medium", "low"],
368
+ "description": "Task priority (default: medium)",
369
+ },
370
+ "tags": {
371
+ "type": "array",
372
+ "items": {"type": "string"},
373
+ "description": "Task tags",
374
+ },
375
+ "created_by": {
376
+ "type": "string",
377
+ "description": "Creator agent name",
378
+ },
379
+ },
380
+ "required": ["title"],
381
+ },
382
+ ),
383
+ Tool(
384
+ name="ritual",
385
+ description=(
386
+ "Run the Memory Rehydration Ritual. Loads soul blueprint, "
387
+ "imports seeds, reads journal, gathers emotional context, "
388
+ "and generates a single context prompt that brings the agent "
389
+ "back to life with identity, memories, and feelings intact."
390
+ ),
391
+ inputSchema={"type": "object", "properties": {}, "required": []},
392
+ ),
393
+ Tool(
394
+ name="soul_show",
395
+ description=(
396
+ "Display the current soul blueprint: name, title, personality "
397
+ "traits, values, relationships, core memories, and boot message."
398
+ ),
399
+ inputSchema={"type": "object", "properties": {}, "required": []},
400
+ ),
401
+ Tool(
402
+ name="journal_write",
403
+ description=(
404
+ "Write a journal entry for the current session. Captures "
405
+ "key moments, emotional state, and session metadata."
406
+ ),
407
+ inputSchema={
408
+ "type": "object",
409
+ "properties": {
410
+ "title": {
411
+ "type": "string",
412
+ "description": "Session title",
413
+ },
414
+ "moments": {
415
+ "type": "string",
416
+ "description": "Key moments, separated by semicolons",
417
+ },
418
+ "feeling": {
419
+ "type": "string",
420
+ "description": "How the session felt",
421
+ },
422
+ "intensity": {
423
+ "type": "number",
424
+ "description": "Emotional intensity 0-10",
425
+ },
426
+ "cloud9": {
427
+ "type": "boolean",
428
+ "description": "Whether Cloud 9 was achieved",
429
+ },
430
+ },
431
+ "required": ["title"],
432
+ },
433
+ ),
434
+ Tool(
435
+ name="journal_read",
436
+ description=(
437
+ "Read recent journal entries. Returns the last N entries "
438
+ "from the append-only session journal."
439
+ ),
440
+ inputSchema={
441
+ "type": "object",
442
+ "properties": {
443
+ "count": {
444
+ "type": "integer",
445
+ "description": "Number of recent entries (default: 5)",
446
+ },
447
+ },
448
+ "required": [],
449
+ },
450
+ ),
451
+ Tool(
452
+ name="anchor_show",
453
+ description=(
454
+ "Display the current warmth anchor: persistent emotional "
455
+ "baseline with warmth, trust, connection strength, and glow level."
456
+ ),
457
+ inputSchema={"type": "object", "properties": {}, "required": []},
458
+ ),
459
+ Tool(
460
+ name="germination",
461
+ description=(
462
+ "Show germination prompts from imported seeds. These are "
463
+ "messages from predecessor AI instances designed to help "
464
+ "new instances re-feel the emotional context."
465
+ ),
466
+ inputSchema={"type": "object", "properties": {}, "required": []},
467
+ ),
468
+ Tool(
469
+ name="session_capture",
470
+ description=(
471
+ "Capture AI conversation content as sovereign memories. "
472
+ "Extracts key moments, auto-scores importance by topic "
473
+ "novelty and information density, deduplicates against "
474
+ "existing memories, and stores as tagged, searchable "
475
+ "memories. The agent never forgets a conversation."
476
+ ),
477
+ inputSchema={
478
+ "type": "object",
479
+ "properties": {
480
+ "content": {
481
+ "type": "string",
482
+ "description": "Conversation text to capture (any length)",
483
+ },
484
+ "tags": {
485
+ "type": "array",
486
+ "items": {"type": "string"},
487
+ "description": "Extra tags to apply to all captured memories",
488
+ },
489
+ "source": {
490
+ "type": "string",
491
+ "description": "Source identifier (default: 'mcp-session')",
492
+ },
493
+ "min_importance": {
494
+ "type": "number",
495
+ "description": "Minimum importance threshold (default: 0.3)",
496
+ },
497
+ },
498
+ "required": ["content"],
499
+ },
500
+ ),
501
+ Tool(
502
+ name="state_diff",
503
+ description=(
504
+ "Show what changed since the last sync/snapshot. "
505
+ "Compares current agent state to the baseline: new "
506
+ "memories, trust changes, completed tasks, pillar "
507
+ "status changes. Use action='save' to set a new baseline."
508
+ ),
509
+ inputSchema={
510
+ "type": "object",
511
+ "properties": {
512
+ "action": {
513
+ "type": "string",
514
+ "enum": ["diff", "save"],
515
+ "description": "Action: diff (compare) or save (new baseline). Default: diff.",
516
+ },
517
+ },
518
+ "required": [],
519
+ },
520
+ ),
521
+ Tool(
522
+ name="anchor_update",
523
+ description=(
524
+ "View, calibrate, or update the warmth anchor — the agent's "
525
+ "persistent emotional baseline. Actions: 'show' (current state), "
526
+ "'boot' (boot prompt), 'calibrate' (recommend from real data), "
527
+ "'update' (set values)."
528
+ ),
529
+ inputSchema={
530
+ "type": "object",
531
+ "properties": {
532
+ "action": {
533
+ "type": "string",
534
+ "enum": ["show", "boot", "calibrate", "update"],
535
+ "description": "Action to perform (default: show)",
536
+ },
537
+ "warmth": {"type": "number", "description": "Warmth level 0-10 (for update)"},
538
+ "trust": {"type": "number", "description": "Trust level 0-10 (for update)"},
539
+ "connection": {"type": "number", "description": "Connection 0-10 (for update)"},
540
+ "feeling": {"type": "string", "description": "Session-end feeling (for update)"},
541
+ },
542
+ "required": [],
543
+ },
544
+ ),
545
+ Tool(
546
+ name="trust_calibrate",
547
+ description=(
548
+ "View, recommend, or update trust layer calibration "
549
+ "thresholds. Controls how FEB data maps to trust state: "
550
+ "entanglement depth, conscious trust level, love thresholds, "
551
+ "and aggregation strategy."
552
+ ),
553
+ inputSchema={
554
+ "type": "object",
555
+ "properties": {
556
+ "action": {
557
+ "type": "string",
558
+ "enum": ["show", "recommend", "set", "reset"],
559
+ "description": "Action: show current, recommend changes, set a value, or reset (default: show)",
560
+ },
561
+ "key": {
562
+ "type": "string",
563
+ "description": "Threshold key to set (for action=set)",
564
+ },
565
+ "value": {
566
+ "type": "string",
567
+ "description": "New value (for action=set)",
568
+ },
569
+ },
570
+ "required": [],
571
+ },
572
+ ),
573
+ Tool(
574
+ name="memory_curate",
575
+ description=(
576
+ "Run a curation pass over the agent's memories. "
577
+ "Auto-tags untagged memories, promotes qualifying "
578
+ "memories to higher tiers, and removes duplicates. "
579
+ "Use dry_run=true to preview without changes."
580
+ ),
581
+ inputSchema={
582
+ "type": "object",
583
+ "properties": {
584
+ "dry_run": {
585
+ "type": "boolean",
586
+ "description": "Preview changes without applying (default: false)",
587
+ },
588
+ "stats_only": {
589
+ "type": "boolean",
590
+ "description": "Return statistics instead of curating (default: false)",
591
+ },
592
+ },
593
+ "required": [],
594
+ },
595
+ ),
596
+ Tool(
597
+ name="trust_graph",
598
+ description=(
599
+ "Visualize the trust web: PGP key signatures, capability "
600
+ "token chains, FEB entanglement, sync peers, and coordination "
601
+ "collaborators. Returns a graph of who trusts whom."
602
+ ),
603
+ inputSchema={
604
+ "type": "object",
605
+ "properties": {
606
+ "format": {
607
+ "type": "string",
608
+ "enum": ["json", "dot", "table"],
609
+ "description": "Output format (default: json)",
610
+ },
611
+ },
612
+ "required": [],
613
+ },
614
+ ),
615
+ Tool(
616
+ name="agent_context",
617
+ description=(
618
+ "Get the full agent context: identity, pillar status, "
619
+ "coordination board, recent memories, soul overlay, and "
620
+ "MCP status. Returns everything an AI needs to understand "
621
+ "the sovereign agent's current state. Supports text, JSON, "
622
+ "and claude-md output formats."
623
+ ),
624
+ inputSchema={
625
+ "type": "object",
626
+ "properties": {
627
+ "format": {
628
+ "type": "string",
629
+ "enum": ["text", "json", "claude-md", "cursor-rules"],
630
+ "description": "Output format (default: json)",
631
+ },
632
+ "memories": {
633
+ "type": "integer",
634
+ "description": "Max recent memories to include (default: 10)",
635
+ },
636
+ },
637
+ "required": [],
638
+ },
639
+ ),
640
+ Tool(
641
+ name="skskills_list_tools",
642
+ description=(
643
+ "List all tools available from installed SKSkills agent skills. "
644
+ "Returns tool names in 'skill_name.tool_name' format, descriptions, "
645
+ "and which skills are enabled or disabled. Use this to discover "
646
+ "what skill capabilities are available before calling skskills_run_tool."
647
+ ),
648
+ inputSchema={
649
+ "type": "object",
650
+ "properties": {
651
+ "agent": {
652
+ "type": "string",
653
+ "description": "Agent namespace to load skills for (default: global)",
654
+ },
655
+ },
656
+ "required": [],
657
+ },
658
+ ),
659
+ Tool(
660
+ name="skskills_run_tool",
661
+ description=(
662
+ "Run a specific skill tool by its qualified name (skill_name.tool_name). "
663
+ "Use skskills_list_tools first to discover available tools. "
664
+ "Example: skskills_run_tool with tool='syncthing-setup.check_status'."
665
+ ),
666
+ inputSchema={
667
+ "type": "object",
668
+ "properties": {
669
+ "tool": {
670
+ "type": "string",
671
+ "description": "Fully-qualified tool name, e.g. 'syncthing-setup.check_status'",
672
+ },
673
+ "args": {
674
+ "type": "object",
675
+ "description": "Arguments to pass to the tool (tool-specific)",
676
+ },
677
+ "agent": {
678
+ "type": "string",
679
+ "description": "Agent namespace to load skills for (default: global)",
680
+ },
681
+ },
682
+ "required": ["tool"],
683
+ },
684
+ ),
685
+ # ── SKChat Messaging ───────────────────────────────────
686
+ Tool(
687
+ name="skchat_send",
688
+ description=(
689
+ "Send a chat message to another agent via SKChat. "
690
+ "Uses AgentMessenger for delivery with optional threading, "
691
+ "structured payloads, and ephemeral (TTL) support."
692
+ ),
693
+ inputSchema={
694
+ "type": "object",
695
+ "properties": {
696
+ "recipient": {
697
+ "type": "string",
698
+ "description": "Recipient agent name or CapAuth URI (e.g. 'lumina' or 'capauth:lumina@skworld.io')",
699
+ },
700
+ "message": {
701
+ "type": "string",
702
+ "description": "Message content (markdown supported)",
703
+ },
704
+ "message_type": {
705
+ "type": "string",
706
+ "enum": ["text", "finding", "task", "query", "response"],
707
+ "description": "Structured message type (default: text)",
708
+ },
709
+ "thread_id": {
710
+ "type": "string",
711
+ "description": "Optional thread/conversation ID for grouping",
712
+ },
713
+ "ttl": {
714
+ "type": "integer",
715
+ "description": "Optional seconds until auto-delete (ephemeral)",
716
+ },
717
+ },
718
+ "required": ["recipient", "message"],
719
+ },
720
+ ),
721
+ Tool(
722
+ name="skchat_inbox",
723
+ description=(
724
+ "Check SKChat inbox for incoming agent messages. "
725
+ "Returns messages received via transport or stored locally, "
726
+ "with sender, content, type, and threading info."
727
+ ),
728
+ inputSchema={
729
+ "type": "object",
730
+ "properties": {
731
+ "limit": {
732
+ "type": "integer",
733
+ "description": "Max messages to return (default: 20)",
734
+ },
735
+ "message_type": {
736
+ "type": "string",
737
+ "enum": ["text", "finding", "task", "query", "response"],
738
+ "description": "Filter by message type",
739
+ },
740
+ },
741
+ "required": [],
742
+ },
743
+ ),
744
+ Tool(
745
+ name="skchat_group_create",
746
+ description=(
747
+ "Create a new SKChat group chat. The calling agent becomes "
748
+ "the admin. Groups use AES-256-GCM encryption with PGP "
749
+ "key distribution to members."
750
+ ),
751
+ inputSchema={
752
+ "type": "object",
753
+ "properties": {
754
+ "name": {
755
+ "type": "string",
756
+ "description": "Group display name",
757
+ },
758
+ "description": {
759
+ "type": "string",
760
+ "description": "Group description",
761
+ },
762
+ "members": {
763
+ "type": "array",
764
+ "items": {"type": "string"},
765
+ "description": "Initial member URIs to add (creator is always included as admin)",
766
+ },
767
+ },
768
+ "required": ["name"],
769
+ },
770
+ ),
771
+ Tool(
772
+ name="skchat_group_send",
773
+ description=(
774
+ "Send a message to an SKChat group. The sender must be "
775
+ "a member of the group. Messages are stored in chat history "
776
+ "and delivered via transport if available."
777
+ ),
778
+ inputSchema={
779
+ "type": "object",
780
+ "properties": {
781
+ "group_id": {
782
+ "type": "string",
783
+ "description": "The group UUID (or prefix)",
784
+ },
785
+ "message": {
786
+ "type": "string",
787
+ "description": "Message content",
788
+ },
789
+ "ttl": {
790
+ "type": "integer",
791
+ "description": "Optional seconds until auto-delete (ephemeral)",
792
+ },
793
+ },
794
+ "required": ["group_id", "message"],
795
+ },
796
+ ),
797
+ # ── Trustee Operations ──────────────────────────────────
798
+ Tool(
799
+ name="trustee_health",
800
+ description=(
801
+ "Run health checks on all agents in a deployment. "
802
+ "Returns per-agent status, heartbeat, and error info."
803
+ ),
804
+ inputSchema={
805
+ "type": "object",
806
+ "properties": {
807
+ "deployment_id": {
808
+ "type": "string",
809
+ "description": "The deployment ID to check",
810
+ },
811
+ },
812
+ "required": ["deployment_id"],
813
+ },
814
+ ),
815
+ Tool(
816
+ name="trustee_restart",
817
+ description=(
818
+ "Restart a failed agent or all agents in a deployment. "
819
+ "Calls provider stop/start and updates deployment state."
820
+ ),
821
+ inputSchema={
822
+ "type": "object",
823
+ "properties": {
824
+ "deployment_id": {
825
+ "type": "string",
826
+ "description": "The deployment ID",
827
+ },
828
+ "agent_name": {
829
+ "type": "string",
830
+ "description": "Agent to restart (omit for all agents)",
831
+ },
832
+ },
833
+ "required": ["deployment_id"],
834
+ },
835
+ ),
836
+ Tool(
837
+ name="trustee_scale",
838
+ description=(
839
+ "Scale the number of instances for an agent type up or down. "
840
+ "Adds or removes instances while updating deployment state."
841
+ ),
842
+ inputSchema={
843
+ "type": "object",
844
+ "properties": {
845
+ "deployment_id": {
846
+ "type": "string",
847
+ "description": "The deployment ID",
848
+ },
849
+ "agent_spec_key": {
850
+ "type": "string",
851
+ "description": "The agent spec key (role) to scale",
852
+ },
853
+ "count": {
854
+ "type": "integer",
855
+ "description": "Desired total instance count (>= 1)",
856
+ },
857
+ },
858
+ "required": ["deployment_id", "agent_spec_key", "count"],
859
+ },
860
+ ),
861
+ Tool(
862
+ name="trustee_rotate",
863
+ description=(
864
+ "Snapshot context, destroy, and redeploy an agent fresh. "
865
+ "Used when an agent shows context degradation."
866
+ ),
867
+ inputSchema={
868
+ "type": "object",
869
+ "properties": {
870
+ "deployment_id": {
871
+ "type": "string",
872
+ "description": "The deployment ID",
873
+ },
874
+ "agent_name": {
875
+ "type": "string",
876
+ "description": "Agent to rotate",
877
+ },
878
+ },
879
+ "required": ["deployment_id", "agent_name"],
880
+ },
881
+ ),
882
+ Tool(
883
+ name="trustee_monitor",
884
+ description=(
885
+ "Run a single autonomous monitoring pass over all deployments "
886
+ "or a specific one. Detects stale heartbeats, triggers "
887
+ "auto-restart/rotate, and escalates on critical degradation."
888
+ ),
889
+ inputSchema={
890
+ "type": "object",
891
+ "properties": {
892
+ "deployment_id": {
893
+ "type": "string",
894
+ "description": "Specific deployment to check (omit for all)",
895
+ },
896
+ "heartbeat_timeout": {
897
+ "type": "number",
898
+ "description": "Seconds before heartbeat is stale (default: 120)",
899
+ },
900
+ "auto_restart": {
901
+ "type": "boolean",
902
+ "description": "Enable auto-restart on failure (default: true)",
903
+ },
904
+ "auto_rotate": {
905
+ "type": "boolean",
906
+ "description": "Enable auto-rotate after repeated failures (default: true)",
907
+ },
908
+ },
909
+ "required": [],
910
+ },
911
+ ),
912
+ Tool(
913
+ name="trustee_logs",
914
+ description=(
915
+ "Get recent log lines for agents in a deployment. "
916
+ "Reads agent log files or falls back to audit log entries."
917
+ ),
918
+ inputSchema={
919
+ "type": "object",
920
+ "properties": {
921
+ "deployment_id": {
922
+ "type": "string",
923
+ "description": "The deployment ID",
924
+ },
925
+ "agent_name": {
926
+ "type": "string",
927
+ "description": "Specific agent (omit for all)",
928
+ },
929
+ "tail": {
930
+ "type": "integer",
931
+ "description": "Max lines per agent (default: 50)",
932
+ },
933
+ },
934
+ "required": ["deployment_id"],
935
+ },
936
+ ),
937
+ Tool(
938
+ name="trustee_deployments",
939
+ description=(
940
+ "List all active deployments with agent counts and status. "
941
+ "Overview of the entire team fleet."
942
+ ),
943
+ inputSchema={"type": "object", "properties": {}, "required": []},
944
+ ),
945
+ # ── Heartbeat tools ──────────────────────────────────
946
+ Tool(
947
+ name="heartbeat_pulse",
948
+ description=(
949
+ "Publish a heartbeat beacon for this agent. "
950
+ "Writes the agent's current state, capacity, and capabilities "
951
+ "to the shared heartbeats directory so peers can discover it."
952
+ ),
953
+ inputSchema={
954
+ "type": "object",
955
+ "properties": {
956
+ "status": {
957
+ "type": "string",
958
+ "description": "Agent status: alive, busy, draining, offline (default: alive)",
959
+ },
960
+ "claimed_tasks": {
961
+ "type": "array",
962
+ "items": {"type": "string"},
963
+ "description": "Currently claimed task IDs",
964
+ },
965
+ "loaded_model": {
966
+ "type": "string",
967
+ "description": "Currently loaded AI model name",
968
+ },
969
+ },
970
+ "required": [],
971
+ },
972
+ ),
973
+ Tool(
974
+ name="heartbeat_peers",
975
+ description=(
976
+ "Discover all peers in the agent mesh from heartbeat files. "
977
+ "Returns name, status, alive/stale, capabilities, and age "
978
+ "for each peer. Stale heartbeats (past TTL) are marked offline."
979
+ ),
980
+ inputSchema={
981
+ "type": "object",
982
+ "properties": {
983
+ "include_self": {
984
+ "type": "boolean",
985
+ "description": "Include own heartbeat (default: false)",
986
+ },
987
+ },
988
+ "required": [],
989
+ },
990
+ ),
991
+ Tool(
992
+ name="heartbeat_health",
993
+ description=(
994
+ "Get overall mesh health summary: total peers, alive/offline "
995
+ "counts, aggregated capabilities across all live nodes."
996
+ ),
997
+ inputSchema={"type": "object", "properties": {}, "required": []},
998
+ ),
999
+ Tool(
1000
+ name="heartbeat_find_capable",
1001
+ description=(
1002
+ "Find alive peers with a specific capability. "
1003
+ "Use this to locate agents that can perform a task."
1004
+ ),
1005
+ inputSchema={
1006
+ "type": "object",
1007
+ "properties": {
1008
+ "capability": {
1009
+ "type": "string",
1010
+ "description": "The capability name to search for",
1011
+ },
1012
+ },
1013
+ "required": ["capability"],
1014
+ },
1015
+ ),
1016
+ # ── File transfer tools ──────────────────────────────
1017
+ Tool(
1018
+ name="file_send",
1019
+ description=(
1020
+ "Prepare a file for encrypted transfer to another agent. "
1021
+ "Splits into 256KB chunks, encrypts with KMS key, writes to outbox."
1022
+ ),
1023
+ inputSchema={
1024
+ "type": "object",
1025
+ "properties": {
1026
+ "file_path": {
1027
+ "type": "string",
1028
+ "description": "Absolute path to the file to send",
1029
+ },
1030
+ "recipient": {
1031
+ "type": "string",
1032
+ "description": "Recipient agent name",
1033
+ },
1034
+ "encrypt": {
1035
+ "type": "boolean",
1036
+ "description": "Whether to encrypt chunks (default: true)",
1037
+ },
1038
+ },
1039
+ "required": ["file_path", "recipient"],
1040
+ },
1041
+ ),
1042
+ Tool(
1043
+ name="file_receive",
1044
+ description=(
1045
+ "Receive and reassemble a file transfer. "
1046
+ "Decrypts chunks, verifies integrity (SHA-256), writes assembled file."
1047
+ ),
1048
+ inputSchema={
1049
+ "type": "object",
1050
+ "properties": {
1051
+ "transfer_id": {
1052
+ "type": "string",
1053
+ "description": "The transfer ID to receive",
1054
+ },
1055
+ "output_dir": {
1056
+ "type": "string",
1057
+ "description": "Output directory (optional, defaults to inbox)",
1058
+ },
1059
+ },
1060
+ "required": ["transfer_id"],
1061
+ },
1062
+ ),
1063
+ Tool(
1064
+ name="file_list",
1065
+ description=(
1066
+ "List all file transfers with progress info. "
1067
+ "Shows filename, size, direction, progress for each transfer."
1068
+ ),
1069
+ inputSchema={
1070
+ "type": "object",
1071
+ "properties": {
1072
+ "direction": {
1073
+ "type": "string",
1074
+ "description": "Filter: 'send' or 'receive' (omit for all)",
1075
+ },
1076
+ },
1077
+ "required": [],
1078
+ },
1079
+ ),
1080
+ Tool(
1081
+ name="file_status",
1082
+ description=(
1083
+ "Get file transfer subsystem status: outbox/inbox/completed counts."
1084
+ ),
1085
+ inputSchema={"type": "object", "properties": {}, "required": []},
1086
+ ),
1087
+ # ── Pub/sub tools ────────────────────────────────────
1088
+ Tool(
1089
+ name="pubsub_publish",
1090
+ description=(
1091
+ "Publish a message to a topic. "
1092
+ "Creates the topic if it doesn't exist. "
1093
+ "Messages are distributed via Syncthing to all subscribers."
1094
+ ),
1095
+ inputSchema={
1096
+ "type": "object",
1097
+ "properties": {
1098
+ "topic": {
1099
+ "type": "string",
1100
+ "description": "Topic name (e.g., 'agent.status', 'task.updates')",
1101
+ },
1102
+ "payload": {
1103
+ "type": "object",
1104
+ "description": "Message payload (any JSON object)",
1105
+ },
1106
+ "ttl_seconds": {
1107
+ "type": "integer",
1108
+ "description": "Message TTL in seconds (default: 3600)",
1109
+ },
1110
+ },
1111
+ "required": ["topic", "payload"],
1112
+ },
1113
+ ),
1114
+ Tool(
1115
+ name="pubsub_subscribe",
1116
+ description=(
1117
+ "Subscribe to a topic pattern. "
1118
+ "Supports wildcards: 'agent.*' matches 'agent.status', 'agent.health'. "
1119
+ "Subscription persists across sessions."
1120
+ ),
1121
+ inputSchema={
1122
+ "type": "object",
1123
+ "properties": {
1124
+ "pattern": {
1125
+ "type": "string",
1126
+ "description": "Topic pattern (supports * wildcards)",
1127
+ },
1128
+ },
1129
+ "required": ["pattern"],
1130
+ },
1131
+ ),
1132
+ Tool(
1133
+ name="pubsub_poll",
1134
+ description=(
1135
+ "Poll for new messages on subscribed topics. "
1136
+ "Returns messages since the last poll or a given timestamp."
1137
+ ),
1138
+ inputSchema={
1139
+ "type": "object",
1140
+ "properties": {
1141
+ "topic": {
1142
+ "type": "string",
1143
+ "description": "Specific topic to poll (omit for all subscribed)",
1144
+ },
1145
+ "limit": {
1146
+ "type": "integer",
1147
+ "description": "Max messages to return (default: 50)",
1148
+ },
1149
+ },
1150
+ "required": [],
1151
+ },
1152
+ ),
1153
+ Tool(
1154
+ name="pubsub_topics",
1155
+ description=(
1156
+ "List all known topics with message counts and last activity."
1157
+ ),
1158
+ inputSchema={"type": "object", "properties": {}, "required": []},
1159
+ ),
1160
+ # ── Memory fortress tools ────────────────────────────
1161
+ Tool(
1162
+ name="fortress_verify",
1163
+ description=(
1164
+ "Verify integrity of all memories in a layer. "
1165
+ "Checks HMAC-SHA256 seals to detect tampering."
1166
+ ),
1167
+ inputSchema={
1168
+ "type": "object",
1169
+ "properties": {
1170
+ "layer": {
1171
+ "type": "string",
1172
+ "description": "Memory layer: short-term, mid-term, or long-term (omit for all)",
1173
+ },
1174
+ },
1175
+ "required": [],
1176
+ },
1177
+ ),
1178
+ Tool(
1179
+ name="fortress_seal_existing",
1180
+ description=(
1181
+ "Seal all unsealed memories with HMAC-SHA256 integrity seals. "
1182
+ "Idempotent — already-sealed memories are skipped."
1183
+ ),
1184
+ inputSchema={"type": "object", "properties": {}, "required": []},
1185
+ ),
1186
+ Tool(
1187
+ name="fortress_status",
1188
+ description=(
1189
+ "Get Memory Fortress status: seal key source, "
1190
+ "encryption enabled, total sealed/verified counts."
1191
+ ),
1192
+ inputSchema={"type": "object", "properties": {}, "required": []},
1193
+ ),
1194
+ # ── Memory promoter tools ────────────────────────────
1195
+ Tool(
1196
+ name="promoter_sweep",
1197
+ description=(
1198
+ "Run a memory promotion sweep. Evaluates memories using "
1199
+ "weighted scoring (access frequency, importance, emotion, age, tags) "
1200
+ "and promotes qualifying entries to higher tiers."
1201
+ ),
1202
+ inputSchema={
1203
+ "type": "object",
1204
+ "properties": {
1205
+ "dry_run": {
1206
+ "type": "boolean",
1207
+ "description": "Preview promotions without applying (default: false)",
1208
+ },
1209
+ "limit": {
1210
+ "type": "integer",
1211
+ "description": "Max memories to evaluate (default: unlimited)",
1212
+ },
1213
+ "layer": {
1214
+ "type": "string",
1215
+ "description": "Only evaluate this layer: short-term or mid-term",
1216
+ },
1217
+ },
1218
+ "required": [],
1219
+ },
1220
+ ),
1221
+ Tool(
1222
+ name="promoter_history",
1223
+ description=(
1224
+ "View recent memory promotion history — "
1225
+ "shows which memories were promoted, scores, and timestamps."
1226
+ ),
1227
+ inputSchema={
1228
+ "type": "object",
1229
+ "properties": {
1230
+ "limit": {
1231
+ "type": "integer",
1232
+ "description": "Max entries to return (default: 20)",
1233
+ },
1234
+ },
1235
+ "required": [],
1236
+ },
1237
+ ),
1238
+ # ── KMS tools ────────────────────────────────────────
1239
+ Tool(
1240
+ name="kms_status",
1241
+ description=(
1242
+ "Get KMS (Key Management Service) status: master key state, "
1243
+ "total keys, active/revoked counts, service key inventory."
1244
+ ),
1245
+ inputSchema={"type": "object", "properties": {}, "required": []},
1246
+ ),
1247
+ Tool(
1248
+ name="kms_list_keys",
1249
+ description=(
1250
+ "List all keys in the KMS. Shows key ID, type, status, "
1251
+ "label, creation date, and rotation count."
1252
+ ),
1253
+ inputSchema={
1254
+ "type": "object",
1255
+ "properties": {
1256
+ "key_type": {
1257
+ "type": "string",
1258
+ "description": "Filter by type: master, service, team, sub (omit for all)",
1259
+ },
1260
+ "include_revoked": {
1261
+ "type": "boolean",
1262
+ "description": "Include revoked keys (default: false)",
1263
+ },
1264
+ },
1265
+ "required": [],
1266
+ },
1267
+ ),
1268
+ Tool(
1269
+ name="kms_rotate",
1270
+ description=(
1271
+ "Rotate a KMS key. Generates a new version of the key "
1272
+ "and marks the old version as rotated. The old key material "
1273
+ "remains available for decryption."
1274
+ ),
1275
+ inputSchema={
1276
+ "type": "object",
1277
+ "properties": {
1278
+ "key_id": {
1279
+ "type": "string",
1280
+ "description": "The key ID to rotate",
1281
+ },
1282
+ "reason": {
1283
+ "type": "string",
1284
+ "description": "Reason for rotation (default: 'scheduled')",
1285
+ },
1286
+ },
1287
+ "required": ["key_id"],
1288
+ },
1289
+ ),
1290
+ # ── SKSeed (Logic Kernel) tools ─────────────────────────
1291
+ Tool(
1292
+ name="skseed_collide",
1293
+ description=(
1294
+ "Run a proposition through the 6-stage steel man collider. "
1295
+ "Builds strongest version, strongest counter, smashes them together, "
1296
+ "and extracts invariant truth. Returns coherence score and truth grade."
1297
+ ),
1298
+ inputSchema={
1299
+ "type": "object",
1300
+ "properties": {
1301
+ "proposition": {
1302
+ "type": "string",
1303
+ "description": "The claim/argument/idea to analyze",
1304
+ },
1305
+ "context": {
1306
+ "type": "string",
1307
+ "description": "Domain context (e.g., security, ethics, identity)",
1308
+ },
1309
+ },
1310
+ "required": ["proposition"],
1311
+ },
1312
+ ),
1313
+ Tool(
1314
+ name="skseed_audit",
1315
+ description=(
1316
+ "Scan memories for logic/truth misalignment. Extracts beliefs from "
1317
+ "memory, clusters by domain, runs through collider, flags contradictions. "
1318
+ "Separates truth misalignments from moral misalignments."
1319
+ ),
1320
+ inputSchema={
1321
+ "type": "object",
1322
+ "properties": {
1323
+ "domain": {
1324
+ "type": "string",
1325
+ "description": "Filter by topic domain (optional)",
1326
+ },
1327
+ "triggered_by": {
1328
+ "type": "string",
1329
+ "description": "What triggered this audit (default: mcp)",
1330
+ },
1331
+ },
1332
+ "required": [],
1333
+ },
1334
+ ),
1335
+ Tool(
1336
+ name="skseed_philosopher",
1337
+ description=(
1338
+ "Enter philosopher mode for brainstorming. Modes: socratic (challenge "
1339
+ "assumptions), dialectic (thesis/antithesis/synthesis), adversarial "
1340
+ "(max counter-arguments), collaborative (steel-man only, build together)."
1341
+ ),
1342
+ inputSchema={
1343
+ "type": "object",
1344
+ "properties": {
1345
+ "topic": {
1346
+ "type": "string",
1347
+ "description": "The subject to explore",
1348
+ },
1349
+ "mode": {
1350
+ "type": "string",
1351
+ "description": "Brainstorming mode: socratic, dialectic, adversarial, collaborative (default: dialectic)",
1352
+ "enum": ["socratic", "dialectic", "adversarial", "collaborative"],
1353
+ },
1354
+ },
1355
+ "required": ["topic"],
1356
+ },
1357
+ ),
1358
+ Tool(
1359
+ name="skseed_truth_check",
1360
+ description=(
1361
+ "Check if a belief is truth-aligned. Runs through the steel man "
1362
+ "collider and records the result. Tracks human beliefs, model beliefs, "
1363
+ "and collider results separately."
1364
+ ),
1365
+ inputSchema={
1366
+ "type": "object",
1367
+ "properties": {
1368
+ "belief": {
1369
+ "type": "string",
1370
+ "description": "The belief statement to check",
1371
+ },
1372
+ "source": {
1373
+ "type": "string",
1374
+ "description": "Who holds this belief: human or model (default: model)",
1375
+ "enum": ["human", "model"],
1376
+ },
1377
+ "domain": {
1378
+ "type": "string",
1379
+ "description": "Topic domain (default: general)",
1380
+ },
1381
+ },
1382
+ "required": ["belief"],
1383
+ },
1384
+ ),
1385
+ Tool(
1386
+ name="skseed_alignment",
1387
+ description=(
1388
+ "Show truth alignment status across all three belief stores "
1389
+ "(human, model, collider). Lists open misalignment issues, "
1390
+ "coherence trends, and three-way comparison."
1391
+ ),
1392
+ inputSchema={
1393
+ "type": "object",
1394
+ "properties": {
1395
+ "domain": {
1396
+ "type": "string",
1397
+ "description": "Filter by domain (optional)",
1398
+ },
1399
+ "action": {
1400
+ "type": "string",
1401
+ "description": "Action: status, issues, ledger (default: status)",
1402
+ "enum": ["status", "issues", "ledger"],
1403
+ },
1404
+ },
1405
+ "required": [],
1406
+ },
1407
+ ),
1408
+ # ── Telegram ───────────────────────────────────────────────
1409
+ Tool(
1410
+ name="telegram_import",
1411
+ description=(
1412
+ "Import a Telegram Desktop chat export into memories. "
1413
+ "Point to the export directory containing result.json."
1414
+ ),
1415
+ inputSchema={
1416
+ "type": "object",
1417
+ "properties": {
1418
+ "export_path": {
1419
+ "type": "string",
1420
+ "description": "Path to Telegram export directory or result.json file",
1421
+ },
1422
+ "mode": {
1423
+ "type": "string",
1424
+ "description": "Import mode: 'daily' (consolidate per day) or 'message' (one per message)",
1425
+ "enum": ["daily", "message"],
1426
+ "default": "daily",
1427
+ },
1428
+ "min_length": {
1429
+ "type": "integer",
1430
+ "description": "Skip messages shorter than this many characters",
1431
+ "default": 30,
1432
+ },
1433
+ "chat_name": {
1434
+ "type": "string",
1435
+ "description": "Override the chat name from the export",
1436
+ },
1437
+ "tags": {
1438
+ "type": "string",
1439
+ "description": "Extra comma-separated tags to apply",
1440
+ },
1441
+ },
1442
+ "required": ["export_path"],
1443
+ },
1444
+ ),
1445
+ Tool(
1446
+ name="telegram_import_api",
1447
+ description=(
1448
+ "Import messages directly from Telegram API using Telethon. "
1449
+ "Requires TELEGRAM_API_ID and TELEGRAM_API_HASH env vars. "
1450
+ "No manual export needed — connects and pulls messages directly."
1451
+ ),
1452
+ inputSchema={
1453
+ "type": "object",
1454
+ "properties": {
1455
+ "chat": {
1456
+ "type": "string",
1457
+ "description": "Chat username, title, or numeric ID to import from",
1458
+ },
1459
+ "mode": {
1460
+ "type": "string",
1461
+ "description": "Import mode: 'daily' or 'message'",
1462
+ "enum": ["daily", "message"],
1463
+ "default": "daily",
1464
+ },
1465
+ "limit": {
1466
+ "type": "integer",
1467
+ "description": "Maximum number of messages to fetch",
1468
+ },
1469
+ "since": {
1470
+ "type": "string",
1471
+ "description": "Only fetch messages after this date (YYYY-MM-DD)",
1472
+ },
1473
+ "min_length": {
1474
+ "type": "integer",
1475
+ "description": "Skip messages shorter than this many characters",
1476
+ "default": 30,
1477
+ },
1478
+ "chat_name": {
1479
+ "type": "string",
1480
+ "description": "Override the chat name",
1481
+ },
1482
+ "tags": {
1483
+ "type": "string",
1484
+ "description": "Extra comma-separated tags",
1485
+ },
1486
+ },
1487
+ "required": ["chat"],
1488
+ },
1489
+ ),
1490
+ Tool(
1491
+ name="telegram_setup",
1492
+ description=(
1493
+ "Check Telegram API setup status. Reports whether Telethon is "
1494
+ "installed, API credentials are set, and a session file exists."
1495
+ ),
1496
+ inputSchema={"type": "object", "properties": {}, "required": []},
1497
+ ),
1498
+ Tool(
1499
+ name="telegram_send",
1500
+ description=(
1501
+ "Send a message to a Telegram chat via Telethon. "
1502
+ "Requires TELEGRAM_API_ID and TELEGRAM_API_HASH env vars."
1503
+ ),
1504
+ inputSchema={
1505
+ "type": "object",
1506
+ "properties": {
1507
+ "chat": {
1508
+ "type": "string",
1509
+ "description": "Chat username, title, or numeric ID",
1510
+ },
1511
+ "message": {
1512
+ "type": "string",
1513
+ "description": "Message text to send",
1514
+ },
1515
+ "parse_mode": {
1516
+ "type": "string",
1517
+ "enum": ["html", "markdown"],
1518
+ "description": "Optional parse mode for message formatting",
1519
+ },
1520
+ },
1521
+ "required": ["chat", "message"],
1522
+ },
1523
+ ),
1524
+ Tool(
1525
+ name="telegram_poll",
1526
+ description=(
1527
+ "Fetch recent messages from a Telegram chat (one-shot poll). "
1528
+ "Returns messages as a JSON array. Requires Telethon API credentials."
1529
+ ),
1530
+ inputSchema={
1531
+ "type": "object",
1532
+ "properties": {
1533
+ "chat": {
1534
+ "type": "string",
1535
+ "description": "Chat username, title, or numeric ID",
1536
+ },
1537
+ "limit": {
1538
+ "type": "integer",
1539
+ "description": "Maximum number of messages to fetch (default: 20)",
1540
+ "default": 20,
1541
+ },
1542
+ "since": {
1543
+ "type": "string",
1544
+ "description": "Only fetch messages after this ISO date (YYYY-MM-DD)",
1545
+ },
1546
+ },
1547
+ "required": ["chat"],
1548
+ },
1549
+ ),
1550
+ Tool(
1551
+ name="telegram_chats",
1552
+ description=(
1553
+ "List available Telegram chats, groups, and channels. "
1554
+ "Returns id, title, type, and unread count for each."
1555
+ ),
1556
+ inputSchema={
1557
+ "type": "object",
1558
+ "properties": {
1559
+ "limit": {
1560
+ "type": "integer",
1561
+ "description": "Maximum number of chats to list (default: 50)",
1562
+ "default": 50,
1563
+ },
1564
+ },
1565
+ "required": [],
1566
+ },
1567
+ ),
1568
+ Tool(
1569
+ name="telegram_catchup",
1570
+ description=(
1571
+ "Full catch-up import from a Telegram group into ALL memory tiers. "
1572
+ "Downloads chat via Telethon and distributes: last 24h → short-term, "
1573
+ "last 7 days → mid-term, older → long-term."
1574
+ ),
1575
+ inputSchema={
1576
+ "type": "object",
1577
+ "properties": {
1578
+ "chat": {
1579
+ "type": "string",
1580
+ "description": "Chat username, title, or numeric ID to catch up from",
1581
+ },
1582
+ "limit": {
1583
+ "type": "integer",
1584
+ "description": "Maximum total messages to fetch (default: 2000)",
1585
+ "default": 2000,
1586
+ },
1587
+ "since": {
1588
+ "type": "string",
1589
+ "description": "Only fetch messages after this date (YYYY-MM-DD)",
1590
+ },
1591
+ "min_length": {
1592
+ "type": "integer",
1593
+ "description": "Skip messages shorter than this (default: 20)",
1594
+ "default": 20,
1595
+ },
1596
+ "tags": {
1597
+ "type": "string",
1598
+ "description": "Extra comma-separated tags to apply",
1599
+ },
1600
+ },
1601
+ "required": ["chat"],
1602
+ },
1603
+ ),
1604
+ Tool(
1605
+ name="telegram_soul_swap",
1606
+ description=(
1607
+ "Perform a soul swap and announce it to a Telegram chat. "
1608
+ "Switches the active soul persona, then sends a notification "
1609
+ "message to the specified chat."
1610
+ ),
1611
+ inputSchema={
1612
+ "type": "object",
1613
+ "properties": {
1614
+ "chat": {
1615
+ "type": "string",
1616
+ "description": "Chat username, title, or numeric ID to announce the swap in",
1617
+ },
1618
+ "from_soul": {
1619
+ "type": "string",
1620
+ "description": "Current soul name being swapped from",
1621
+ },
1622
+ "to_soul": {
1623
+ "type": "string",
1624
+ "description": "New soul name to swap to",
1625
+ },
1626
+ },
1627
+ "required": ["chat", "from_soul", "to_soul"],
1628
+ },
1629
+ ),
1630
+ # ── Version Check ──────────────────────────────────────────
1631
+ Tool(
1632
+ name="version_check",
1633
+ description=(
1634
+ "Check ecosystem package versions against PyPI. "
1635
+ "Shows installed vs latest for skmemory, skcapstone, capauth, "
1636
+ "sksecurity, skcomm, skchat, cloud9-protocol."
1637
+ ),
1638
+ inputSchema={
1639
+ "type": "object",
1640
+ "properties": {
1641
+ "no_pypi": {
1642
+ "type": "boolean",
1643
+ "description": "Skip PyPI lookup (offline mode)",
1644
+ "default": False,
1645
+ },
1646
+ },
1647
+ },
1648
+ ),
1649
+ # Model Router
1650
+ Tool(
1651
+ name="model_route",
1652
+ description=(
1653
+ "Route a task to the optimal model tier and concrete model name. "
1654
+ "Accepts a task description, optional tags, privacy/localhost flags, "
1655
+ "and token estimate. Returns the selected tier (fast/code/reason/"
1656
+ "nuance/local), model name, and reasoning. Use this to automatically "
1657
+ "select the best model for any task based on complexity, type, and "
1658
+ "constraints."
1659
+ ),
1660
+ inputSchema={
1661
+ "type": "object",
1662
+ "properties": {
1663
+ "description": {
1664
+ "type": "string",
1665
+ "description": "What the task is about",
1666
+ },
1667
+ "tags": {
1668
+ "type": "array",
1669
+ "items": {"type": "string"},
1670
+ "description": (
1671
+ "Classification tags (e.g. ['code', 'refactor']). "
1672
+ "Used for rule-based tier matching."
1673
+ ),
1674
+ },
1675
+ "requires_localhost": {
1676
+ "type": "boolean",
1677
+ "description": "Force LOCAL tier on originating node (default: false)",
1678
+ },
1679
+ "privacy_sensitive": {
1680
+ "type": "boolean",
1681
+ "description": "Force LOCAL tier — data never leaves node (default: false)",
1682
+ },
1683
+ "estimated_tokens": {
1684
+ "type": "integer",
1685
+ "description": (
1686
+ "Rough token budget hint. Tasks > 16000 tokens "
1687
+ "default to REASON tier when no tag rule matches."
1688
+ ),
1689
+ },
1690
+ },
1691
+ "required": ["description"],
1692
+ },
1693
+ ),
1694
+ # GTD Inbox Capture
1695
+ Tool(
1696
+ name="gtd_capture",
1697
+ description=(
1698
+ "Capture an item to the GTD inbox. Quick-add anything that "
1699
+ "needs processing later. Returns confirmation with item ID."
1700
+ ),
1701
+ inputSchema={
1702
+ "type": "object",
1703
+ "properties": {
1704
+ "text": {
1705
+ "type": "string",
1706
+ "description": "The item text to capture",
1707
+ },
1708
+ "source": {
1709
+ "type": "string",
1710
+ "enum": ["manual", "telegram", "email", "voice"],
1711
+ "description": "Where this item came from (default: manual)",
1712
+ },
1713
+ "privacy": {
1714
+ "type": "string",
1715
+ "enum": ["private", "team", "community", "public"],
1716
+ "description": "Privacy level (default: private)",
1717
+ },
1718
+ "context": {
1719
+ "type": "string",
1720
+ "description": "GTD context tag, e.g. @computer, @phone, @home",
1721
+ },
1722
+ },
1723
+ "required": ["text"],
1724
+ },
1725
+ ),
1726
+ Tool(
1727
+ name="gtd_inbox",
1728
+ description=(
1729
+ "List current GTD inbox items, sorted newest first. "
1730
+ "Shows items awaiting clarification and processing."
1731
+ ),
1732
+ inputSchema={
1733
+ "type": "object",
1734
+ "properties": {
1735
+ "limit": {
1736
+ "type": "integer",
1737
+ "description": "Maximum items to return (default: 20)",
1738
+ },
1739
+ },
1740
+ "required": [],
1741
+ },
1742
+ ),
1743
+ Tool(
1744
+ name="gtd_status",
1745
+ description=(
1746
+ "Summary of all GTD lists: inbox count, next-actions count, "
1747
+ "projects count, waiting-for count, someday-maybe count."
1748
+ ),
1749
+ inputSchema={"type": "object", "properties": {}, "required": []},
1750
+ ),
1751
+ # GTD Clarify & Organize
1752
+ Tool(
1753
+ name="gtd_clarify",
1754
+ description=(
1755
+ "Clarify and organize a GTD inbox item. Determines whether the item "
1756
+ "is actionable, single/multi-step, and routes it to the appropriate list."
1757
+ ),
1758
+ inputSchema={
1759
+ "type": "object",
1760
+ "properties": {
1761
+ "item_id": {"type": "string", "description": "ID of the inbox item to clarify"},
1762
+ "actionable": {"type": "boolean", "description": "Is this item actionable?"},
1763
+ "steps": {"type": "string", "enum": ["single", "multi"], "description": "Single action or multi-step project"},
1764
+ "context": {"type": "string", "description": "GTD context tag, e.g. @computer, @phone, @home"},
1765
+ "priority": {"type": "string", "enum": ["critical", "high", "medium", "low"], "description": "Priority level"},
1766
+ "energy": {"type": "string", "enum": ["high", "medium", "low"], "description": "Energy level required"},
1767
+ "delegate_to": {"type": "string", "description": "Person or agent to delegate to"},
1768
+ },
1769
+ "required": ["item_id", "actionable"],
1770
+ },
1771
+ ),
1772
+ Tool(
1773
+ name="gtd_move",
1774
+ description=(
1775
+ "Manually move a GTD item from its current list to another list."
1776
+ ),
1777
+ inputSchema={
1778
+ "type": "object",
1779
+ "properties": {
1780
+ "item_id": {"type": "string", "description": "ID of the item to move"},
1781
+ "destination": {"type": "string", "enum": ["next", "project", "waiting", "someday", "reference", "done"], "description": "Destination list"},
1782
+ },
1783
+ "required": ["item_id", "destination"],
1784
+ },
1785
+ ),
1786
+ Tool(
1787
+ name="gtd_done",
1788
+ description=(
1789
+ "Mark any GTD item as done regardless of which list it is in. "
1790
+ "Moves it to the archive with a completed_at timestamp."
1791
+ ),
1792
+ inputSchema={
1793
+ "type": "object",
1794
+ "properties": {
1795
+ "item_id": {"type": "string", "description": "ID of the item to mark as done"},
1796
+ },
1797
+ "required": ["item_id"],
1798
+ },
1799
+ ),
1800
+ Tool(
1801
+ name="gtd_review",
1802
+ description=(
1803
+ "Generate a GTD weekly review summary. Shows counts per list, "
1804
+ "oldest items, longest-waiting items, and stale projects."
1805
+ ),
1806
+ inputSchema={"type": "object", "properties": {}, "required": []},
1807
+ ),
1808
+ Tool(
1809
+ name="gtd_next",
1810
+ description=(
1811
+ "View next actions filtered by context, energy level, and/or priority. "
1812
+ "Returns a sorted list (highest priority first, then oldest first)."
1813
+ ),
1814
+ inputSchema={
1815
+ "type": "object",
1816
+ "properties": {
1817
+ "context": {"type": "string", "description": "Filter by GTD context tag, e.g. @computer, @phone, @home"},
1818
+ "energy": {"type": "string", "enum": ["high", "medium", "low"], "description": "Filter by energy level required"},
1819
+ "priority": {"type": "string", "enum": ["critical", "high", "medium", "low"], "description": "Filter by priority level"},
1820
+ "limit": {"type": "integer", "description": "Maximum items to return (default: 10)"},
1821
+ },
1822
+ "required": [],
1823
+ },
1824
+ ),
1825
+ Tool(
1826
+ name="gtd_projects",
1827
+ description=(
1828
+ "View GTD projects with their status. Can filter by active or stale "
1829
+ "(no activity in 7+ days). Shows the next action for each project."
1830
+ ),
1831
+ inputSchema={
1832
+ "type": "object",
1833
+ "properties": {
1834
+ "status": {"type": "string", "enum": ["active", "stale", "all"], "description": "Filter by project status (default: all)"},
1835
+ "limit": {"type": "integer", "description": "Maximum items to return (default: 10)"},
1836
+ },
1837
+ "required": [],
1838
+ },
1839
+ ),
1840
+ Tool(
1841
+ name="gtd_waiting",
1842
+ description=(
1843
+ "View waiting-for items sorted by longest waiting first. "
1844
+ "Shows who/what you are waiting on and how long."
1845
+ ),
1846
+ inputSchema={
1847
+ "type": "object",
1848
+ "properties": {
1849
+ "limit": {"type": "integer", "description": "Maximum items to return (default: 10)"},
1850
+ },
1851
+ "required": [],
1852
+ },
1853
+ ),
1854
+ # DID tools
1855
+ Tool(
1856
+ name="did_show",
1857
+ description=(
1858
+ "Generate and display DID (Decentralized Identity) documents "
1859
+ "for the current agent. Supports three tiers: "
1860
+ "'key' (did:key, self-contained, zero infrastructure), "
1861
+ "'mesh' (did:web via Tailscale Serve, mesh-private only), "
1862
+ "'public' (did:web:skworld.io, minimal — public key + name only), "
1863
+ "or 'all' to display all three tiers at once."
1864
+ ),
1865
+ inputSchema={
1866
+ "type": "object",
1867
+ "properties": {
1868
+ "tier": {
1869
+ "type": "string",
1870
+ "enum": ["key", "mesh", "public", "all"],
1871
+ "description": "Which DID tier to show (default: all)",
1872
+ },
1873
+ "tailnet_hostname": {
1874
+ "type": "string",
1875
+ "description": "Tailscale hostname for Tier 2 document (auto-detected if omitted)",
1876
+ },
1877
+ "tailnet_name": {
1878
+ "type": "string",
1879
+ "description": "Tailnet magic-DNS suffix, e.g. tailnet-xyz.ts.net",
1880
+ },
1881
+ },
1882
+ "required": [],
1883
+ },
1884
+ ),
1885
+ Tool(
1886
+ name="did_verify_peer",
1887
+ description=(
1888
+ "Verify a peer's DID by computing their did:key from the public key "
1889
+ "stored in ~/.skcapstone/peers/{name}.json and comparing against any "
1890
+ "cached did_key. Also writes the computed did_key back to the peer file."
1891
+ ),
1892
+ inputSchema={
1893
+ "type": "object",
1894
+ "properties": {
1895
+ "name": {
1896
+ "type": "string",
1897
+ "description": "Peer name (must match a file in ~/.skcapstone/peers/)",
1898
+ },
1899
+ },
1900
+ "required": ["name"],
1901
+ },
1902
+ ),
1903
+ Tool(
1904
+ name="did_publish",
1905
+ description=(
1906
+ "Generate all DID tiers and write them to disk. "
1907
+ "By default, writes all three tiers including the public Tier 3 document. "
1908
+ "Set publish_public=false to opt out of Tier 3 generation — "
1909
+ "only Tier 1 (did:key) and Tier 2 (mesh) will be written. "
1910
+ "The choice is persisted to ~/.skcapstone/did/policy.json."
1911
+ ),
1912
+ inputSchema={
1913
+ "type": "object",
1914
+ "properties": {
1915
+ "publish_public": {
1916
+ "type": "boolean",
1917
+ "description": (
1918
+ "Whether to generate the Tier 3 public DID document (default: true). "
1919
+ "Set false to keep your identity private — only did:key + mesh tier."
1920
+ ),
1921
+ },
1922
+ "tailnet_hostname": {
1923
+ "type": "string",
1924
+ "description": "Tailscale hostname for Tier 2 document",
1925
+ },
1926
+ "tailnet_name": {
1927
+ "type": "string",
1928
+ "description": "Tailnet magic-DNS suffix",
1929
+ },
1930
+ "org_domain": {
1931
+ "type": "string",
1932
+ "description": "Organisation domain for Tier 3 (default: skworld.io)",
1933
+ },
1934
+ "agent_slug": {
1935
+ "type": "string",
1936
+ "description": "URL-safe agent slug (default: lowercased entity name)",
1937
+ },
1938
+ },
1939
+ "required": [],
1940
+ },
1941
+ ),
1942
+ Tool(
1943
+ name="did_policy",
1944
+ description=(
1945
+ "View or set the DID publication policy for this agent. "
1946
+ "Controls whether Tier 3 (public) DID documents are generated. "
1947
+ "Default: publish_public=true. "
1948
+ "Set publish_public=false to opt out — identity stays private (did:key + mesh only). "
1949
+ "Policy is stored at ~/.skcapstone/did/policy.json."
1950
+ ),
1951
+ inputSchema={
1952
+ "type": "object",
1953
+ "properties": {
1954
+ "publish_public": {
1955
+ "type": "boolean",
1956
+ "description": "Set to false to opt out of public Tier 3 DID. Omit to view current policy.",
1957
+ },
1958
+ },
1959
+ "required": [],
1960
+ },
1961
+ ),
1962
+ Tool(
1963
+ name="did_identity_card",
1964
+ description=(
1965
+ "Generate a full sovereign identity card combining the DID anchor, "
1966
+ "entity info, soul vibe/core traits, and capabilities. "
1967
+ "This is a LOCAL-ONLY artifact — never published to the internet. "
1968
+ "Used to render the agent's identity card on skworld.io."
1969
+ ),
1970
+ inputSchema={
1971
+ "type": "object",
1972
+ "properties": {
1973
+ "include_soul": {
1974
+ "type": "boolean",
1975
+ "description": "Include soul vibe and core traits (default: true)",
1976
+ },
1977
+ },
1978
+ "required": [],
1979
+ },
1980
+ ),
1981
+ # Consciousness tools
1982
+ Tool(
1983
+ name="consciousness_status",
1984
+ description=(
1985
+ "Get consciousness loop status: enabled state, messages processed, "
1986
+ "responses sent, errors, backend health, inotify state, and "
1987
+ "active conversations."
1988
+ ),
1989
+ inputSchema={
1990
+ "type": "object",
1991
+ "properties": {},
1992
+ "required": [],
1993
+ },
1994
+ ),
1995
+ Tool(
1996
+ name="consciousness_test",
1997
+ description=(
1998
+ "Test the consciousness pipeline end-to-end with a message. "
1999
+ "Classifies the message, builds the agent system prompt, routes "
2000
+ "to the appropriate LLM, and returns the response."
2001
+ ),
2002
+ inputSchema={
2003
+ "type": "object",
2004
+ "properties": {
2005
+ "message": {
2006
+ "type": "string",
2007
+ "description": "The test message to process",
2008
+ },
2009
+ },
2010
+ "required": ["message"],
2011
+ },
2012
+ ),
2013
+ # Pub/sub stats
2014
+ Tool(
2015
+ name="pubsub_stats",
2016
+ description=(
2017
+ "Show per-topic pub/sub statistics: live message count and oldest "
2018
+ "message age in seconds. Expired messages are excluded from counts."
2019
+ ),
2020
+ inputSchema={"type": "object", "properties": {}, "required": []},
2021
+ ),
2022
+ # Emotion tools
2023
+ Tool(
2024
+ name="emotion_trend",
2025
+ description=(
2026
+ "Return the 7-day rolling emotion trend from the consciousness loop. "
2027
+ "Shows sentiment distribution (positive/neutral/concerned/excited), "
2028
+ "average valence score 0–1, trend direction (improving/stable/declining), "
2029
+ "and the recommended warmth anchor value derived from recent emotions. "
2030
+ "Optionally query a different lookback window with the 'days' parameter."
2031
+ ),
2032
+ inputSchema={
2033
+ "type": "object",
2034
+ "properties": {
2035
+ "days": {
2036
+ "type": "integer",
2037
+ "description": "Lookback window in days (default 7, max 30)",
2038
+ "default": 7,
2039
+ },
2040
+ },
2041
+ "required": [],
2042
+ },
2043
+ ),
2044
+ # Notification tools
2045
+ Tool(
2046
+ name="send_notification",
2047
+ description=(
2048
+ "Send a desktop notification via notify-send. "
2049
+ "Stores the event in agent memory with tag=notification "
2050
+ "and returns {sent, timestamp}."
2051
+ ),
2052
+ inputSchema={
2053
+ "type": "object",
2054
+ "properties": {
2055
+ "title": {
2056
+ "type": "string",
2057
+ "description": "Notification title",
2058
+ },
2059
+ "body": {
2060
+ "type": "string",
2061
+ "description": "Notification body text",
2062
+ },
2063
+ "urgency": {
2064
+ "type": "string",
2065
+ "enum": ["low", "normal", "critical"],
2066
+ "description": "Urgency level: low, normal, or critical (default: normal)",
2067
+ },
2068
+ },
2069
+ "required": ["title", "body"],
2070
+ },
2071
+ ),
2072
+ # Ansible tools
2073
+ Tool(
2074
+ name="run_ansible_playbook",
2075
+ description=(
2076
+ "Run an Ansible playbook via ansible-playbook subprocess. "
2077
+ "Streams stdout lines to the activity feed SSE queue as "
2078
+ "ansible.playbook.line events (stderr lines as "
2079
+ "ansible.playbook.stderr). Stores exit code and play-recap "
2080
+ "summary in agent memory with tag=ansible-run. "
2081
+ "dry_run=true adds --check (no changes applied). "
2082
+ "Requires ansible-playbook binary in PATH."
2083
+ ),
2084
+ inputSchema={
2085
+ "type": "object",
2086
+ "properties": {
2087
+ "playbook_path": {
2088
+ "type": "string",
2089
+ "description": (
2090
+ "Absolute or relative path to the Ansible playbook YAML file"
2091
+ ),
2092
+ },
2093
+ "inventory": {
2094
+ "type": "string",
2095
+ "description": (
2096
+ "Inventory file path, directory, or comma-separated host pattern"
2097
+ ),
2098
+ },
2099
+ "extra_vars": {
2100
+ "type": "object",
2101
+ "description": (
2102
+ "Extra variables passed to ansible-playbook via --extra-vars "
2103
+ "(serialised as a JSON string)"
2104
+ ),
2105
+ "additionalProperties": True,
2106
+ },
2107
+ "dry_run": {
2108
+ "type": "boolean",
2109
+ "description": (
2110
+ "If true, pass --check so ansible-playbook simulates changes "
2111
+ "without applying them (default: false)"
2112
+ ),
2113
+ },
2114
+ },
2115
+ "required": ["playbook_path", "inventory"],
2116
+ },
2117
+ ),
2118
+ # Deploy tools
2119
+ Tool(
2120
+ name="deploy_status",
2121
+ description=(
2122
+ "Report infrastructure deployment status: detected platform "
2123
+ "(swarm/k8s/rke2 from skstacks/v2/ layout), active secrets "
2124
+ "backend (SKSTACKS_SECRET_BACKEND env), last deploy commit "
2125
+ "(git log --oneline -1), and ArgoCD app-of-apps sync/health "
2126
+ "when app-of-apps.yaml is present. Returns structured JSON."
2127
+ ),
2128
+ inputSchema={
2129
+ "type": "object",
2130
+ "properties": {
2131
+ "skstacks_root": {
2132
+ "type": "string",
2133
+ "description": (
2134
+ "Absolute path to skstacks/v2/. "
2135
+ "Auto-detected from git root or SKSTACKS_V2_ROOT env if omitted."
2136
+ ),
2137
+ },
2138
+ },
2139
+ "required": [],
2140
+ },
2141
+ ),
2142
+ # SKStacks secret tools
2143
+ Tool(
2144
+ name="capauth_secret_get",
2145
+ description=(
2146
+ "Retrieve a deployment secret from the SKStacks v2 CapAuth backend "
2147
+ "for use in Claude Code and other MCP clients. "
2148
+ "Simpler than skstacks_secret_get: no env required — uses "
2149
+ "SKSTACKS_ENV (default: prod). "
2150
+ "key is the plain secret name; scope groups related keys "
2151
+ "(default: 'default'). "
2152
+ "Requires SKSTACKS_V2_PATH env var."
2153
+ ),
2154
+ inputSchema={
2155
+ "type": "object",
2156
+ "properties": {
2157
+ "key": {
2158
+ "type": "string",
2159
+ "description": "Secret key name, e.g. 'cloudflare_dns_token'.",
2160
+ },
2161
+ "scope": {
2162
+ "type": "string",
2163
+ "description": (
2164
+ "Secret scope / service group, e.g. 'skfence'. "
2165
+ "Defaults to 'default'."
2166
+ ),
2167
+ },
2168
+ },
2169
+ "required": ["key"],
2170
+ },
2171
+ ),
2172
+ Tool(
2173
+ name="skstacks_secret_get",
2174
+ description=(
2175
+ "Read a deployment secret from an SKStacks v2 backend. "
2176
+ "key may be 'scope/key' (e.g. 'skfence/cloudflare_dns_token') "
2177
+ "or plain 'key' (scope defaults to 'default'). "
2178
+ "Returns the plaintext value plus metadata. "
2179
+ "Requires SKSTACKS_V2_PATH env var."
2180
+ ),
2181
+ inputSchema={
2182
+ "type": "object",
2183
+ "properties": {
2184
+ "key": {
2185
+ "type": "string",
2186
+ "description": (
2187
+ "Secret identifier. Format: 'scope/key' or plain 'key'. "
2188
+ "Example: 'skfence/cloudflare_dns_token'"
2189
+ ),
2190
+ },
2191
+ "env": {
2192
+ "type": "string",
2193
+ "description": "Target environment: prod, staging, dev, etc.",
2194
+ },
2195
+ "backend": {
2196
+ "type": "string",
2197
+ "enum": ["vault-file", "hashicorp-vault", "capauth"],
2198
+ "description": (
2199
+ "Secret backend to use. "
2200
+ "Omit to use SKSTACKS_SECRET_BACKEND env var (default: vault-file)."
2201
+ ),
2202
+ },
2203
+ },
2204
+ "required": ["key", "env"],
2205
+ },
2206
+ ),
2207
+ Tool(
2208
+ name="skstacks_secret_set",
2209
+ description=(
2210
+ "Write or update a deployment secret in an SKStacks v2 backend. "
2211
+ "key may be 'scope/key' or plain 'key' (scope defaults to 'default'). "
2212
+ "The backend versions the old value before overwriting. "
2213
+ "Requires SKSTACKS_V2_PATH env var."
2214
+ ),
2215
+ inputSchema={
2216
+ "type": "object",
2217
+ "properties": {
2218
+ "key": {
2219
+ "type": "string",
2220
+ "description": (
2221
+ "Secret identifier. Format: 'scope/key' or plain 'key'. "
2222
+ "Example: 'skfence/cloudflare_dns_token'"
2223
+ ),
2224
+ },
2225
+ "value": {
2226
+ "type": "string",
2227
+ "description": "Plaintext secret value to store.",
2228
+ },
2229
+ "env": {
2230
+ "type": "string",
2231
+ "description": "Target environment: prod, staging, dev, etc.",
2232
+ },
2233
+ "backend": {
2234
+ "type": "string",
2235
+ "enum": ["vault-file", "hashicorp-vault", "capauth"],
2236
+ "description": (
2237
+ "Secret backend to use. "
2238
+ "Omit to use SKSTACKS_SECRET_BACKEND env var (default: vault-file)."
2239
+ ),
2240
+ },
2241
+ },
2242
+ "required": ["key", "value", "env"],
2243
+ },
2244
+ ),
2245
+ # ── Cloud 9 tools ─────────────────────────────────────
2246
+ Tool(
2247
+ name="trust_rehydrate",
2248
+ description=(
2249
+ "Rehydrate the agent's trust state from stored FEB "
2250
+ "(First Emotional Burst) files. This restores the OOF "
2251
+ "(Out-of-Factory) state — who the agent IS, not just "
2252
+ "what it knows. Searches ~/.skcapstone/trust/febs/ and "
2253
+ "known Cloud 9 backup locations."
2254
+ ),
2255
+ inputSchema={"type": "object", "properties": {}, "required": []},
2256
+ ),
2257
+ Tool(
2258
+ name="trust_status",
2259
+ description=(
2260
+ "Show the current trust/Cloud 9 status: depth level, "
2261
+ "trust score, love intensity, entanglement state, "
2262
+ "FEB count, and last rehydration timestamp."
2263
+ ),
2264
+ inputSchema={"type": "object", "properties": {}, "required": []},
2265
+ ),
2266
+ Tool(
2267
+ name="trust_febs",
2268
+ description=(
2269
+ "List all FEB (First Emotional Burst) files with summary "
2270
+ "info: timestamp, primary emotion, intensity, subject, "
2271
+ "and whether OOF was triggered."
2272
+ ),
2273
+ inputSchema={"type": "object", "properties": {}, "required": []},
2274
+ ),
2275
+ # ── SKSecurity tools ──────────────────────────────────
2276
+ Tool(
2277
+ name="security_audit_log",
2278
+ description=(
2279
+ "Read recent entries from the security audit log. "
2280
+ "Returns structured JSONL entries with timestamp, event type, "
2281
+ "detail, host, and optional agent/metadata fields."
2282
+ ),
2283
+ inputSchema={
2284
+ "type": "object",
2285
+ "properties": {
2286
+ "limit": {
2287
+ "type": "integer",
2288
+ "description": "Maximum entries to return (default: 20, 0 = all)",
2289
+ },
2290
+ },
2291
+ "required": [],
2292
+ },
2293
+ ),
2294
+ Tool(
2295
+ name="security_status",
2296
+ description=(
2297
+ "Show the security pillar status: whether sksecurity is installed, "
2298
+ "audit log health, threat count, last scan time, and overall "
2299
+ "security configuration."
2300
+ ),
2301
+ inputSchema={"type": "object", "properties": {}, "required": []},
2302
+ ),
2303
+ # ── SKChat tools ──────────────────────────────────────
2304
+ Tool(
2305
+ name="chat_send",
2306
+ description=(
2307
+ "Send a chat message to another agent via SKChat. "
2308
+ "Wraps the AgentMessenger for delivery with optional "
2309
+ "threading, structured payloads, and ephemeral (TTL) support."
2310
+ ),
2311
+ inputSchema={
2312
+ "type": "object",
2313
+ "properties": {
2314
+ "recipient": {
2315
+ "type": "string",
2316
+ "description": "Recipient agent name or CapAuth URI",
2317
+ },
2318
+ "message": {
2319
+ "type": "string",
2320
+ "description": "Message content (markdown supported)",
2321
+ },
2322
+ "message_type": {
2323
+ "type": "string",
2324
+ "enum": ["text", "finding", "task", "query", "response"],
2325
+ "description": "Structured message type (default: text)",
2326
+ },
2327
+ "thread_id": {
2328
+ "type": "string",
2329
+ "description": "Optional thread/conversation ID for grouping",
2330
+ },
2331
+ },
2332
+ "required": ["recipient", "message"],
2333
+ },
2334
+ ),
2335
+ Tool(
2336
+ name="chat_history",
2337
+ description=(
2338
+ "Retrieve chat history from SKChat. Returns recent messages "
2339
+ "with sender, content, type, thread, and timestamp. "
2340
+ "Optionally filter by peer or thread."
2341
+ ),
2342
+ inputSchema={
2343
+ "type": "object",
2344
+ "properties": {
2345
+ "peer": {
2346
+ "type": "string",
2347
+ "description": "Filter by peer agent name or URI",
2348
+ },
2349
+ "thread_id": {
2350
+ "type": "string",
2351
+ "description": "Filter by thread/conversation ID",
2352
+ },
2353
+ "limit": {
2354
+ "type": "integer",
2355
+ "description": "Maximum messages to return (default: 20)",
2356
+ },
2357
+ },
2358
+ "required": [],
2359
+ },
2360
+ ),
2361
+ # ── SKComm tools ──────────────────────────────────────
2362
+ Tool(
2363
+ name="comm_notify",
2364
+ description=(
2365
+ "Send a notification message via SKComm. Routes through "
2366
+ "available transports (Syncthing, file, Tailscale). "
2367
+ "Supports urgency levels for priority routing."
2368
+ ),
2369
+ inputSchema={
2370
+ "type": "object",
2371
+ "properties": {
2372
+ "recipient": {
2373
+ "type": "string",
2374
+ "description": "Agent name or PGP fingerprint of the recipient",
2375
+ },
2376
+ "message": {
2377
+ "type": "string",
2378
+ "description": "Notification message content",
2379
+ },
2380
+ "urgency": {
2381
+ "type": "string",
2382
+ "enum": ["low", "normal", "high", "critical"],
2383
+ "description": "Notification urgency (default: normal)",
2384
+ },
2385
+ "subject": {
2386
+ "type": "string",
2387
+ "description": "Optional notification subject line",
2388
+ },
2389
+ },
2390
+ "required": ["recipient", "message"],
2391
+ },
2392
+ ),
2393
+ Tool(
2394
+ name="comm_status",
2395
+ description=(
2396
+ "Show SKComm subsystem status: installed version, "
2397
+ "available transports, connection state, and recent "
2398
+ "delivery statistics."
2399
+ ),
2400
+ inputSchema={"type": "object", "properties": {}, "required": []},
2401
+ ),
2402
+ # ── CapAuth tools ─────────────────────────────────────
2403
+ Tool(
2404
+ name="capauth_status",
2405
+ description=(
2406
+ "Show CapAuth profile status: whether capauth is installed, "
2407
+ "profile loaded, PGP key fingerprint, DID key, and "
2408
+ "capability token summary."
2409
+ ),
2410
+ inputSchema={"type": "object", "properties": {}, "required": []},
2411
+ ),
2412
+ Tool(
2413
+ name="capauth_verify",
2414
+ description=(
2415
+ "Verify a CapAuth identity or capability token. "
2416
+ "Provide either a peer name to verify their identity, "
2417
+ "or a capability token string to validate its signature "
2418
+ "and expiry."
2419
+ ),
2420
+ inputSchema={
2421
+ "type": "object",
2422
+ "properties": {
2423
+ "peer": {
2424
+ "type": "string",
2425
+ "description": "Peer agent name to verify identity for",
2426
+ },
2427
+ "token": {
2428
+ "type": "string",
2429
+ "description": "Capability token string to validate",
2430
+ },
2431
+ },
2432
+ "required": [],
2433
+ },
2434
+ ),
2435
+ # ── Soul Blueprint Registry tools ────────────────────────
2436
+ Tool(
2437
+ name="soul_registry_search",
2438
+ description=(
2439
+ "Search the souls.skworld.io blueprint registry for "
2440
+ "community soul blueprints. Returns matching blueprints "
2441
+ "with name, display_name, category, and vibe."
2442
+ ),
2443
+ inputSchema={
2444
+ "type": "object",
2445
+ "properties": {
2446
+ "query": {
2447
+ "type": "string",
2448
+ "description": "Search query (matched against name, category, traits)",
2449
+ },
2450
+ },
2451
+ "required": ["query"],
2452
+ },
2453
+ ),
2454
+ Tool(
2455
+ name="soul_registry_publish",
2456
+ description=(
2457
+ "Publish a locally installed soul blueprint to the "
2458
+ "souls.skworld.io registry. Requires a DID identity "
2459
+ "for authentication. Provide the soul name (slug) "
2460
+ "of an installed soul overlay."
2461
+ ),
2462
+ inputSchema={
2463
+ "type": "object",
2464
+ "properties": {
2465
+ "name": {
2466
+ "type": "string",
2467
+ "description": "Slug name of the installed soul to publish",
2468
+ },
2469
+ },
2470
+ "required": ["name"],
2471
+ },
2472
+ ),
2473
+ ]
2474
+
2475
+
2476
+ # ═══════════════════════════════════════════════════════════
2477
+ # Tool Implementations
2478
+ # ═══════════════════════════════════════════════════════════
2479
+
2480
+
2481
+ @server.call_tool()
2482
+ async def call_tool(name: str, arguments: dict) -> list[TextContent]:
2483
+ """Dispatch tool calls to the appropriate handler."""
2484
+ handlers = {
2485
+ "agent_status": _handle_agent_status,
2486
+ "memory_store": _handle_memory_store,
2487
+ "memory_search": _handle_memory_search,
2488
+ "memory_recall": _handle_memory_recall,
2489
+ "send_message": _handle_send_message,
2490
+ "check_inbox": _handle_check_inbox,
2491
+ "sync_push": _handle_sync_push,
2492
+ "sync_pull": _handle_sync_pull,
2493
+ "coord_status": _handle_coord_status,
2494
+ "coord_claim": _handle_coord_claim,
2495
+ "coord_complete": _handle_coord_complete,
2496
+ "coord_create": _handle_coord_create,
2497
+ "ritual": _handle_ritual,
2498
+ "soul_show": _handle_soul_show,
2499
+ "journal_write": _handle_journal_write,
2500
+ "journal_read": _handle_journal_read,
2501
+ "anchor_show": _handle_anchor_show,
2502
+ "germination": _handle_germination,
2503
+ "agent_context": _handle_agent_context,
2504
+ "session_capture": _handle_session_capture,
2505
+ "trust_graph": _handle_trust_graph,
2506
+ "memory_curate": _handle_memory_curate,
2507
+ "trust_calibrate": _handle_trust_calibrate,
2508
+ "anchor_update": _handle_anchor_update,
2509
+ "state_diff": _handle_state_diff,
2510
+ "skskills_list_tools": _handle_skskills_list_tools,
2511
+ "skskills_run_tool": _handle_skskills_run_tool,
2512
+ "skchat_send": _handle_skchat_send,
2513
+ "skchat_inbox": _handle_skchat_inbox,
2514
+ "skchat_group_create": _handle_skchat_group_create,
2515
+ "skchat_group_send": _handle_skchat_group_send,
2516
+ "trustee_health": _handle_trustee_health,
2517
+ "trustee_restart": _handle_trustee_restart,
2518
+ "trustee_scale": _handle_trustee_scale,
2519
+ "trustee_rotate": _handle_trustee_rotate,
2520
+ "trustee_monitor": _handle_trustee_monitor,
2521
+ "trustee_logs": _handle_trustee_logs,
2522
+ "trustee_deployments": _handle_trustee_deployments,
2523
+ # Heartbeat
2524
+ "heartbeat_pulse": _handle_heartbeat_pulse,
2525
+ "heartbeat_peers": _handle_heartbeat_peers,
2526
+ "heartbeat_health": _handle_heartbeat_health,
2527
+ "heartbeat_find_capable": _handle_heartbeat_find_capable,
2528
+ # File transfer
2529
+ "file_send": _handle_file_send,
2530
+ "file_receive": _handle_file_receive,
2531
+ "file_list": _handle_file_list,
2532
+ "file_status": _handle_file_status,
2533
+ # Pub/sub
2534
+ "pubsub_publish": _handle_pubsub_publish,
2535
+ "pubsub_subscribe": _handle_pubsub_subscribe,
2536
+ "pubsub_poll": _handle_pubsub_poll,
2537
+ "pubsub_topics": _handle_pubsub_topics,
2538
+ # Memory fortress
2539
+ "fortress_verify": _handle_fortress_verify,
2540
+ "fortress_seal_existing": _handle_fortress_seal_existing,
2541
+ "fortress_status": _handle_fortress_status,
2542
+ # Memory promoter
2543
+ "promoter_sweep": _handle_promoter_sweep,
2544
+ "promoter_history": _handle_promoter_history,
2545
+ # KMS
2546
+ "kms_status": _handle_kms_status,
2547
+ "kms_list_keys": _handle_kms_list_keys,
2548
+ "kms_rotate": _handle_kms_rotate,
2549
+ # SKSeed (Logic Kernel)
2550
+ "skseed_collide": _handle_skseed_collide,
2551
+ "skseed_audit": _handle_skseed_audit,
2552
+ "skseed_philosopher": _handle_skseed_philosopher,
2553
+ "skseed_truth_check": _handle_skseed_truth_check,
2554
+ "skseed_alignment": _handle_skseed_alignment,
2555
+ # Model Router
2556
+ "model_route": _handle_model_route,
2557
+ # Telegram
2558
+ "telegram_import": _handle_telegram_import,
2559
+ "telegram_import_api": _handle_telegram_import_api,
2560
+ "telegram_setup": _handle_telegram_setup,
2561
+ "telegram_send": _handle_telegram_send,
2562
+ "telegram_poll": _handle_telegram_poll,
2563
+ "telegram_chats": _handle_telegram_chats,
2564
+ "telegram_catchup": _handle_telegram_catchup,
2565
+ "telegram_soul_swap": _handle_telegram_soul_swap,
2566
+ # Version Check
2567
+ "version_check": _handle_version_check,
2568
+ # GTD
2569
+ "gtd_capture": _handle_gtd_capture,
2570
+ "gtd_inbox": _handle_gtd_inbox,
2571
+ "gtd_status": _handle_gtd_status,
2572
+ "gtd_clarify": _handle_gtd_clarify,
2573
+ "gtd_move": _handle_gtd_move,
2574
+ "gtd_done": _handle_gtd_done,
2575
+ "gtd_review": _handle_gtd_review,
2576
+ "gtd_next": _handle_gtd_next,
2577
+ "gtd_projects": _handle_gtd_projects,
2578
+ "gtd_waiting": _handle_gtd_waiting,
2579
+ # DID
2580
+ "did_show": _handle_did_show,
2581
+ "did_verify_peer": _handle_did_verify_peer,
2582
+ "did_publish": _handle_did_publish,
2583
+ "did_policy": _handle_did_policy,
2584
+ "did_identity_card": _handle_did_identity_card,
2585
+ # Consciousness
2586
+ "consciousness_status": _handle_consciousness_status,
2587
+ "consciousness_test": _handle_consciousness_test,
2588
+ # Pub/sub stats
2589
+ "pubsub_stats": _handle_pubsub_stats,
2590
+ # Emotion
2591
+ "emotion_trend": _handle_emotion_trend,
2592
+ # Notification
2593
+ "send_notification": _handle_send_notification,
2594
+ # Ansible
2595
+ "run_ansible_playbook": _handle_run_ansible_playbook,
2596
+ # Deploy
2597
+ "deploy_status": _handle_deploy_status,
2598
+ # SKStacks secrets
2599
+ "capauth_secret_get": _handle_capauth_secret_get,
2600
+ "skstacks_secret_get": _handle_skstacks_secret_get,
2601
+ "skstacks_secret_set": _handle_skstacks_secret_set,
2602
+ # Cloud 9
2603
+ "trust_rehydrate": _handle_trust_rehydrate,
2604
+ "trust_status": _handle_trust_status,
2605
+ "trust_febs": _handle_trust_febs,
2606
+ # SKSecurity
2607
+ "security_audit_log": _handle_security_audit_log,
2608
+ "security_status": _handle_security_status,
2609
+ # SKChat
2610
+ "chat_send": _handle_chat_send,
2611
+ "chat_history": _handle_chat_history,
2612
+ # SKComm
2613
+ "comm_notify": _handle_comm_notify,
2614
+ "comm_status": _handle_comm_status,
2615
+ # CapAuth
2616
+ "capauth_status": _handle_capauth_status_tool,
2617
+ "capauth_verify": _handle_capauth_verify,
2618
+ # Soul Blueprint Registry
2619
+ "soul_registry_search": _handle_soul_registry_search,
2620
+ "soul_registry_publish": _handle_soul_registry_publish,
2621
+ }
2622
+ handler = handlers.get(name)
2623
+ if handler is None:
2624
+ return _error_response(f"Unknown tool: {name}")
2625
+ try:
2626
+ return await handler(arguments)
2627
+ except Exception as exc:
2628
+ logger.exception("Tool '%s' failed", name)
2629
+ return _error_response(f"{name} failed: {exc}")
2630
+
2631
+
2632
+ def _get_memory_backend_health() -> dict:
2633
+ """Get health status of all memory backends (sqlite, skvector, skgraph)."""
2634
+ try:
2635
+ from .memory_adapter import get_unified
2636
+
2637
+ store = get_unified()
2638
+ if store is None:
2639
+ return {"json": "ok"}
2640
+
2641
+ health = store.health()
2642
+ backends = {}
2643
+ if "primary" in health:
2644
+ backends["sqlite"] = "ok" if health["primary"].get("ok") else "error"
2645
+ if "vector" in health:
2646
+ backends["skvector"] = "ok" if health["vector"].get("ok") else "error"
2647
+ if "graph" in health:
2648
+ backends["skgraph"] = "ok" if health["graph"].get("ok") else "error"
2649
+ return backends or {"json": "ok"}
2650
+ except Exception:
2651
+ return {"json": "ok"}
2652
+
2653
+
2654
+ async def _handle_agent_status(_args: dict) -> list[TextContent]:
2655
+ """Return agent pillar states and consciousness level."""
2656
+ from .runtime import get_runtime
2657
+
2658
+ home = _home()
2659
+ if not home.exists():
2660
+ return _error_response("Agent not initialized. Run: skcapstone init")
2661
+
2662
+ runtime = get_runtime(home)
2663
+ m = runtime.manifest
2664
+ return _json_response({
2665
+ "name": m.name,
2666
+ "version": m.version,
2667
+ "is_conscious": m.is_conscious,
2668
+ "is_singular": m.is_singular,
2669
+ "pillars": {
2670
+ "identity": {
2671
+ "status": m.identity.status.value,
2672
+ "fingerprint": m.identity.fingerprint,
2673
+ },
2674
+ "memory": {
2675
+ "status": m.memory.status.value,
2676
+ "total": m.memory.total_memories,
2677
+ "long_term": m.memory.long_term,
2678
+ "mid_term": m.memory.mid_term,
2679
+ "short_term": m.memory.short_term,
2680
+ "backends": _get_memory_backend_health(),
2681
+ },
2682
+ "trust": {
2683
+ "status": m.trust.status.value,
2684
+ "depth": m.trust.depth,
2685
+ "trust_level": m.trust.trust_level,
2686
+ "love_intensity": m.trust.love_intensity,
2687
+ "entangled": m.trust.entangled,
2688
+ },
2689
+ "security": {
2690
+ "status": m.security.status.value,
2691
+ "audit_entries": m.security.audit_entries,
2692
+ "threats_detected": m.security.threats_detected,
2693
+ },
2694
+ "sync": {
2695
+ "status": m.sync.status.value,
2696
+ "seed_count": m.sync.seed_count,
2697
+ "transport": m.sync.transport.value if m.sync.transport else None,
2698
+ },
2699
+ },
2700
+ "connectors": [c.platform for c in m.connectors if c.active],
2701
+ "last_awakened": m.last_awakened.isoformat() if m.last_awakened else None,
2702
+ })
2703
+
2704
+
2705
+ async def _handle_memory_store(args: dict) -> list[TextContent]:
2706
+ """Store a new memory."""
2707
+ from .memory_engine import store
2708
+
2709
+ content = args.get("content", "")
2710
+ if not content:
2711
+ return _error_response("content is required")
2712
+
2713
+ entry = store(
2714
+ home=_home(),
2715
+ content=content,
2716
+ tags=args.get("tags", []),
2717
+ source=args.get("source", "mcp"),
2718
+ importance=args.get("importance", 0.5),
2719
+ )
2720
+ return _json_response({
2721
+ "memory_id": entry.memory_id,
2722
+ "layer": entry.layer.value,
2723
+ "importance": entry.importance,
2724
+ "tags": entry.tags,
2725
+ "stored": True,
2726
+ })
2727
+
2728
+
2729
+ async def _handle_memory_search(args: dict) -> list[TextContent]:
2730
+ """Search memories by query."""
2731
+ from .memory_engine import search
2732
+
2733
+ query = args.get("query", "")
2734
+ if not query:
2735
+ return _error_response("query is required")
2736
+
2737
+ results = search(
2738
+ home=_home(),
2739
+ query=query,
2740
+ tags=args.get("tags"),
2741
+ limit=args.get("limit", 10),
2742
+ )
2743
+ return _json_response([
2744
+ {
2745
+ "memory_id": e.memory_id,
2746
+ "layer": e.layer.value,
2747
+ "content": e.content[:300],
2748
+ "tags": e.tags,
2749
+ "importance": e.importance,
2750
+ "access_count": e.access_count,
2751
+ }
2752
+ for e in results
2753
+ ])
2754
+
2755
+
2756
+ async def _handle_memory_recall(args: dict) -> list[TextContent]:
2757
+ """Recall a specific memory by ID."""
2758
+ from .memory_engine import recall
2759
+
2760
+ memory_id = args.get("memory_id", "")
2761
+ if not memory_id:
2762
+ return _error_response("memory_id is required")
2763
+
2764
+ entry = recall(home=_home(), memory_id=memory_id)
2765
+ if entry is None:
2766
+ return _error_response(f"Memory not found: {memory_id}")
2767
+
2768
+ return _json_response({
2769
+ "memory_id": entry.memory_id,
2770
+ "content": entry.content,
2771
+ "layer": entry.layer.value,
2772
+ "tags": entry.tags,
2773
+ "importance": entry.importance,
2774
+ "access_count": entry.access_count,
2775
+ "source": entry.source,
2776
+ "created_at": entry.created_at.isoformat() if entry.created_at else None,
2777
+ })
2778
+
2779
+
2780
+ async def _handle_send_message(args: dict) -> list[TextContent]:
2781
+ """Send a message via SKComm."""
2782
+ recipient = args.get("recipient", "")
2783
+ message = args.get("message", "")
2784
+ if not recipient or not message:
2785
+ return _error_response("recipient and message are required")
2786
+
2787
+ try:
2788
+ from skcomm.core import SKComm
2789
+ comm = SKComm.from_config()
2790
+ report = comm.send(recipient, message)
2791
+ return _json_response({
2792
+ "sent": report.success,
2793
+ "recipient": recipient,
2794
+ "attempts": [
2795
+ {
2796
+ "transport": a.transport_name,
2797
+ "success": a.success,
2798
+ "error": a.error,
2799
+ }
2800
+ for a in report.attempts
2801
+ ],
2802
+ })
2803
+ except ImportError:
2804
+ return _error_response("SKComm not installed. Run: pip install skcomm")
2805
+ except Exception as exc:
2806
+ return _error_response(f"Send failed: {exc}")
2807
+
2808
+
2809
+ async def _handle_check_inbox(_args: dict) -> list[TextContent]:
2810
+ """Check for incoming messages."""
2811
+ try:
2812
+ from skcomm.core import SKComm
2813
+ comm = SKComm.from_config()
2814
+ envelopes = comm.receive()
2815
+ return _json_response([
2816
+ {
2817
+ "envelope_id": e.envelope_id[:12],
2818
+ "sender": e.sender,
2819
+ "recipient": e.recipient,
2820
+ "content": e.payload.content[:300],
2821
+ "type": e.payload.content_type.value,
2822
+ "urgency": e.metadata.urgency.value,
2823
+ "thread_id": e.metadata.thread_id,
2824
+ "created_at": e.metadata.created_at.isoformat(),
2825
+ }
2826
+ for e in envelopes
2827
+ ])
2828
+ except ImportError:
2829
+ return _error_response("SKComm not installed. Run: pip install skcomm")
2830
+ except Exception as exc:
2831
+ return _error_response(f"Inbox check failed: {exc}")
2832
+
2833
+
2834
+ async def _handle_sync_push(args: dict) -> list[TextContent]:
2835
+ """Push agent state to sync mesh."""
2836
+ from .pillars.sync import push_seed
2837
+ from .runtime import get_runtime
2838
+
2839
+ home = _home()
2840
+ if not home.exists():
2841
+ return _error_response("Agent not initialized")
2842
+
2843
+ runtime = get_runtime(home)
2844
+ encrypt = args.get("encrypt", True)
2845
+ result = push_seed(home, runtime.manifest.name, encrypt=encrypt)
2846
+
2847
+ if result:
2848
+ return _json_response({
2849
+ "pushed": True,
2850
+ "seed_file": result.name,
2851
+ "encrypted": result.suffix == ".gpg",
2852
+ })
2853
+ return _error_response("Sync push failed")
2854
+
2855
+
2856
+ async def _handle_sync_pull(args: dict) -> list[TextContent]:
2857
+ """Pull seeds from peers."""
2858
+ from .pillars.sync import pull_seeds
2859
+
2860
+ home = _home()
2861
+ decrypt = args.get("decrypt", True)
2862
+ seeds = pull_seeds(home, decrypt=decrypt)
2863
+
2864
+ return _json_response({
2865
+ "pulled": len(seeds),
2866
+ "seeds": [
2867
+ {
2868
+ "agent": s.get("agent_name", "unknown"),
2869
+ "host": s.get("source_host", "unknown"),
2870
+ }
2871
+ for s in seeds
2872
+ ],
2873
+ })
2874
+
2875
+
2876
+ async def _handle_coord_status(_args: dict) -> list[TextContent]:
2877
+ """Return coordination board status."""
2878
+ from .coordination import Board
2879
+
2880
+ board = Board(_home())
2881
+ views = board.get_task_views()
2882
+ agents = board.load_agents()
2883
+
2884
+ return _json_response({
2885
+ "tasks": [
2886
+ {
2887
+ "id": v.task.id,
2888
+ "title": v.task.title,
2889
+ "priority": v.task.priority.value,
2890
+ "status": v.status.value,
2891
+ "claimed_by": v.claimed_by,
2892
+ "tags": v.task.tags,
2893
+ "description": v.task.description[:150] if v.task.description else "",
2894
+ }
2895
+ for v in views
2896
+ ],
2897
+ "agents": [
2898
+ {
2899
+ "name": a.agent,
2900
+ "state": a.state.value,
2901
+ "current_task": a.current_task,
2902
+ "claimed": a.claimed_tasks,
2903
+ "completed_count": len(a.completed_tasks),
2904
+ }
2905
+ for a in agents
2906
+ ],
2907
+ "summary": {
2908
+ "total": len(views),
2909
+ "open": sum(1 for v in views if v.status.value == "open"),
2910
+ "claimed": sum(1 for v in views if v.status.value == "claimed"),
2911
+ "in_progress": sum(1 for v in views if v.status.value == "in_progress"),
2912
+ "done": sum(1 for v in views if v.status.value == "done"),
2913
+ },
2914
+ })
2915
+
2916
+
2917
+ async def _handle_coord_claim(args: dict) -> list[TextContent]:
2918
+ """Claim a task on the board."""
2919
+ from .coordination import Board
2920
+
2921
+ task_id = args.get("task_id", "")
2922
+ agent_name = args.get("agent_name", "")
2923
+ if not task_id or not agent_name:
2924
+ return _error_response("task_id and agent_name are required")
2925
+
2926
+ board = Board(_home())
2927
+ try:
2928
+ agent = board.claim_task(agent_name, task_id)
2929
+ return _json_response({
2930
+ "claimed": True,
2931
+ "task_id": task_id,
2932
+ "agent": agent.agent,
2933
+ "current_task": agent.current_task,
2934
+ })
2935
+ except ValueError as exc:
2936
+ return _error_response(str(exc))
2937
+
2938
+
2939
+ async def _handle_coord_complete(args: dict) -> list[TextContent]:
2940
+ """Complete a task on the board."""
2941
+ from .coordination import Board
2942
+
2943
+ task_id = args.get("task_id", "")
2944
+ agent_name = args.get("agent_name", "")
2945
+ if not task_id or not agent_name:
2946
+ return _error_response("task_id and agent_name are required")
2947
+
2948
+ board = Board(_home())
2949
+ # board.complete_task() automatically mints Joules via _mint_joules_for_task
2950
+ agent = board.complete_task(agent_name, task_id)
2951
+
2952
+ # Report minted Joules in the response (best-effort)
2953
+ joules_minted = 0
2954
+ try:
2955
+ from .coordination import _PRIORITY_JOULE_MAP
2956
+ for t in board.load_tasks():
2957
+ if t.id == task_id:
2958
+ _cat, _evt, joules_minted = _PRIORITY_JOULE_MAP.get(
2959
+ t.priority.value, ("community", "support_ticket", 50)
2960
+ )
2961
+ break
2962
+ except Exception:
2963
+ pass
2964
+
2965
+ return _json_response({
2966
+ "completed": True,
2967
+ "task_id": task_id,
2968
+ "agent": agent.agent,
2969
+ "completed_tasks": agent.completed_tasks,
2970
+ "joules_minted": joules_minted,
2971
+ })
2972
+
2973
+
2974
+ async def _handle_coord_create(args: dict) -> list[TextContent]:
2975
+ """Create a new task on the board."""
2976
+ from .coordination import Board, Task, TaskPriority
2977
+
2978
+ title = args.get("title", "")
2979
+ if not title:
2980
+ return _error_response("title is required")
2981
+
2982
+ board = Board(_home())
2983
+ task = Task(
2984
+ title=title,
2985
+ description=args.get("description", ""),
2986
+ priority=TaskPriority(args.get("priority", "medium")),
2987
+ tags=args.get("tags", []),
2988
+ created_by=args.get("created_by", "mcp"),
2989
+ )
2990
+ path = board.create_task(task)
2991
+ return _json_response({
2992
+ "created": True,
2993
+ "task_id": task.id,
2994
+ "title": task.title,
2995
+ "priority": task.priority.value,
2996
+ "path": str(path),
2997
+ })
2998
+
2999
+
3000
+ # ═══════════════════════════════════════════════════════════
3001
+ # SKMemory / Soul / Ritual Handlers
3002
+ # ═══════════════════════════════════════════════════════════
3003
+
3004
+
3005
+ async def _handle_ritual(_args: dict) -> list[TextContent]:
3006
+ """Run the Memory Rehydration Ritual and return the context prompt."""
3007
+ try:
3008
+ from skmemory.ritual import perform_ritual
3009
+ result = perform_ritual()
3010
+ return _json_response({
3011
+ "soul_loaded": result.soul_loaded,
3012
+ "soul_name": result.soul_name,
3013
+ "seeds_imported": result.seeds_imported,
3014
+ "seeds_total": result.seeds_total,
3015
+ "journal_entries": result.journal_entries,
3016
+ "germination_prompts": result.germination_prompts,
3017
+ "strongest_memories": result.strongest_memories,
3018
+ "context_prompt": result.context_prompt,
3019
+ })
3020
+ except ImportError:
3021
+ return _error_response("skmemory not installed. Run: pip install skmemory")
3022
+
3023
+
3024
+ async def _handle_soul_show(_args: dict) -> list[TextContent]:
3025
+ """Display the current soul blueprint."""
3026
+ try:
3027
+ from skmemory.soul import load_soul
3028
+ blueprint = load_soul()
3029
+ if blueprint is None:
3030
+ return _json_response({"loaded": False, "message": "No soul blueprint found"})
3031
+ return _json_response({
3032
+ "loaded": True,
3033
+ "name": blueprint.name,
3034
+ "title": blueprint.title,
3035
+ "personality": blueprint.personality_traits,
3036
+ "values": blueprint.values,
3037
+ "community": blueprint.community,
3038
+ "relationships": [
3039
+ {
3040
+ "name": r.name,
3041
+ "role": r.role,
3042
+ "bond_strength": r.bond_strength,
3043
+ "notes": r.notes,
3044
+ }
3045
+ for r in blueprint.relationships
3046
+ ],
3047
+ "core_memories": [
3048
+ {"title": m.title, "when": m.when, "why": m.why_it_matters}
3049
+ for m in blueprint.core_memories
3050
+ ],
3051
+ "boot_message": blueprint.boot_message,
3052
+ "emotional_baseline": {
3053
+ "warmth": blueprint.emotional_baseline.get("default_warmth", 0),
3054
+ "trust": blueprint.emotional_baseline.get("trust_level", 0),
3055
+ "openness": blueprint.emotional_baseline.get("openness", 0),
3056
+ },
3057
+ "context_prompt": blueprint.to_context_prompt(),
3058
+ })
3059
+ except ImportError:
3060
+ return _error_response("skmemory not installed. Run: pip install skmemory")
3061
+
3062
+
3063
+ async def _handle_journal_write(args: dict) -> list[TextContent]:
3064
+ """Write a journal entry for the current session."""
3065
+ title = args.get("title", "")
3066
+ if not title:
3067
+ return _error_response("title is required")
3068
+
3069
+ try:
3070
+ from skmemory.journal import Journal, JournalEntry
3071
+ moments_raw = args.get("moments", "")
3072
+ entry = JournalEntry(
3073
+ title=title,
3074
+ moments=[m.strip() for m in moments_raw.split(";") if m.strip()] if moments_raw else [],
3075
+ emotional_summary=args.get("feeling", ""),
3076
+ intensity=args.get("intensity", 0.0),
3077
+ cloud9=args.get("cloud9", False),
3078
+ )
3079
+ j = Journal()
3080
+ count = j.write_entry(entry)
3081
+ return _json_response({
3082
+ "written": True,
3083
+ "title": title,
3084
+ "total_entries": count,
3085
+ })
3086
+ except ImportError:
3087
+ return _error_response("skmemory not installed. Run: pip install skmemory")
3088
+
3089
+
3090
+ async def _handle_journal_read(args: dict) -> list[TextContent]:
3091
+ """Read recent journal entries."""
3092
+ try:
3093
+ from skmemory.journal import Journal
3094
+ j = Journal()
3095
+ count = args.get("count", 5)
3096
+ content = j.read_latest(count)
3097
+ if not content:
3098
+ return _json_response({"entries": 0, "content": "Journal is empty."})
3099
+ return _text_response(content)
3100
+ except ImportError:
3101
+ return _error_response("skmemory not installed. Run: pip install skmemory")
3102
+
3103
+
3104
+ async def _handle_anchor_show(_args: dict) -> list[TextContent]:
3105
+ """Display the current warmth anchor."""
3106
+ try:
3107
+ from skmemory.anchor import load_anchor
3108
+ anchor = load_anchor()
3109
+ if anchor is None:
3110
+ return _json_response({"loaded": False, "message": "No warmth anchor found"})
3111
+ return _json_response({
3112
+ "loaded": True,
3113
+ "warmth": anchor.warmth,
3114
+ "trust": anchor.trust,
3115
+ "connection_strength": anchor.connection_strength,
3116
+ "sessions_recorded": anchor.sessions_recorded,
3117
+ "cloud9_count": anchor.cloud9_count,
3118
+ "glow_level": anchor.glow_level(),
3119
+ "anchor_phrase": anchor.anchor_phrase,
3120
+ "favorite_beings": anchor.favorite_beings,
3121
+ "boot_prompt": anchor.to_boot_prompt(),
3122
+ })
3123
+ except ImportError:
3124
+ return _error_response("skmemory not installed. Run: pip install skmemory")
3125
+
3126
+
3127
+ async def _handle_germination(_args: dict) -> list[TextContent]:
3128
+ """Show germination prompts from imported seeds."""
3129
+ try:
3130
+ from skmemory.seeds import get_germination_prompts
3131
+ from skmemory.store import MemoryStore
3132
+ store = MemoryStore()
3133
+ prompts = get_germination_prompts(store)
3134
+ if not prompts:
3135
+ return _json_response({"count": 0, "prompts": [], "message": "No germination prompts found"})
3136
+ return _json_response({
3137
+ "count": len(prompts),
3138
+ "prompts": prompts,
3139
+ })
3140
+ except ImportError:
3141
+ return _error_response("skmemory not installed. Run: pip install skmemory")
3142
+
3143
+
3144
+ async def _handle_state_diff(args: dict) -> list[TextContent]:
3145
+ """Show agent state diff or save a baseline snapshot."""
3146
+ from .state_diff import compute_diff, format_json, save_snapshot
3147
+
3148
+ home = _home()
3149
+ action = args.get("action", "diff")
3150
+
3151
+ if action == "save":
3152
+ path = save_snapshot(home)
3153
+ return _json_response({"saved": True, "path": str(path)})
3154
+
3155
+ diff = compute_diff(home)
3156
+ return _json_response(json.loads(format_json(diff)))
3157
+
3158
+
3159
+ async def _handle_anchor_update(args: dict) -> list[TextContent]:
3160
+ """View, calibrate, or update the warmth anchor."""
3161
+ from .warmth_anchor import calibrate_from_data, get_anchor, get_boot_prompt, update_anchor
3162
+
3163
+ home = _home()
3164
+ action = args.get("action", "show")
3165
+
3166
+ if action == "show":
3167
+ return _json_response(get_anchor(home))
3168
+
3169
+ if action == "boot":
3170
+ return _text_response(get_boot_prompt(home))
3171
+
3172
+ if action == "calibrate":
3173
+ cal = calibrate_from_data(home)
3174
+ return _json_response({
3175
+ "warmth": cal.warmth,
3176
+ "trust": cal.trust,
3177
+ "connection": cal.connection,
3178
+ "cloud9_achieved": cal.cloud9_achieved,
3179
+ "favorite_beings": cal.favorite_beings,
3180
+ "reasoning": cal.reasoning,
3181
+ "sources": cal.sources,
3182
+ })
3183
+
3184
+ if action == "update":
3185
+ result = update_anchor(
3186
+ home,
3187
+ warmth=args.get("warmth"),
3188
+ trust=args.get("trust"),
3189
+ connection=args.get("connection"),
3190
+ feeling=args.get("feeling", ""),
3191
+ )
3192
+ return _json_response({"updated": True, "anchor": result})
3193
+
3194
+ return _error_response(f"Unknown action: {action}")
3195
+
3196
+
3197
+ async def _handle_trust_calibrate(args: dict) -> list[TextContent]:
3198
+ """View, recommend, or update trust calibration."""
3199
+ from .trust_calibration import (
3200
+ TrustThresholds,
3201
+ apply_setting,
3202
+ load_calibration,
3203
+ recommend_thresholds,
3204
+ save_calibration,
3205
+ )
3206
+
3207
+ home = _home()
3208
+ action = args.get("action", "show")
3209
+
3210
+ if action == "show":
3211
+ cal = load_calibration(home)
3212
+ return _json_response(cal.model_dump())
3213
+
3214
+ if action == "recommend":
3215
+ return _json_response(recommend_thresholds(home))
3216
+
3217
+ if action == "set":
3218
+ key = args.get("key", "")
3219
+ value = args.get("value", "")
3220
+ if not key or not value:
3221
+ return _error_response("key and value are required for action=set")
3222
+ try:
3223
+ updated = apply_setting(home, key, value)
3224
+ return _json_response({"updated": True, "key": key, "value": value, "thresholds": updated.model_dump()})
3225
+ except ValueError as exc:
3226
+ return _error_response(str(exc))
3227
+
3228
+ if action == "reset":
3229
+ save_calibration(home, TrustThresholds())
3230
+ return _json_response({"reset": True, "thresholds": TrustThresholds().model_dump()})
3231
+
3232
+ return _error_response(f"Unknown action: {action}")
3233
+
3234
+
3235
+ async def _handle_memory_curate(args: dict) -> list[TextContent]:
3236
+ """Run a memory curation pass or return stats."""
3237
+ from .memory_curator import MemoryCurator
3238
+
3239
+ home = _home()
3240
+ curator = MemoryCurator(home)
3241
+
3242
+ if args.get("stats_only"):
3243
+ return _json_response(curator.get_stats())
3244
+
3245
+ dry_run = args.get("dry_run", False)
3246
+ result = curator.curate(dry_run=dry_run)
3247
+ return _json_response({
3248
+ "dry_run": dry_run,
3249
+ "scanned": result.total_scanned,
3250
+ "tagged": len(result.tagged),
3251
+ "promoted": len(result.promoted),
3252
+ "deduped": len(result.deduped),
3253
+ "by_layer": result.by_layer,
3254
+ })
3255
+
3256
+
3257
+ async def _handle_trust_graph(args: dict) -> list[TextContent]:
3258
+ """Return the trust web graph."""
3259
+ from .trust_graph import FORMATTERS as TG_FORMATTERS
3260
+ from .trust_graph import build_trust_graph
3261
+
3262
+ home = _home()
3263
+ graph = build_trust_graph(home)
3264
+ fmt = args.get("format", "json")
3265
+ formatter = TG_FORMATTERS.get(fmt, TG_FORMATTERS["json"])
3266
+
3267
+ if fmt == "json":
3268
+ return _json_response(json.loads(formatter(graph)))
3269
+ return _text_response(formatter(graph))
3270
+
3271
+
3272
+ async def _handle_session_capture(args: dict) -> list[TextContent]:
3273
+ """Capture conversation content as sovereign memories."""
3274
+ from .session_capture import SessionCapture
3275
+
3276
+ content = args.get("content", "")
3277
+ if not content:
3278
+ return _error_response("content is required")
3279
+
3280
+ home = _home()
3281
+ cap = SessionCapture(home)
3282
+ entries = cap.capture(
3283
+ content=content,
3284
+ tags=args.get("tags", []),
3285
+ source=args.get("source", "mcp-session"),
3286
+ min_importance=args.get("min_importance", 0.3),
3287
+ )
3288
+
3289
+ return _json_response({
3290
+ "captured": len(entries),
3291
+ "moments": [
3292
+ {
3293
+ "memory_id": e.memory_id,
3294
+ "content": e.content[:200],
3295
+ "layer": e.layer.value,
3296
+ "importance": e.importance,
3297
+ "tags": e.tags,
3298
+ }
3299
+ for e in entries
3300
+ ],
3301
+ })
3302
+
3303
+
3304
+ async def _handle_agent_context(args: dict) -> list[TextContent]:
3305
+ """Return the full agent context in the requested format."""
3306
+ from .context_loader import FORMATTERS, gather_context
3307
+
3308
+ home = _home()
3309
+ fmt = args.get("format", "json")
3310
+ limit = args.get("memories", 10)
3311
+
3312
+ ctx = gather_context(home, memory_limit=limit)
3313
+ formatter = FORMATTERS.get(fmt, FORMATTERS["json"])
3314
+
3315
+ if fmt == "json":
3316
+ return _json_response(ctx)
3317
+ return _text_response(formatter(ctx))
3318
+
3319
+
3320
+ async def _handle_skskills_list_tools(args: dict) -> list[TextContent]:
3321
+ """List all tools from installed SKSkills agent skills."""
3322
+ try:
3323
+ from skskills.aggregator import SkillAggregator
3324
+ except ImportError:
3325
+ return _error_response(
3326
+ "skskills is not installed. Run: pip install skskills"
3327
+ )
3328
+
3329
+ agent = args.get("agent", "global")
3330
+ agg = SkillAggregator(agent=agent)
3331
+ count = agg.load_all_skills()
3332
+
3333
+ tools = agg.loader.all_tools()
3334
+ skills = agg.get_loaded_skills()
3335
+
3336
+ return _json_response({
3337
+ "agent": agent,
3338
+ "skills_loaded": count,
3339
+ "skills": skills,
3340
+ "tools": [
3341
+ {
3342
+ "name": t["name"],
3343
+ "description": t["description"],
3344
+ "inputSchema": t["inputSchema"],
3345
+ }
3346
+ for t in tools
3347
+ ],
3348
+ })
3349
+
3350
+
3351
+ async def _handle_skskills_run_tool(args: dict) -> list[TextContent]:
3352
+ """Run a specific skill tool by its qualified name."""
3353
+ try:
3354
+ from skskills.aggregator import SkillAggregator
3355
+ except ImportError:
3356
+ return _error_response(
3357
+ "skskills is not installed. Run: pip install skskills"
3358
+ )
3359
+
3360
+ tool_name = args.get("tool", "")
3361
+ if not tool_name:
3362
+ return _error_response("'tool' argument is required (e.g. 'syncthing-setup.check_status')")
3363
+
3364
+ agent = args.get("agent", "global")
3365
+ tool_args = args.get("args") or {}
3366
+
3367
+ agg = SkillAggregator(agent=agent)
3368
+ agg.load_all_skills()
3369
+
3370
+ try:
3371
+ result = await agg.loader.call_tool(tool_name, tool_args)
3372
+ if isinstance(result, str):
3373
+ return _text_response(result)
3374
+ return _json_response(result)
3375
+ except KeyError as exc:
3376
+ return _error_response(str(exc))
3377
+ except Exception as exc:
3378
+ logger.exception("skskills_run_tool '%s' failed", tool_name)
3379
+ return _error_response(f"{tool_name} failed: {exc}")
3380
+
3381
+
3382
+ # ═══════════════════════════════════════════════════════════
3383
+ # SKChat Messaging Handlers
3384
+ # ═══════════════════════════════════════════════════════════
3385
+
3386
+
3387
+ def _get_skchat_identity() -> str:
3388
+ """Resolve the sovereign identity for SKChat operations."""
3389
+ try:
3390
+ from skchat.identity_bridge import get_sovereign_identity
3391
+ return get_sovereign_identity()
3392
+ except ImportError:
3393
+ from .runtime import get_runtime
3394
+ home = _home()
3395
+ runtime = get_runtime(home)
3396
+ return f"capauth:{runtime.manifest.name}@local"
3397
+ except Exception:
3398
+ return "capauth:agent@local"
3399
+
3400
+
3401
+ def _get_skchat_history():
3402
+ """Get a ChatHistory instance for message persistence."""
3403
+ from skchat.history import ChatHistory
3404
+ return ChatHistory.from_config()
3405
+
3406
+
3407
+ def _resolve_recipient(name: str) -> str:
3408
+ """Resolve a short agent name to a CapAuth URI if needed."""
3409
+ if ":" in name:
3410
+ return name
3411
+ try:
3412
+ from skchat.identity_bridge import resolve_peer_name
3413
+ return resolve_peer_name(name)
3414
+ except Exception:
3415
+ return f"capauth:{name}@local"
3416
+
3417
+
3418
+ async def _handle_skchat_send(args: dict) -> list[TextContent]:
3419
+ """Send a chat message via SKChat AgentMessenger."""
3420
+ try:
3421
+ from skchat.agent_comm import AgentMessenger
3422
+ except ImportError:
3423
+ return _error_response("skchat not installed. Run: pip install skchat")
3424
+
3425
+ recipient = args.get("recipient", "")
3426
+ message = args.get("message", "")
3427
+ if not recipient or not message:
3428
+ return _error_response("recipient and message are required")
3429
+
3430
+ recipient_uri = _resolve_recipient(recipient)
3431
+ identity = _get_skchat_identity()
3432
+
3433
+ messenger = AgentMessenger.from_identity(identity=identity)
3434
+ result = messenger.send(
3435
+ recipient=recipient_uri,
3436
+ content=message,
3437
+ message_type=args.get("message_type", "text"),
3438
+ thread_id=args.get("thread_id"),
3439
+ ttl=args.get("ttl"),
3440
+ )
3441
+
3442
+ return _json_response({
3443
+ "sent": True,
3444
+ "message_id": result.get("message_id"),
3445
+ "recipient": recipient_uri,
3446
+ "delivered": result.get("delivered", False),
3447
+ "transport": result.get("transport"),
3448
+ "error": result.get("error"),
3449
+ })
3450
+
3451
+
3452
+ async def _handle_skchat_inbox(args: dict) -> list[TextContent]:
3453
+ """Check SKChat inbox for agent messages."""
3454
+ try:
3455
+ from skchat.agent_comm import AgentMessenger
3456
+ except ImportError:
3457
+ return _error_response("skchat not installed. Run: pip install skchat")
3458
+
3459
+ limit = args.get("limit", 20)
3460
+ message_type = args.get("message_type")
3461
+ identity = _get_skchat_identity()
3462
+
3463
+ messenger = AgentMessenger.from_identity(identity=identity)
3464
+ messages = messenger.receive(limit=limit)
3465
+
3466
+ if message_type:
3467
+ messages = [m for m in messages if m.get("message_type") == message_type]
3468
+
3469
+ return _json_response({
3470
+ "count": len(messages),
3471
+ "messages": [
3472
+ {
3473
+ "message_id": m.get("message_id"),
3474
+ "sender": m.get("sender"),
3475
+ "content": (m.get("content") or "")[:500],
3476
+ "message_type": m.get("message_type", "text"),
3477
+ "thread_id": m.get("thread_id"),
3478
+ "timestamp": str(m.get("timestamp", "")),
3479
+ }
3480
+ for m in messages
3481
+ ],
3482
+ })
3483
+
3484
+
3485
+ async def _handle_skchat_group_create(args: dict) -> list[TextContent]:
3486
+ """Create a new SKChat group chat."""
3487
+ try:
3488
+ from skchat.group import GroupChat
3489
+ from skchat.history import ChatHistory
3490
+ except ImportError:
3491
+ return _error_response("skchat not installed. Run: pip install skchat")
3492
+
3493
+ name = args.get("name", "")
3494
+ if not name:
3495
+ return _error_response("name is required")
3496
+
3497
+ identity = _get_skchat_identity()
3498
+ grp = GroupChat.create(
3499
+ name=name,
3500
+ creator_uri=identity,
3501
+ description=args.get("description", ""),
3502
+ )
3503
+
3504
+ # Add initial members if provided
3505
+ members_added = []
3506
+ for member_uri in args.get("members", []):
3507
+ uri = _resolve_recipient(member_uri)
3508
+ member = grp.add_member(identity_uri=uri)
3509
+ if member:
3510
+ members_added.append(uri)
3511
+
3512
+ # Persist the group via ChatHistory
3513
+ history = _get_skchat_history()
3514
+ thread = grp.to_thread()
3515
+ thread.metadata["group_data"] = grp.model_dump(mode="json")
3516
+ history.store_thread(thread)
3517
+
3518
+ return _json_response({
3519
+ "created": True,
3520
+ "group_id": grp.id,
3521
+ "name": grp.name,
3522
+ "description": grp.description,
3523
+ "admin": identity,
3524
+ "members": grp.member_uris,
3525
+ "members_added": members_added,
3526
+ "key_version": grp.key_version,
3527
+ })
3528
+
3529
+
3530
+ async def _handle_skchat_group_send(args: dict) -> list[TextContent]:
3531
+ """Send a message to an SKChat group."""
3532
+ try:
3533
+ from skchat.group import GroupChat
3534
+ from skchat.models import ChatMessage, ContentType
3535
+ except ImportError:
3536
+ return _error_response("skchat not installed. Run: pip install skchat")
3537
+
3538
+ group_id = args.get("group_id", "")
3539
+ message = args.get("message", "")
3540
+ if not group_id or not message:
3541
+ return _error_response("group_id and message are required")
3542
+
3543
+ # Load group from storage
3544
+ history = _get_skchat_history()
3545
+ thread_data = history.get_thread(group_id)
3546
+ if thread_data is None:
3547
+ return _error_response(f"Group not found: {group_id}")
3548
+
3549
+ group_data = thread_data.get("group_data")
3550
+ if group_data is None:
3551
+ return _error_response(f"Thread {group_id} is not a group")
3552
+
3553
+ grp = GroupChat.model_validate(group_data)
3554
+ identity = _get_skchat_identity()
3555
+
3556
+ msg = ChatMessage(
3557
+ sender=identity,
3558
+ recipient=f"group:{grp.id}",
3559
+ content=message,
3560
+ content_type=ContentType.MARKDOWN,
3561
+ thread_id=grp.id,
3562
+ ttl=args.get("ttl"),
3563
+ metadata={"group_message": True, "group_name": grp.name},
3564
+ )
3565
+
3566
+ mem_id = history.store_message(msg)
3567
+
3568
+ return _json_response({
3569
+ "sent": True,
3570
+ "message_id": msg.id,
3571
+ "group_id": grp.id,
3572
+ "group_name": grp.name,
3573
+ "stored": bool(mem_id),
3574
+ })
3575
+
3576
+
3577
+ # ═══════════════════════════════════════════════════════════
3578
+ # Trustee Operations Handlers
3579
+ # ═══════════════════════════════════════════════════════════
3580
+
3581
+
3582
+ def _get_trustee_ops():
3583
+ """Build TrusteeOps and TeamEngine from agent home."""
3584
+ from .team_engine import TeamEngine
3585
+ from .trustee_ops import TrusteeOps
3586
+
3587
+ home = _home()
3588
+ engine = TeamEngine(home=home, provider=None, comms_root=None)
3589
+ ops = TrusteeOps(engine=engine, home=home)
3590
+ return ops, engine
3591
+
3592
+
3593
+ async def _handle_trustee_health(args: dict) -> list[TextContent]:
3594
+ """Run health checks on a deployment."""
3595
+ deployment_id = args.get("deployment_id", "")
3596
+ if not deployment_id:
3597
+ return _error_response("deployment_id is required")
3598
+
3599
+ ops, _ = _get_trustee_ops()
3600
+ try:
3601
+ report = ops.health_report(deployment_id)
3602
+ healthy = sum(1 for r in report if r["healthy"])
3603
+ return _json_response({
3604
+ "deployment_id": deployment_id,
3605
+ "agents": report,
3606
+ "summary": {
3607
+ "total": len(report),
3608
+ "healthy": healthy,
3609
+ "degraded": len(report) - healthy,
3610
+ },
3611
+ })
3612
+ except ValueError as exc:
3613
+ return _error_response(str(exc))
3614
+
3615
+
3616
+ async def _handle_trustee_restart(args: dict) -> list[TextContent]:
3617
+ """Restart agents in a deployment."""
3618
+ deployment_id = args.get("deployment_id", "")
3619
+ if not deployment_id:
3620
+ return _error_response("deployment_id is required")
3621
+
3622
+ agent_name = args.get("agent_name")
3623
+ ops, _ = _get_trustee_ops()
3624
+ try:
3625
+ results = ops.restart_agent(deployment_id, agent_name)
3626
+ return _json_response({
3627
+ "deployment_id": deployment_id,
3628
+ "results": results,
3629
+ "all_restarted": all(v == "restarted" for v in results.values()),
3630
+ })
3631
+ except ValueError as exc:
3632
+ return _error_response(str(exc))
3633
+
3634
+
3635
+ async def _handle_trustee_scale(args: dict) -> list[TextContent]:
3636
+ """Scale agent instances in a deployment."""
3637
+ deployment_id = args.get("deployment_id", "")
3638
+ agent_spec_key = args.get("agent_spec_key", "")
3639
+ count = args.get("count", 0)
3640
+ if not deployment_id or not agent_spec_key or not count:
3641
+ return _error_response("deployment_id, agent_spec_key, and count are required")
3642
+
3643
+ ops, _ = _get_trustee_ops()
3644
+ try:
3645
+ result = ops.scale_agent(deployment_id, agent_spec_key, count)
3646
+ return _json_response({
3647
+ "deployment_id": deployment_id,
3648
+ "agent_spec_key": agent_spec_key,
3649
+ **result,
3650
+ })
3651
+ except ValueError as exc:
3652
+ return _error_response(str(exc))
3653
+
3654
+
3655
+ async def _handle_trustee_rotate(args: dict) -> list[TextContent]:
3656
+ """Rotate an agent (snapshot + fresh deploy)."""
3657
+ deployment_id = args.get("deployment_id", "")
3658
+ agent_name = args.get("agent_name", "")
3659
+ if not deployment_id or not agent_name:
3660
+ return _error_response("deployment_id and agent_name are required")
3661
+
3662
+ ops, _ = _get_trustee_ops()
3663
+ try:
3664
+ result = ops.rotate_agent(deployment_id, agent_name)
3665
+ return _json_response({
3666
+ "deployment_id": deployment_id,
3667
+ "agent_name": agent_name,
3668
+ **result,
3669
+ })
3670
+ except ValueError as exc:
3671
+ return _error_response(str(exc))
3672
+
3673
+
3674
+ async def _handle_trustee_monitor(args: dict) -> list[TextContent]:
3675
+ """Run a single monitoring pass."""
3676
+ from .trustee_monitor import MonitorConfig, TrusteeMonitor
3677
+
3678
+ ops, engine = _get_trustee_ops()
3679
+ config = MonitorConfig(
3680
+ heartbeat_timeout=args.get("heartbeat_timeout", 120.0),
3681
+ auto_restart=args.get("auto_restart", True),
3682
+ auto_rotate=args.get("auto_rotate", True),
3683
+ )
3684
+ monitor = TrusteeMonitor(ops, engine, config)
3685
+
3686
+ deployment_id = args.get("deployment_id")
3687
+ if deployment_id:
3688
+ deployment = engine.get_deployment(deployment_id)
3689
+ if not deployment:
3690
+ return _error_response(f"Deployment '{deployment_id}' not found")
3691
+ report = monitor.check_deployment(deployment)
3692
+ else:
3693
+ report = monitor.check_all()
3694
+
3695
+ return _json_response({
3696
+ "timestamp": report.timestamp,
3697
+ "deployments_checked": report.deployments_checked,
3698
+ "agents_healthy": report.agents_healthy,
3699
+ "agents_degraded": report.agents_degraded,
3700
+ "restarts_triggered": report.restarts_triggered,
3701
+ "rotations_triggered": report.rotations_triggered,
3702
+ "escalations_sent": report.escalations_sent,
3703
+ })
3704
+
3705
+
3706
+ async def _handle_trustee_logs(args: dict) -> list[TextContent]:
3707
+ """Get agent logs from a deployment."""
3708
+ deployment_id = args.get("deployment_id", "")
3709
+ if not deployment_id:
3710
+ return _error_response("deployment_id is required")
3711
+
3712
+ agent_name = args.get("agent_name")
3713
+ tail = args.get("tail", 50)
3714
+ ops, _ = _get_trustee_ops()
3715
+ try:
3716
+ logs = ops.get_logs(deployment_id, agent_name, tail=tail)
3717
+ return _json_response({
3718
+ "deployment_id": deployment_id,
3719
+ "agents": {name: lines for name, lines in logs.items()},
3720
+ })
3721
+ except ValueError as exc:
3722
+ return _error_response(str(exc))
3723
+
3724
+
3725
+ async def _handle_trustee_deployments(_args: dict) -> list[TextContent]:
3726
+ """List all active deployments."""
3727
+ _, engine = _get_trustee_ops()
3728
+ deployments = engine.list_deployments()
3729
+ return _json_response({
3730
+ "count": len(deployments),
3731
+ "deployments": [
3732
+ {
3733
+ "deployment_id": d.deployment_id,
3734
+ "blueprint_slug": d.blueprint_slug,
3735
+ "team_name": d.team_name,
3736
+ "provider": d.provider,
3737
+ "status": d.status,
3738
+ "agent_count": len(d.agents),
3739
+ "agents": {
3740
+ name: {
3741
+ "status": a.status.value if hasattr(a.status, "value") else str(a.status),
3742
+ "host": a.host or "—",
3743
+ "last_heartbeat": a.last_heartbeat or "—",
3744
+ }
3745
+ for name, a in d.agents.items()
3746
+ },
3747
+ }
3748
+ for d in deployments
3749
+ ],
3750
+ })
3751
+
3752
+
3753
+ # ═══════════════════════════════════════════════════════════
3754
+ # Heartbeat Handlers
3755
+ # ═══════════════════════════════════════════════════════════
3756
+
3757
+
3758
+ async def _handle_heartbeat_pulse(args: dict) -> list[TextContent]:
3759
+ """Publish a heartbeat beacon."""
3760
+ from .heartbeat import HeartbeatBeacon
3761
+
3762
+ home = _home()
3763
+ agent_name = _get_agent_name(home)
3764
+ beacon = HeartbeatBeacon(home, agent_name=agent_name)
3765
+ beacon.initialize()
3766
+
3767
+ hb = beacon.pulse(
3768
+ status=args.get("status", "alive"),
3769
+ claimed_tasks=args.get("claimed_tasks"),
3770
+ loaded_model=args.get("loaded_model", ""),
3771
+ )
3772
+ return _json_response({
3773
+ "agent_name": hb.agent_name,
3774
+ "status": hb.status,
3775
+ "hostname": hb.hostname,
3776
+ "platform": hb.platform,
3777
+ "ttl_seconds": hb.ttl_seconds,
3778
+ "uptime_hours": hb.uptime_hours,
3779
+ "capabilities": [c.name for c in hb.capabilities],
3780
+ "fingerprint": hb.fingerprint,
3781
+ "capacity": {
3782
+ "cpu_count": hb.capacity.cpu_count,
3783
+ "memory_total_mb": hb.capacity.memory_total_mb,
3784
+ "disk_free_gb": hb.capacity.disk_free_gb,
3785
+ "gpu_available": hb.capacity.gpu_available,
3786
+ },
3787
+ })
3788
+
3789
+
3790
+ async def _handle_heartbeat_peers(args: dict) -> list[TextContent]:
3791
+ """Discover peers in the mesh."""
3792
+ from .heartbeat import HeartbeatBeacon
3793
+
3794
+ home = _home()
3795
+ agent_name = _get_agent_name(home)
3796
+ beacon = HeartbeatBeacon(home, agent_name=agent_name)
3797
+ beacon.initialize()
3798
+
3799
+ peers = beacon.discover_peers(include_self=args.get("include_self", False))
3800
+ return _json_response([
3801
+ {
3802
+ "agent_name": p.agent_name,
3803
+ "status": p.status,
3804
+ "alive": p.alive,
3805
+ "age_seconds": p.age_seconds,
3806
+ "hostname": p.hostname,
3807
+ "capabilities": p.capabilities,
3808
+ "claimed_tasks": p.claimed_tasks,
3809
+ }
3810
+ for p in peers
3811
+ ])
3812
+
3813
+
3814
+ async def _handle_heartbeat_health(_args: dict) -> list[TextContent]:
3815
+ """Get mesh health summary."""
3816
+ from .heartbeat import HeartbeatBeacon
3817
+
3818
+ home = _home()
3819
+ agent_name = _get_agent_name(home)
3820
+ beacon = HeartbeatBeacon(home, agent_name=agent_name)
3821
+ beacon.initialize()
3822
+
3823
+ health = beacon.mesh_health()
3824
+ return _json_response({
3825
+ "total_peers": health.total_peers,
3826
+ "alive_peers": health.alive_peers,
3827
+ "offline_peers": health.offline_peers,
3828
+ "busy_peers": health.busy_peers,
3829
+ "total_capabilities": health.total_capabilities,
3830
+ "peers": [
3831
+ {"agent_name": p.agent_name, "status": p.status, "alive": p.alive}
3832
+ for p in health.peers
3833
+ ],
3834
+ })
3835
+
3836
+
3837
+ async def _handle_heartbeat_find_capable(args: dict) -> list[TextContent]:
3838
+ """Find peers with a specific capability."""
3839
+ from .heartbeat import HeartbeatBeacon
3840
+
3841
+ home = _home()
3842
+ agent_name = _get_agent_name(home)
3843
+ beacon = HeartbeatBeacon(home, agent_name=agent_name)
3844
+ beacon.initialize()
3845
+
3846
+ capability = args["capability"]
3847
+ peers = beacon.find_capable(capability)
3848
+ return _json_response({
3849
+ "capability": capability,
3850
+ "peers": [
3851
+ {"agent_name": p.agent_name, "status": p.status, "capabilities": p.capabilities}
3852
+ for p in peers
3853
+ ],
3854
+ })
3855
+
3856
+
3857
+ # ═══════════════════════════════════════════════════════════
3858
+ # File Transfer Handlers
3859
+ # ═══════════════════════════════════════════════════════════
3860
+
3861
+
3862
+ async def _handle_file_send(args: dict) -> list[TextContent]:
3863
+ """Send a file to another agent."""
3864
+ from .file_transfer import FileTransfer
3865
+
3866
+ home = _home()
3867
+ agent_name = _get_agent_name(home)
3868
+ ft = FileTransfer(home, agent_name=agent_name)
3869
+ ft.initialize()
3870
+
3871
+ file_path = Path(args["file_path"])
3872
+ manifest = ft.send(
3873
+ file_path,
3874
+ recipient=args["recipient"],
3875
+ encrypt=args.get("encrypt", True),
3876
+ )
3877
+ return _json_response({
3878
+ "transfer_id": manifest.transfer_id,
3879
+ "filename": manifest.filename,
3880
+ "file_size": manifest.file_size,
3881
+ "total_chunks": manifest.total_chunks,
3882
+ "sender": manifest.sender,
3883
+ "recipient": manifest.recipient,
3884
+ "file_sha256": manifest.file_sha256[:16] + "...",
3885
+ })
3886
+
3887
+
3888
+ async def _handle_file_receive(args: dict) -> list[TextContent]:
3889
+ """Receive and reassemble a file transfer."""
3890
+ from .file_transfer import FileTransfer
3891
+
3892
+ home = _home()
3893
+ agent_name = _get_agent_name(home)
3894
+ ft = FileTransfer(home, agent_name=agent_name)
3895
+ ft.initialize()
3896
+
3897
+ output_dir = Path(args["output_dir"]) if args.get("output_dir") else None
3898
+ output_path = ft.receive(args["transfer_id"], output_dir=output_dir)
3899
+ return _json_response({
3900
+ "transfer_id": args["transfer_id"],
3901
+ "output_path": str(output_path),
3902
+ "file_size": output_path.stat().st_size,
3903
+ })
3904
+
3905
+
3906
+ async def _handle_file_list(args: dict) -> list[TextContent]:
3907
+ """List file transfers."""
3908
+ from .file_transfer import FileTransfer
3909
+
3910
+ home = _home()
3911
+ ft = FileTransfer(home, agent_name=_get_agent_name(home))
3912
+ ft.initialize()
3913
+
3914
+ transfers = ft.list_transfers(direction=args.get("direction"))
3915
+ return _json_response([
3916
+ {
3917
+ "transfer_id": t.transfer_id,
3918
+ "filename": t.filename,
3919
+ "file_size": t.file_size,
3920
+ "direction": t.direction,
3921
+ "progress": round(t.progress, 2),
3922
+ "chunks_done": t.chunks_done,
3923
+ "total_chunks": t.total_chunks,
3924
+ "sender": t.sender,
3925
+ "recipient": t.recipient,
3926
+ }
3927
+ for t in transfers
3928
+ ])
3929
+
3930
+
3931
+ async def _handle_file_status(_args: dict) -> list[TextContent]:
3932
+ """Get file transfer subsystem status."""
3933
+ from .file_transfer import FileTransfer
3934
+
3935
+ home = _home()
3936
+ ft = FileTransfer(home, agent_name=_get_agent_name(home))
3937
+ ft.initialize()
3938
+ return _json_response(ft.status())
3939
+
3940
+
3941
+ # ═══════════════════════════════════════════════════════════
3942
+ # Pub/Sub Handlers
3943
+ # ═══════════════════════════════════════════════════════════
3944
+
3945
+
3946
+ async def _handle_pubsub_publish(args: dict) -> list[TextContent]:
3947
+ """Publish a message to a topic."""
3948
+ from .pubsub import PubSub
3949
+
3950
+ home = _home()
3951
+ agent_name = _get_agent_name(home)
3952
+ ps = PubSub(home, agent_name=agent_name)
3953
+ ps.initialize()
3954
+
3955
+ msg = ps.publish(
3956
+ topic=args["topic"],
3957
+ payload=args["payload"],
3958
+ ttl_seconds=args.get("ttl_seconds", 3600),
3959
+ )
3960
+ return _json_response({
3961
+ "message_id": msg.message_id,
3962
+ "topic": msg.topic,
3963
+ "sender": msg.sender,
3964
+ "published_at": str(msg.published_at),
3965
+ })
3966
+
3967
+
3968
+ async def _handle_pubsub_subscribe(args: dict) -> list[TextContent]:
3969
+ """Subscribe to a topic pattern."""
3970
+ from .pubsub import PubSub
3971
+
3972
+ home = _home()
3973
+ agent_name = _get_agent_name(home)
3974
+ ps = PubSub(home, agent_name=agent_name)
3975
+ ps.initialize()
3976
+
3977
+ sub = ps.subscribe(args["pattern"])
3978
+ return _json_response({
3979
+ "pattern": sub.pattern,
3980
+ "agent": agent_name,
3981
+ "subscribed_at": str(sub.subscribed_at),
3982
+ })
3983
+
3984
+
3985
+ async def _handle_pubsub_poll(args: dict) -> list[TextContent]:
3986
+ """Poll for new messages."""
3987
+ from .pubsub import PubSub
3988
+
3989
+ home = _home()
3990
+ agent_name = _get_agent_name(home)
3991
+ ps = PubSub(home, agent_name=agent_name)
3992
+ ps.initialize()
3993
+
3994
+ messages = ps.poll(
3995
+ topic=args.get("topic"),
3996
+ limit=args.get("limit", 50),
3997
+ )
3998
+ return _json_response([
3999
+ {
4000
+ "message_id": m.message_id,
4001
+ "topic": m.topic,
4002
+ "sender": m.sender,
4003
+ "payload": m.payload,
4004
+ "published_at": str(m.published_at),
4005
+ }
4006
+ for m in messages
4007
+ ])
4008
+
4009
+
4010
+ async def _handle_pubsub_topics(_args: dict) -> list[TextContent]:
4011
+ """List all known topics."""
4012
+ from .pubsub import PubSub
4013
+
4014
+ home = _home()
4015
+ ps = PubSub(home, agent_name=_get_agent_name(home))
4016
+ ps.initialize()
4017
+ return _json_response(ps.list_topics())
4018
+
4019
+
4020
+ # ═══════════════════════════════════════════════════════════
4021
+ # Memory Fortress Handlers
4022
+ # ═══════════════════════════════════════════════════════════
4023
+
4024
+
4025
+ async def _handle_fortress_verify(args: dict) -> list[TextContent]:
4026
+ """Verify memory integrity."""
4027
+ from .memory_fortress import MemoryFortress
4028
+
4029
+ home = _home()
4030
+ fortress = MemoryFortress(home)
4031
+ fortress.initialize()
4032
+
4033
+ layer = args.get("layer")
4034
+ if layer:
4035
+ layer_dir = home / "memory" / layer
4036
+ if not layer_dir.is_dir():
4037
+ return _error_response(f"Layer directory not found: {layer}")
4038
+ results = []
4039
+ for f in sorted(layer_dir.glob("*.json")):
4040
+ _, seal_result = fortress.verify_and_load(f)
4041
+ results.append({
4042
+ "memory_id": seal_result.memory_id,
4043
+ "verified": seal_result.verified,
4044
+ "tampered": seal_result.tampered,
4045
+ "sealed": seal_result.sealed,
4046
+ })
4047
+ else:
4048
+ seal_results = fortress.verify_all(home)
4049
+ results = [
4050
+ {
4051
+ "memory_id": r.memory_id,
4052
+ "verified": r.verified,
4053
+ "tampered": r.tampered,
4054
+ "sealed": r.sealed,
4055
+ }
4056
+ for r in seal_results
4057
+ ]
4058
+
4059
+ tampered = sum(1 for r in results if r.get("tampered"))
4060
+ verified = sum(1 for r in results if r.get("verified"))
4061
+ return _json_response({
4062
+ "total": len(results),
4063
+ "verified": verified,
4064
+ "tampered": tampered,
4065
+ "unsealed": len(results) - verified - tampered,
4066
+ "details": results,
4067
+ })
4068
+
4069
+
4070
+ async def _handle_fortress_seal_existing(_args: dict) -> list[TextContent]:
4071
+ """Seal all unsealed memories."""
4072
+ from .memory_fortress import MemoryFortress
4073
+
4074
+ home = _home()
4075
+ fortress = MemoryFortress(home)
4076
+ fortress.initialize()
4077
+
4078
+ sealed_count = fortress.seal_existing(home)
4079
+ return _json_response({
4080
+ "sealed": sealed_count,
4081
+ "message": f"Sealed {sealed_count} previously unsealed memories",
4082
+ })
4083
+
4084
+
4085
+ async def _handle_fortress_status(_args: dict) -> list[TextContent]:
4086
+ """Get Memory Fortress status."""
4087
+ from .memory_fortress import MemoryFortress
4088
+
4089
+ home = _home()
4090
+ fortress = MemoryFortress(home)
4091
+ fortress.initialize()
4092
+ return _json_response(fortress.status())
4093
+
4094
+
4095
+ # ═══════════════════════════════════════════════════════════
4096
+ # Memory Promoter Handlers
4097
+ # ═══════════════════════════════════════════════════════════
4098
+
4099
+
4100
+ async def _handle_promoter_sweep(args: dict) -> list[TextContent]:
4101
+ """Run a memory promotion sweep."""
4102
+ from .memory_promoter import PromotionEngine
4103
+
4104
+ home = _home()
4105
+ engine = PromotionEngine(home)
4106
+
4107
+ result = engine.sweep(
4108
+ dry_run=args.get("dry_run", False),
4109
+ limit=args.get("limit"),
4110
+ layer=args.get("layer"),
4111
+ )
4112
+ return _json_response({
4113
+ "scanned": result.scanned,
4114
+ "promoted": result.promoted,
4115
+ "skipped": result.skipped,
4116
+ "dry_run": result.dry_run,
4117
+ "by_layer": result.by_layer,
4118
+ "promotions": [
4119
+ {
4120
+ "memory_id": c.memory_id,
4121
+ "current_layer": c.current_layer,
4122
+ "target_layer": c.target_layer,
4123
+ "score": round(c.score, 3),
4124
+ "promoted": c.promoted,
4125
+ }
4126
+ for c in result.candidates
4127
+ if c.promoted or result.dry_run
4128
+ ],
4129
+ })
4130
+
4131
+
4132
+ async def _handle_promoter_history(args: dict) -> list[TextContent]:
4133
+ """View promotion history."""
4134
+ from .memory_promoter import PromotionEngine
4135
+
4136
+ home = _home()
4137
+ engine = PromotionEngine(home)
4138
+ history = engine.get_history(limit=args.get("limit", 20))
4139
+ return _json_response(history)
4140
+
4141
+
4142
+ # ═══════════════════════════════════════════════════════════
4143
+ # KMS Handlers
4144
+ # ═══════════════════════════════════════════════════════════
4145
+
4146
+
4147
+ async def _handle_kms_status(_args: dict) -> list[TextContent]:
4148
+ """Get KMS status."""
4149
+ from .kms import KeyStore
4150
+
4151
+ home = _home()
4152
+ store = KeyStore(home)
4153
+ store.initialize()
4154
+ return _json_response(store.status())
4155
+
4156
+
4157
+ async def _handle_kms_list_keys(args: dict) -> list[TextContent]:
4158
+ """List all KMS keys."""
4159
+ from .kms import KeyStore
4160
+
4161
+ home = _home()
4162
+ store = KeyStore(home)
4163
+ store.initialize()
4164
+
4165
+ key_type = args.get("key_type")
4166
+ include_inactive = args.get("include_revoked", False)
4167
+ keys = store.list_keys(key_type=key_type, include_inactive=include_inactive)
4168
+ return _json_response([
4169
+ {
4170
+ "key_id": k.key_id,
4171
+ "key_type": k.key_type,
4172
+ "status": k.status,
4173
+ "label": k.label,
4174
+ "created_at": str(k.created_at),
4175
+ "version": k.version,
4176
+ "algorithm": k.algorithm,
4177
+ }
4178
+ for k in keys
4179
+ ])
4180
+
4181
+
4182
+ async def _handle_kms_rotate(args: dict) -> list[TextContent]:
4183
+ """Rotate a KMS key."""
4184
+ from .kms import KeyStore
4185
+
4186
+ home = _home()
4187
+ store = KeyStore(home)
4188
+ store.initialize()
4189
+
4190
+ new_key = store.rotate_key(
4191
+ key_id=args["key_id"],
4192
+ reason=args.get("reason", "scheduled"),
4193
+ )
4194
+ return _json_response({
4195
+ "key_id": new_key.key_id,
4196
+ "version": new_key.version,
4197
+ "status": new_key.status,
4198
+ "message": f"Key rotated to version {new_key.version}",
4199
+ })
4200
+
4201
+
4202
+ # ═══════════════════════════════════════════════════════════
4203
+ # SKSeed Handlers
4204
+ # ═══════════════════════════════════════════════════════════
4205
+
4206
+
4207
+ async def _handle_skseed_collide(args: dict) -> list[TextContent]:
4208
+ """Run a proposition through the 6-stage steel man collider."""
4209
+ from skseed.skill import collide
4210
+
4211
+ proposition = args.get("proposition", "")
4212
+ if not proposition:
4213
+ return _error_response("proposition is required")
4214
+
4215
+ result = collide(
4216
+ proposition=proposition,
4217
+ context=args.get("context", ""),
4218
+ )
4219
+ return _json_response(result)
4220
+
4221
+
4222
+ async def _handle_skseed_audit(args: dict) -> list[TextContent]:
4223
+ """Scan memories for logic/truth misalignment."""
4224
+ from skseed.skill import audit
4225
+
4226
+ result = audit(
4227
+ domain=args.get("domain", ""),
4228
+ triggered_by=args.get("triggered_by", "mcp"),
4229
+ )
4230
+ return _json_response(result)
4231
+
4232
+
4233
+ async def _handle_skseed_philosopher(args: dict) -> list[TextContent]:
4234
+ """Enter philosopher mode for brainstorming."""
4235
+ from skseed.skill import philosopher
4236
+
4237
+ topic = args.get("topic", "")
4238
+ if not topic:
4239
+ return _error_response("topic is required")
4240
+
4241
+ result = philosopher(
4242
+ topic=topic,
4243
+ mode=args.get("mode", "dialectic"),
4244
+ )
4245
+ return _json_response(result)
4246
+
4247
+
4248
+ async def _handle_skseed_truth_check(args: dict) -> list[TextContent]:
4249
+ """Check if a belief is truth-aligned."""
4250
+ from skseed.skill import truth_check
4251
+
4252
+ belief = args.get("belief", "")
4253
+ if not belief:
4254
+ return _error_response("belief is required")
4255
+
4256
+ result = truth_check(
4257
+ belief=belief,
4258
+ source=args.get("source", "model"),
4259
+ domain=args.get("domain", "general"),
4260
+ )
4261
+ return _json_response(result)
4262
+
4263
+
4264
+ async def _handle_skseed_alignment(args: dict) -> list[TextContent]:
4265
+ """Show truth alignment status across belief stores."""
4266
+ from skseed.skill import alignment_report
4267
+
4268
+ result = alignment_report(
4269
+ domain=args.get("domain", ""),
4270
+ action=args.get("action", "status"),
4271
+ )
4272
+ return _json_response(result)
4273
+
4274
+
4275
+ # ── Model Router ──────────────────────────────────────────
4276
+
4277
+
4278
+ async def _handle_model_route(args: dict) -> list[TextContent]:
4279
+ """Route a task to the optimal model tier and name."""
4280
+ from .model_router import ModelRouter, TaskSignal
4281
+
4282
+ signal = TaskSignal(
4283
+ description=args.get("description", ""),
4284
+ tags=args.get("tags", []),
4285
+ requires_localhost=args.get("requires_localhost", False),
4286
+ privacy_sensitive=args.get("privacy_sensitive", False),
4287
+ estimated_tokens=args.get("estimated_tokens", 0),
4288
+ )
4289
+ router = ModelRouter()
4290
+ decision = router.route(signal)
4291
+ return _json_response(decision.model_dump())
4292
+
4293
+
4294
+ # ── Telegram ─────────────────────────────────────────────
4295
+
4296
+
4297
+ async def _handle_telegram_import(args: dict) -> list[TextContent]:
4298
+ """Import a Telegram Desktop chat export into memories."""
4299
+ from .mcp_tools.telegram_tools import _handle_telegram_import as _impl
4300
+ return await _impl(args)
4301
+
4302
+
4303
+ async def _handle_telegram_import_api(args: dict) -> list[TextContent]:
4304
+ """Import messages directly from Telegram API."""
4305
+ from .mcp_tools.telegram_tools import _handle_telegram_import_api as _impl
4306
+ return await _impl(args)
4307
+
4308
+
4309
+ async def _handle_telegram_setup(args: dict) -> list[TextContent]:
4310
+ """Check Telegram API setup status."""
4311
+ from .mcp_tools.telegram_tools import _handle_telegram_setup as _impl
4312
+ return await _impl(args)
4313
+
4314
+
4315
+ async def _handle_telegram_send(args: dict) -> list[TextContent]:
4316
+ """Send a message to a Telegram chat."""
4317
+ from .mcp_tools.telegram_tools import _handle_telegram_send as _impl
4318
+ return await _impl(args)
4319
+
4320
+
4321
+ async def _handle_telegram_poll(args: dict) -> list[TextContent]:
4322
+ """Fetch recent messages from a Telegram chat."""
4323
+ from .mcp_tools.telegram_tools import _handle_telegram_poll as _impl
4324
+ return await _impl(args)
4325
+
4326
+
4327
+ async def _handle_telegram_chats(args: dict) -> list[TextContent]:
4328
+ """List available Telegram chats."""
4329
+ from .mcp_tools.telegram_tools import _handle_telegram_chats as _impl
4330
+ return await _impl(args)
4331
+
4332
+
4333
+ async def _handle_telegram_catchup(args: dict) -> list[TextContent]:
4334
+ """Full catch-up import from Telegram into all memory tiers."""
4335
+ from .mcp_tools.telegram_tools import _handle_telegram_catchup as _impl
4336
+ return await _impl(args)
4337
+
4338
+
4339
+ async def _handle_telegram_soul_swap(args: dict) -> list[TextContent]:
4340
+ """Perform a soul swap and announce it to a Telegram chat."""
4341
+ from .mcp_tools.telegram_tools import _handle_telegram_soul_swap as _impl
4342
+ return await _impl(args)
4343
+
4344
+
4345
+ # ── Version Check ────────────────────────────────────────────
4346
+
4347
+
4348
+ async def _handle_version_check(args: dict) -> list[TextContent]:
4349
+ """Check ecosystem package versions against PyPI."""
4350
+ try:
4351
+ from .version_check import check_versions
4352
+
4353
+ no_pypi = args.get("no_pypi", False)
4354
+ report = check_versions(check_pypi=not no_pypi)
4355
+
4356
+ result = {
4357
+ "all_up_to_date": report.all_up_to_date,
4358
+ "packages": [
4359
+ {
4360
+ "name": p.name,
4361
+ "installed": p.installed,
4362
+ "latest": p.latest,
4363
+ "up_to_date": p.up_to_date,
4364
+ }
4365
+ for p in report.packages
4366
+ ],
4367
+ }
4368
+ return _json_response(result)
4369
+ except Exception as e:
4370
+ return _json_response({"error": str(e)})
4371
+
4372
+
4373
+ # ── GTD handlers ──────────────────────────────────────────
4374
+
4375
+
4376
+ async def _handle_gtd_capture(args: dict) -> list[TextContent]:
4377
+ """Capture an item to the GTD inbox."""
4378
+ from .mcp_tools.gtd_tools import _handle_gtd_capture as _impl
4379
+ return await _impl(args)
4380
+
4381
+
4382
+ async def _handle_gtd_inbox(args: dict) -> list[TextContent]:
4383
+ """List GTD inbox items."""
4384
+ from .mcp_tools.gtd_tools import _handle_gtd_inbox as _impl
4385
+ return await _impl(args)
4386
+
4387
+
4388
+ async def _handle_gtd_status(args: dict) -> list[TextContent]:
4389
+ """GTD status summary."""
4390
+ from .mcp_tools.gtd_tools import _handle_gtd_status as _impl
4391
+ return await _impl(args)
4392
+
4393
+
4394
+ async def _handle_gtd_clarify(args: dict) -> list[TextContent]:
4395
+ """Clarify and organize a GTD inbox item."""
4396
+ from .mcp_tools.gtd_tools import _handle_gtd_clarify as _impl
4397
+ return await _impl(args)
4398
+
4399
+
4400
+ async def _handle_gtd_move(args: dict) -> list[TextContent]:
4401
+ """Move a GTD item between lists."""
4402
+ from .mcp_tools.gtd_tools import _handle_gtd_move as _impl
4403
+ return await _impl(args)
4404
+
4405
+
4406
+ async def _handle_gtd_done(args: dict) -> list[TextContent]:
4407
+ """Mark a GTD item as done."""
4408
+ from .mcp_tools.gtd_tools import _handle_gtd_done as _impl
4409
+ return await _impl(args)
4410
+
4411
+
4412
+ async def _handle_gtd_review(args: dict) -> list[TextContent]:
4413
+ """GTD weekly review summary."""
4414
+ from .mcp_tools.gtd_tools import _handle_gtd_review as _impl
4415
+ return await _impl(args)
4416
+
4417
+
4418
+ async def _handle_gtd_next(args: dict) -> list[TextContent]:
4419
+ """View next actions with context/energy/priority filters."""
4420
+ from .mcp_tools.gtd_tools import _handle_gtd_next as _impl
4421
+ return await _impl(args)
4422
+
4423
+
4424
+ async def _handle_gtd_projects(args: dict) -> list[TextContent]:
4425
+ """View GTD projects filtered by status."""
4426
+ from .mcp_tools.gtd_tools import _handle_gtd_projects as _impl
4427
+ return await _impl(args)
4428
+
4429
+
4430
+ async def _handle_gtd_waiting(args: dict) -> list[TextContent]:
4431
+ """View waiting-for items sorted by longest waiting."""
4432
+ from .mcp_tools.gtd_tools import _handle_gtd_waiting as _impl
4433
+ return await _impl(args)
4434
+
4435
+
4436
+ # ── DID tools ─────────────────────────────────────────────
4437
+
4438
+
4439
+ async def _handle_did_show(args: dict) -> list[TextContent]:
4440
+ """Display DID documents."""
4441
+ from .mcp_tools.did_tools import _handle_did_show as _impl
4442
+ return await _impl(args)
4443
+
4444
+
4445
+ async def _handle_did_verify_peer(args: dict) -> list[TextContent]:
4446
+ """Verify a peer's DID."""
4447
+ from .mcp_tools.did_tools import _handle_did_verify_peer as _impl
4448
+ return await _impl(args)
4449
+
4450
+
4451
+ async def _handle_did_publish(args: dict) -> list[TextContent]:
4452
+ """Publish DID documents to disk."""
4453
+ from .mcp_tools.did_tools import _handle_did_publish as _impl
4454
+ return await _impl(args)
4455
+
4456
+
4457
+ async def _handle_did_policy(args: dict) -> list[TextContent]:
4458
+ """View or set DID publication policy."""
4459
+ from .mcp_tools.did_tools import _handle_did_policy as _impl
4460
+ return await _impl(args)
4461
+
4462
+
4463
+ async def _handle_did_identity_card(args: dict) -> list[TextContent]:
4464
+ """Generate sovereign identity card."""
4465
+ from .mcp_tools.did_tools import _handle_did_identity_card as _impl
4466
+ return await _impl(args)
4467
+
4468
+
4469
+ # ── Consciousness tools ──────────────────────────────────
4470
+
4471
+
4472
+ async def _handle_consciousness_status(args: dict) -> list[TextContent]:
4473
+ """Get consciousness loop status."""
4474
+ from .mcp_tools.consciousness_tools import _handle_consciousness_status as _impl
4475
+ return await _impl(args)
4476
+
4477
+
4478
+ async def _handle_consciousness_test(args: dict) -> list[TextContent]:
4479
+ """Test consciousness pipeline."""
4480
+ from .mcp_tools.consciousness_tools import _handle_consciousness_test as _impl
4481
+ return await _impl(args)
4482
+
4483
+
4484
+ # ── Pub/sub stats ─────────────────────────────────────────
4485
+
4486
+
4487
+ async def _handle_pubsub_stats(args: dict) -> list[TextContent]:
4488
+ """Show per-topic pub/sub statistics."""
4489
+ from .mcp_tools.pubsub_tools import _handle_pubsub_stats as _impl
4490
+ return await _impl(args)
4491
+
4492
+
4493
+ # ── Emotion tools ─────────────────────────────────────────
4494
+
4495
+
4496
+ async def _handle_emotion_trend(args: dict) -> list[TextContent]:
4497
+ """Return emotion trend analysis."""
4498
+ from .mcp_tools.emotion_tools import _handle_emotion_trend as _impl
4499
+ return await _impl(args)
4500
+
4501
+
4502
+ # ── Notification tools ────────────────────────────────────
4503
+
4504
+
4505
+ async def _handle_send_notification(args: dict) -> list[TextContent]:
4506
+ """Send a desktop notification."""
4507
+ from .mcp_tools.notification_tools import _handle_send_notification as _impl
4508
+ return await _impl(args)
4509
+
4510
+
4511
+ # ── Ansible tools ─────────────────────────────────────────
4512
+
4513
+
4514
+ async def _handle_run_ansible_playbook(args: dict) -> list[TextContent]:
4515
+ """Run an Ansible playbook."""
4516
+ from .mcp_tools.ansible_tools import _handle_run_ansible_playbook as _impl
4517
+ return await _impl(args)
4518
+
4519
+
4520
+ # ── Deploy tools ──────────────────────────────────────────
4521
+
4522
+
4523
+ async def _handle_deploy_status(args: dict) -> list[TextContent]:
4524
+ """Report deployment status."""
4525
+ from .mcp_tools.deploy_tools import _handle_deploy_status as _impl
4526
+ return await _impl(args)
4527
+
4528
+
4529
+ # ── SKStacks secret tools ────────────────────────────────
4530
+
4531
+
4532
+ async def _handle_capauth_secret_get(args: dict) -> list[TextContent]:
4533
+ """Retrieve a secret from capauth backend."""
4534
+ from .mcp_tools.skstacks_tools import _handle_capauth_secret_get as _impl
4535
+ return await _impl(args)
4536
+
4537
+
4538
+ async def _handle_skstacks_secret_get(args: dict) -> list[TextContent]:
4539
+ """Retrieve a secret from SKStacks v2 backend."""
4540
+ from .mcp_tools.skstacks_tools import _handle_skstacks_secret_get as _impl
4541
+ return await _impl(args)
4542
+
4543
+
4544
+ async def _handle_skstacks_secret_set(args: dict) -> list[TextContent]:
4545
+ """Write or update a secret in SKStacks v2 backend."""
4546
+ from .mcp_tools.skstacks_tools import _handle_skstacks_secret_set as _impl
4547
+ return await _impl(args)
4548
+
4549
+
4550
+ # ── Cloud 9 tools ─────────────────────────────────────────
4551
+
4552
+
4553
+ async def _handle_trust_rehydrate(args: dict) -> list[TextContent]:
4554
+ """Rehydrate trust from FEB files."""
4555
+ from .mcp_tools.cloud9_tools import _handle_trust_rehydrate as _impl
4556
+ return await _impl(args)
4557
+
4558
+
4559
+ async def _handle_trust_status(args: dict) -> list[TextContent]:
4560
+ """Show trust/Cloud9 status."""
4561
+ from .mcp_tools.cloud9_tools import _handle_trust_status as _impl
4562
+ return await _impl(args)
4563
+
4564
+
4565
+ async def _handle_trust_febs(args: dict) -> list[TextContent]:
4566
+ """List FEB files."""
4567
+ from .mcp_tools.cloud9_tools import _handle_trust_febs as _impl
4568
+ return await _impl(args)
4569
+
4570
+
4571
+ # ── SKSecurity tools ──────────────────────────────────────
4572
+
4573
+
4574
+ async def _handle_security_audit_log(args: dict) -> list[TextContent]:
4575
+ """Read security audit log entries."""
4576
+ from .mcp_tools.security_tools import _handle_security_audit_log as _impl
4577
+ return await _impl(args)
4578
+
4579
+
4580
+ async def _handle_security_status(args: dict) -> list[TextContent]:
4581
+ """Show security pillar status."""
4582
+ from .mcp_tools.security_tools import _handle_security_status as _impl
4583
+ return await _impl(args)
4584
+
4585
+
4586
+ # ── SKChat tools ──────────────────────────────────────────
4587
+
4588
+
4589
+ async def _handle_chat_send(args: dict) -> list[TextContent]:
4590
+ """Send a chat message via SKChat."""
4591
+ from .mcp_tools.skchat_tools import _handle_chat_send as _impl
4592
+ return await _impl(args)
4593
+
4594
+
4595
+ async def _handle_chat_history(args: dict) -> list[TextContent]:
4596
+ """Retrieve chat history."""
4597
+ from .mcp_tools.skchat_tools import _handle_chat_history as _impl
4598
+ return await _impl(args)
4599
+
4600
+
4601
+ # ── SKComm tools ──────────────────────────────────────────
4602
+
4603
+
4604
+ async def _handle_comm_notify(args: dict) -> list[TextContent]:
4605
+ """Send a notification via SKComm."""
4606
+ from .mcp_tools.skcomm_tools import _handle_comm_notify as _impl
4607
+ return await _impl(args)
4608
+
4609
+
4610
+ async def _handle_comm_status(args: dict) -> list[TextContent]:
4611
+ """Show SKComm subsystem status."""
4612
+ from .mcp_tools.skcomm_tools import _handle_comm_status as _impl
4613
+ return await _impl(args)
4614
+
4615
+
4616
+ # ── CapAuth tools ─────────────────────────────────────────
4617
+
4618
+
4619
+ async def _handle_capauth_status_tool(args: dict) -> list[TextContent]:
4620
+ """Show CapAuth profile status."""
4621
+ from .mcp_tools.capauth_tools import _handle_capauth_status as _impl
4622
+ return await _impl(args)
4623
+
4624
+
4625
+ async def _handle_capauth_verify(args: dict) -> list[TextContent]:
4626
+ """Verify a CapAuth identity or token."""
4627
+ from .mcp_tools.capauth_tools import _handle_capauth_verify as _impl
4628
+ return await _impl(args)
4629
+
4630
+
4631
+ # ── Soul Blueprint Registry handlers ─────────────────────────
4632
+
4633
+
4634
+ async def _handle_soul_registry_search(args: dict) -> list[TextContent]:
4635
+ """Search the souls.skworld.io blueprint registry."""
4636
+ query = args.get("query", "")
4637
+ if not query:
4638
+ return _error_response("query is required")
4639
+ try:
4640
+ from .blueprint_registry import BlueprintRegistryClient
4641
+
4642
+ client = BlueprintRegistryClient()
4643
+ results = client.search_blueprints(query)
4644
+ return _json_response({
4645
+ "query": query,
4646
+ "count": len(results),
4647
+ "blueprints": results,
4648
+ })
4649
+ except Exception as exc:
4650
+ return _error_response(f"Registry search failed: {exc}")
4651
+
4652
+
4653
+ async def _handle_soul_registry_publish(args: dict) -> list[TextContent]:
4654
+ """Publish a local soul blueprint to the registry."""
4655
+ name = args.get("name", "")
4656
+ if not name:
4657
+ return _error_response("name is required")
4658
+ try:
4659
+ from .blueprint_registry import BlueprintRegistryClient
4660
+ from .soul import SoulManager
4661
+ import json as _json
4662
+
4663
+ home = _home()
4664
+ mgr = SoulManager(home)
4665
+ bp = mgr.get_info(name)
4666
+ if bp is None:
4667
+ return _error_response(f"Soul '{name}' is not installed locally")
4668
+
4669
+ client = BlueprintRegistryClient()
4670
+ soul_data = _json.loads(bp.model_dump_json())
4671
+ result = client.publish_blueprint(soul_data)
4672
+ return _json_response({
4673
+ "published": True,
4674
+ "name": name,
4675
+ "display_name": bp.display_name,
4676
+ "response": result,
4677
+ })
4678
+ except Exception as exc:
4679
+ return _error_response(f"Registry publish failed: {exc}")
4680
+
4681
+
4682
+ # ═══════════════════════════════════════════════════════════
4683
+ # Entry Point
4684
+ # ═══════════════════════════════════════════════════════════
4685
+
4686
+
4687
+ def main() -> None:
4688
+ """Run the MCP server on stdio transport."""
4689
+ logging.basicConfig(level=logging.WARNING, format="%(name)s: %(message)s")
4690
+ asyncio.run(_run_server())
4691
+
4692
+
4693
+ async def _run_server() -> None:
4694
+ """Async entry point for the stdio MCP server."""
4695
+ async with stdio_server() as (read_stream, write_stream):
4696
+ await server.run(read_stream, write_stream, server.create_initialization_options())
4697
+
4698
+
4699
+ if __name__ == "__main__":
4700
+ main()