@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,759 @@
1
+ """
2
+ Docker Provider — deploy agent teams as Docker containers.
3
+
4
+ Each agent runs in its own container with resource limits derived from
5
+ the blueprint ResourceSpec. Supports both individual container management
6
+ and docker-compose generation for full team orchestration.
7
+
8
+ The provider wires three sovereign infrastructure components into every
9
+ agent container:
10
+
11
+ 1. **Soul Blueprint** — injected via SOUL_BLUEPRINT env and config.json
12
+ 2. **MCP Server** — host-side skcapstone MCP reachable via env
13
+ (SKCAPSTONE_MCP_HOST / SKCAPSTONE_MCP_SOCKET). Set one of these so
14
+ containers can call memory_store, coord_claim, etc.
15
+ 3. **SKComm Transport** — comms directory bind-mounted at /skcomm so
16
+ containers share the same file-channel inboxes as local agents.
17
+
18
+ Prerequisites:
19
+ - Docker daemon running and accessible (DOCKER_HOST or default socket)
20
+ - docker Python SDK: pip install docker
21
+ - Optional: DOCKER_BASE_IMAGE env var to override the default image
22
+ - Optional: SKCOMM_HOME env var for the comms directory
23
+ - Optional: SKCAPSTONE_MCP_HOST env var (host:port) or
24
+ SKCAPSTONE_MCP_SOCKET env var (unix socket path)
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import json
30
+ import logging
31
+ import os
32
+ from pathlib import Path
33
+ from typing import Any, Dict, List, Optional
34
+
35
+ import yaml
36
+
37
+ from ..blueprints.schema import AgentSpec, BlueprintManifest, ProviderType
38
+ from ..team_engine import AgentStatus, ProviderBackend
39
+
40
+ logger = logging.getLogger(__name__)
41
+
42
+ _DEFAULT_IMAGE = "python:3.12-slim"
43
+ _GRACEFUL_STOP_TIMEOUT = 15 # seconds before SIGKILL
44
+ _MCP_CONTAINER_SOCKET = "/run/skcapstone/mcp.sock" # path inside containers
45
+
46
+
47
+ def _parse_memory_bytes(mem_str: str) -> int:
48
+ """Convert memory string like '2g' or '512m' to bytes.
49
+
50
+ Args:
51
+ mem_str: Memory string with unit suffix (g/G for gigabytes, m/M for
52
+ megabytes).
53
+
54
+ Returns:
55
+ Memory in bytes as an integer.
56
+ """
57
+ mem_str = mem_str.strip().lower()
58
+ if mem_str.endswith("g"):
59
+ return int(float(mem_str[:-1]) * 1024 * 1024 * 1024)
60
+ if mem_str.endswith("m"):
61
+ return int(float(mem_str[:-1]) * 1024 * 1024)
62
+ return int(mem_str)
63
+
64
+
65
+ def _nano_cpus(cores: int) -> int:
66
+ """Convert CPU core count to Docker nano_cpus value.
67
+
68
+ Args:
69
+ cores: Number of CPU cores.
70
+
71
+ Returns:
72
+ NanoCPUs value (cores * 1e9).
73
+ """
74
+ return cores * 1_000_000_000
75
+
76
+
77
+ class DockerProvider(ProviderBackend):
78
+ """Deploy agent teams as Docker containers.
79
+
80
+ Each agent spec maps to one (or more) containers with resource limits,
81
+ environment variables, and a mounted config volume. The provider also
82
+ supports generating a docker-compose.yml for full team orchestration.
83
+
84
+ Sovereign infrastructure wiring
85
+ --------------------------------
86
+ - **SKComm**: pass ``skcomm_home`` (or set SKCOMM_HOME) to bind-mount
87
+ the comms directory at ``/skcomm`` inside every container so
88
+ container agents share the same file-channel inboxes.
89
+ - **MCP server**: pass ``mcp_host`` (host:port) or ``mcp_socket_path``
90
+ to inject the skcapstone MCP endpoint into container env. Containers
91
+ can then call skcapstone memory, coordination, and heartbeat tools.
92
+
93
+ Args:
94
+ base_image: Default Docker image for agent containers.
95
+ network_name: Docker network to attach containers to.
96
+ volume_prefix: Prefix for named volumes created per agent.
97
+ docker_host: Docker daemon socket/URL (default: DOCKER_HOST or
98
+ ``unix:///var/run/docker.sock``).
99
+ skcomm_home: Host-side SKComm comms root directory; bind-mounted at
100
+ ``/skcomm`` inside containers. Reads SKCOMM_HOME if not set.
101
+ mcp_host: Host:port of the skcapstone MCP server (e.g.
102
+ ``"host-gateway:8765"``). Sets SKCAPSTONE_MCP_HOST inside
103
+ containers. Reads SKCAPSTONE_MCP_HOST env if not set.
104
+ mcp_socket_path: Host-side Unix socket for the MCP server. Bind-
105
+ mounted at /run/skcapstone/mcp.sock and sets
106
+ SKCAPSTONE_MCP_SOCKET inside containers. Reads
107
+ SKCAPSTONE_MCP_SOCKET env if not set.
108
+ """
109
+
110
+ provider_type = ProviderType.DOCKER
111
+
112
+ def __init__(
113
+ self,
114
+ base_image: Optional[str] = None,
115
+ network_name: str = "skcapstone",
116
+ volume_prefix: str = "skcapstone-agent",
117
+ docker_host: Optional[str] = None,
118
+ skcomm_home: Optional[str] = None,
119
+ mcp_host: Optional[str] = None,
120
+ mcp_socket_path: Optional[str] = None,
121
+ ) -> None:
122
+ self._base_image = (
123
+ base_image
124
+ or os.environ.get("DOCKER_BASE_IMAGE", _DEFAULT_IMAGE)
125
+ )
126
+ self._network_name = network_name
127
+ self._volume_prefix = volume_prefix
128
+ self._docker_host = docker_host or os.environ.get("DOCKER_HOST", "")
129
+ self._skcomm_home = skcomm_home or os.environ.get("SKCOMM_HOME", "")
130
+ self._mcp_host = mcp_host or os.environ.get("SKCAPSTONE_MCP_HOST", "")
131
+ self._mcp_socket_path = (
132
+ mcp_socket_path or os.environ.get("SKCAPSTONE_MCP_SOCKET", "")
133
+ )
134
+
135
+ # ------------------------------------------------------------------
136
+ # Internal helpers
137
+ # ------------------------------------------------------------------
138
+
139
+ def _client(self):
140
+ """Return an authenticated Docker client.
141
+
142
+ Returns:
143
+ docker.DockerClient instance.
144
+
145
+ Raises:
146
+ RuntimeError: If the docker SDK is not installed or the daemon
147
+ is unreachable.
148
+ """
149
+ try:
150
+ import docker
151
+ except ImportError:
152
+ raise RuntimeError(
153
+ "Docker provider requires 'docker' SDK: pip install docker"
154
+ )
155
+
156
+ kwargs: Dict[str, Any] = {}
157
+ if self._docker_host:
158
+ kwargs["base_url"] = self._docker_host
159
+
160
+ try:
161
+ client = docker.from_env(**kwargs)
162
+ client.ping()
163
+ return client
164
+ except Exception as exc:
165
+ raise RuntimeError(
166
+ f"Cannot connect to Docker daemon: {exc}"
167
+ ) from exc
168
+
169
+ def _ensure_network(self, client) -> None:
170
+ """Create the shared Docker network if it does not exist.
171
+
172
+ Args:
173
+ client: docker.DockerClient instance.
174
+ """
175
+ try:
176
+ client.networks.get(self._network_name)
177
+ except Exception:
178
+ client.networks.create(
179
+ self._network_name,
180
+ driver="bridge",
181
+ check_duplicate=True,
182
+ )
183
+ logger.info("Created Docker network: %s", self._network_name)
184
+
185
+ def _volume_name(self, agent_name: str) -> str:
186
+ """Derive the named volume for an agent.
187
+
188
+ Args:
189
+ agent_name: Agent instance name.
190
+
191
+ Returns:
192
+ Docker volume name string.
193
+ """
194
+ safe = agent_name.replace("_", "-").lower()
195
+ return f"{self._volume_prefix}-{safe}"
196
+
197
+ def _container_name(self, agent_name: str) -> str:
198
+ """Derive the container name for an agent.
199
+
200
+ Args:
201
+ agent_name: Agent instance name.
202
+
203
+ Returns:
204
+ Docker container name string.
205
+ """
206
+ return agent_name.replace("_", "-").lower()
207
+
208
+ def _build_agent_config(self, agent_name: str, spec: AgentSpec, team_name: str) -> Dict[str, Any]:
209
+ """Build the agent config dict written into the container.
210
+
211
+ Args:
212
+ agent_name: Agent instance name.
213
+ spec: Agent specification.
214
+ team_name: Parent team name.
215
+
216
+ Returns:
217
+ Config dict ready for JSON serialisation.
218
+ """
219
+ return {
220
+ "agent_name": agent_name,
221
+ "team_name": team_name,
222
+ "role": spec.role.value,
223
+ "model": spec.model_name or spec.model.value,
224
+ "skills": spec.skills,
225
+ "soul_blueprint": spec.soul_blueprint,
226
+ "env": spec.env,
227
+ }
228
+
229
+ def _build_sovereign_env(self, env_vars: Dict[str, str]) -> None:
230
+ """Inject sovereign infrastructure env vars into the env dict in-place.
231
+
232
+ Adds MCP server endpoint and SKComm home when configured so
233
+ container agents can reach the host-side sovereign stack.
234
+
235
+ Args:
236
+ env_vars: Environment variable dict to mutate.
237
+ """
238
+ if self._mcp_host:
239
+ env_vars["SKCAPSTONE_MCP_HOST"] = self._mcp_host
240
+ if self._mcp_socket_path:
241
+ env_vars["SKCAPSTONE_MCP_SOCKET"] = _MCP_CONTAINER_SOCKET
242
+ if self._skcomm_home:
243
+ env_vars["SKCOMM_HOME"] = "/skcomm"
244
+
245
+ def _build_volumes_config(self, volume_name: str) -> Dict[str, Any]:
246
+ """Build the volumes dict for containers.create().
247
+
248
+ Always includes the per-agent named volume at /agent.
249
+ Optionally adds a bind mount for the SKComm comms directory.
250
+
251
+ Args:
252
+ volume_name: Named Docker volume for agent state.
253
+
254
+ Returns:
255
+ Volumes dict suitable for docker SDK containers.create().
256
+ """
257
+ vols: Dict[str, Any] = {
258
+ volume_name: {"bind": "/agent", "mode": "rw"},
259
+ }
260
+ if self._skcomm_home and Path(self._skcomm_home).exists():
261
+ vols[self._skcomm_home] = {"bind": "/skcomm", "mode": "rw"}
262
+ if self._mcp_socket_path and Path(self._mcp_socket_path).exists():
263
+ vols[self._mcp_socket_path] = {
264
+ "bind": _MCP_CONTAINER_SOCKET,
265
+ "mode": "ro",
266
+ }
267
+ return vols
268
+
269
+ # ------------------------------------------------------------------
270
+ # ProviderBackend interface
271
+ # ------------------------------------------------------------------
272
+
273
+ def provision(
274
+ self,
275
+ agent_name: str,
276
+ spec: AgentSpec,
277
+ team_name: str,
278
+ ) -> Dict[str, Any]:
279
+ """Create a Docker container for one agent instance.
280
+
281
+ The container is created but NOT started here; start() does that.
282
+ Resource limits (CPU, memory) are applied from spec.resources.
283
+ SKComm and MCP sovereign infrastructure are wired in when
284
+ configured on this provider.
285
+
286
+ Args:
287
+ agent_name: Unique agent instance name.
288
+ spec: Agent specification including resource requirements.
289
+ team_name: Parent team name.
290
+
291
+ Returns:
292
+ Dict with 'container_id', 'container_name', 'host',
293
+ 'volume_name', and 'team_name'.
294
+
295
+ Raises:
296
+ RuntimeError: If Docker daemon is unreachable or container
297
+ creation fails.
298
+ """
299
+ client = self._client()
300
+ self._ensure_network(client)
301
+
302
+ container_name = self._container_name(agent_name)
303
+ volume_name = self._volume_name(agent_name)
304
+
305
+ # Remove any stale container with the same name
306
+ try:
307
+ old = client.containers.get(container_name)
308
+ logger.warning("Removing stale container: %s", container_name)
309
+ old.remove(force=True)
310
+ except Exception:
311
+ pass
312
+
313
+ # Ensure named volume for agent state persistence
314
+ try:
315
+ client.volumes.get(volume_name)
316
+ except Exception:
317
+ client.volumes.create(volume_name)
318
+ logger.debug("Created volume: %s", volume_name)
319
+
320
+ mem_bytes = _parse_memory_bytes(spec.resources.memory)
321
+ nano_cpus = _nano_cpus(spec.resources.cores)
322
+
323
+ env_vars: Dict[str, str] = {
324
+ "AGENT_NAME": agent_name,
325
+ "TEAM_NAME": team_name,
326
+ "AGENT_ROLE": spec.role.value,
327
+ "AGENT_MODEL": spec.model_name or spec.model.value,
328
+ }
329
+ if spec.soul_blueprint:
330
+ env_vars["SOUL_BLUEPRINT"] = spec.soul_blueprint
331
+ env_vars.update(spec.env)
332
+
333
+ # Wire sovereign infrastructure (MCP + SKComm)
334
+ self._build_sovereign_env(env_vars)
335
+
336
+ volumes_config = self._build_volumes_config(volume_name)
337
+
338
+ logger.info(
339
+ "Creating container %s (%s, %s RAM, %d cores)",
340
+ container_name,
341
+ self._base_image,
342
+ spec.resources.memory,
343
+ spec.resources.cores,
344
+ )
345
+
346
+ container = client.containers.create(
347
+ image=self._base_image,
348
+ name=container_name,
349
+ environment=env_vars,
350
+ volumes=volumes_config,
351
+ network=self._network_name,
352
+ mem_limit=mem_bytes,
353
+ nano_cpus=nano_cpus,
354
+ # Reason: Keep STDIN open so the container does not exit
355
+ # immediately when used with interactive agent runtimes.
356
+ stdin_open=True,
357
+ tty=False,
358
+ labels={
359
+ "managed_by": "skcapstone",
360
+ "team": team_name,
361
+ "agent": agent_name,
362
+ "role": spec.role.value,
363
+ },
364
+ # Restart unless explicitly stopped (resilience for long-lived agents)
365
+ restart_policy={"Name": "unless-stopped"},
366
+ )
367
+
368
+ return {
369
+ "container_id": container.id,
370
+ "container_name": container_name,
371
+ "host": container_name,
372
+ "volume_name": volume_name,
373
+ "team_name": team_name, # stored so configure() can use it
374
+ }
375
+
376
+ def configure(
377
+ self,
378
+ agent_name: str,
379
+ spec: AgentSpec,
380
+ provision_result: Dict[str, Any],
381
+ ) -> bool:
382
+ """Write agent configuration into the container volume.
383
+
384
+ Injects config.json (soul blueprint, skills, model) into the
385
+ /agent directory via docker exec + shell printf.
386
+
387
+ Args:
388
+ agent_name: Agent instance name.
389
+ spec: Agent specification.
390
+ provision_result: Output from provision().
391
+
392
+ Returns:
393
+ True if configuration was written successfully.
394
+ """
395
+ container_name = provision_result.get("container_name", "")
396
+ if not container_name:
397
+ return False
398
+
399
+ client = self._client()
400
+ try:
401
+ container = client.containers.get(container_name)
402
+ except Exception as exc:
403
+ logger.error("Container %s not found: %s", container_name, exc)
404
+ return False
405
+
406
+ # Start container temporarily to write config if not already running
407
+ if container.status != "running":
408
+ container.start()
409
+
410
+ team_name = provision_result.get("team_name", "")
411
+ config = self._build_agent_config(agent_name, spec, team_name)
412
+ config_json = json.dumps(config, indent=2)
413
+
414
+ # Reason: Use exec to write the config file inside the container so
415
+ # no bind-mounted host path is required; the named volume holds state.
416
+ escaped = config_json.replace("'", "'\\''")
417
+ exit_code, output = container.exec_run(
418
+ cmd=["sh", "-c", f"mkdir -p /agent && printf '%s' '{escaped}' > /agent/config.json"],
419
+ demux=False,
420
+ )
421
+
422
+ if exit_code != 0:
423
+ logger.warning(
424
+ "Config write exit_code=%d for %s: %s",
425
+ exit_code, agent_name, output,
426
+ )
427
+ return False
428
+
429
+ logger.info("Agent config written to container %s", container_name)
430
+ return True
431
+
432
+ def start(
433
+ self,
434
+ agent_name: str,
435
+ provision_result: Dict[str, Any],
436
+ ) -> bool:
437
+ """Start the agent container.
438
+
439
+ Args:
440
+ agent_name: Agent instance name.
441
+ provision_result: Output from provision().
442
+
443
+ Returns:
444
+ True if the container started successfully.
445
+ """
446
+ client = self._client()
447
+ container_name = provision_result.get("container_name", "")
448
+ if not container_name:
449
+ return False
450
+
451
+ try:
452
+ container = client.containers.get(container_name)
453
+ container.start()
454
+ container.reload()
455
+ logger.info(
456
+ "Started container %s (id=%s)",
457
+ container_name, container.id[:12],
458
+ )
459
+ return True
460
+ except Exception as exc:
461
+ logger.error("Failed to start %s: %s", container_name, exc)
462
+ return False
463
+
464
+ def stop(
465
+ self,
466
+ agent_name: str,
467
+ provision_result: Dict[str, Any],
468
+ ) -> bool:
469
+ """Stop the agent container gracefully (SIGTERM then SIGKILL).
470
+
471
+ Args:
472
+ agent_name: Agent instance name.
473
+ provision_result: Output from provision().
474
+
475
+ Returns:
476
+ True if stopped or already not running.
477
+ """
478
+ client = self._client()
479
+ container_name = provision_result.get("container_name", "")
480
+ if not container_name:
481
+ return True
482
+
483
+ try:
484
+ container = client.containers.get(container_name)
485
+ container.stop(timeout=_GRACEFUL_STOP_TIMEOUT)
486
+ logger.info("Stopped container %s", container_name)
487
+ return True
488
+ except Exception as exc:
489
+ logger.warning("Could not stop %s: %s", container_name, exc)
490
+ return False
491
+
492
+ def destroy(
493
+ self,
494
+ agent_name: str,
495
+ provision_result: Dict[str, Any],
496
+ ) -> bool:
497
+ """Remove the container and its associated named volume.
498
+
499
+ Args:
500
+ agent_name: Agent instance name.
501
+ provision_result: Output from provision().
502
+
503
+ Returns:
504
+ True if destroyed (container and volume removed).
505
+ """
506
+ self.stop(agent_name, provision_result)
507
+
508
+ client = self._client()
509
+ container_name = provision_result.get("container_name", "")
510
+ volume_name = provision_result.get("volume_name", "")
511
+
512
+ destroyed = True
513
+
514
+ if container_name:
515
+ try:
516
+ container = client.containers.get(container_name)
517
+ container.remove(v=True, force=True)
518
+ logger.info("Removed container %s", container_name)
519
+ except Exception as exc:
520
+ logger.warning("Could not remove container %s: %s", container_name, exc)
521
+ destroyed = False
522
+
523
+ if volume_name:
524
+ try:
525
+ vol = client.volumes.get(volume_name)
526
+ vol.remove(force=True)
527
+ logger.info("Removed volume %s", volume_name)
528
+ except Exception as exc:
529
+ logger.debug("Volume %s already removed or missing: %s", volume_name, exc)
530
+
531
+ return destroyed
532
+
533
+ def rotate(
534
+ self,
535
+ agent_name: str,
536
+ spec: AgentSpec,
537
+ provision_result: Dict[str, Any],
538
+ ) -> Dict[str, Any]:
539
+ """Destroy the container and redeploy fresh (rotation).
540
+
541
+ Used when an agent shows context degradation. Stops and removes
542
+ the old container, then provisions and starts a fresh instance
543
+ with the same spec and team membership.
544
+
545
+ The original named volume is removed by destroy() so the new
546
+ container starts with a clean /agent directory.
547
+
548
+ Args:
549
+ agent_name: Agent instance name.
550
+ spec: Agent specification for the fresh container.
551
+ provision_result: Output from the previous provision() call;
552
+ used to locate the old container and to recover team_name.
553
+
554
+ Returns:
555
+ New provision_result dict from the fresh provision() call.
556
+ """
557
+ team_name = provision_result.get("team_name", "")
558
+
559
+ self.destroy(agent_name, provision_result)
560
+
561
+ new_result = self.provision(agent_name, spec, team_name)
562
+ self.configure(agent_name, spec, new_result)
563
+ self.start(agent_name, new_result)
564
+
565
+ logger.info(
566
+ "Rotated agent %s (old_container=%s new_container=%s)",
567
+ agent_name,
568
+ provision_result.get("container_name", "?"),
569
+ new_result.get("container_name", "?"),
570
+ )
571
+ return new_result
572
+
573
+ def health_check(
574
+ self,
575
+ agent_name: str,
576
+ provision_result: Dict[str, Any],
577
+ ) -> AgentStatus:
578
+ """Inspect container status via docker inspect.
579
+
580
+ Args:
581
+ agent_name: Agent instance name.
582
+ provision_result: Output from provision().
583
+
584
+ Returns:
585
+ AgentStatus based on container State.Status.
586
+ """
587
+ client = self._client()
588
+ container_name = provision_result.get("container_name", "")
589
+ if not container_name:
590
+ return AgentStatus.STOPPED
591
+
592
+ try:
593
+ container = client.containers.get(container_name)
594
+ container.reload()
595
+ state: str = container.status # running, exited, paused, …
596
+ if state == "running":
597
+ return AgentStatus.RUNNING
598
+ if state in ("exited", "dead"):
599
+ return AgentStatus.STOPPED
600
+ if state == "paused":
601
+ return AgentStatus.DEGRADED
602
+ return AgentStatus.DEGRADED
603
+ except Exception as exc:
604
+ logger.debug("health_check failed for %s: %s", container_name, exc)
605
+ return AgentStatus.FAILED
606
+
607
+ # ------------------------------------------------------------------
608
+ # Docker Compose generation
609
+ # ------------------------------------------------------------------
610
+
611
+ def generate_compose(
612
+ self,
613
+ blueprint: BlueprintManifest,
614
+ output_path: Optional[Path] = None,
615
+ include_mcp_service: bool = False,
616
+ ) -> str:
617
+ """Generate a docker-compose.yml from a full blueprint manifest.
618
+
619
+ Each agent (and each instance when count > 1) becomes a service.
620
+ Resource limits, environment variables, and soul blueprint paths
621
+ are all included.
622
+
623
+ When ``skcomm_home`` is configured on the provider the compose
624
+ output includes a named ``skcomm-data`` volume and mounts it at
625
+ ``/skcomm`` in every agent service.
626
+
627
+ When ``include_mcp_service=True`` a ``skcapstone-mcp`` sidecar
628
+ service is added; all agent containers receive ``SKCAPSTONE_MCP_HOST``
629
+ pointing at it so they can reach the MCP server.
630
+
631
+ Args:
632
+ blueprint: The validated blueprint manifest.
633
+ output_path: If provided, the YAML is written to this path.
634
+ include_mcp_service: Add a skcapstone-mcp service that agents
635
+ connect to via SKCAPSTONE_MCP_HOST.
636
+
637
+ Returns:
638
+ The docker-compose YAML string.
639
+ """
640
+ services: Dict[str, Any] = {}
641
+ volumes: Dict[str, Any] = {}
642
+
643
+ # Add skcapstone MCP sidecar if requested
644
+ mcp_service_name = "skcapstone-mcp"
645
+ if include_mcp_service:
646
+ services[mcp_service_name] = {
647
+ "image": self._base_image,
648
+ "container_name": mcp_service_name,
649
+ "command": ["skcapstone", "mcp", "--stdio"],
650
+ "environment": {
651
+ "SKCAPSTONE_HOME": "/agent/skcapstone",
652
+ },
653
+ "volumes": [
654
+ "skcapstone-mcp-data:/agent/skcapstone",
655
+ ],
656
+ "networks": [self._network_name],
657
+ "restart": "unless-stopped",
658
+ "labels": [
659
+ "managed_by=skcapstone",
660
+ f"team={blueprint.name}",
661
+ "role=mcp-server",
662
+ ],
663
+ }
664
+ volumes["skcapstone-mcp-data"] = {}
665
+
666
+ has_skcomm = bool(self._skcomm_home)
667
+ if has_skcomm:
668
+ volumes["skcomm-data"] = {}
669
+
670
+ for agent_key, spec in blueprint.agents.items():
671
+ for idx in range(spec.count):
672
+ suffix = f"-{idx + 1}" if spec.count > 1 else ""
673
+ svc_name = f"{blueprint.slug}-{agent_key}{suffix}".replace("_", "-")
674
+ volume_name = self._volume_name(svc_name)
675
+
676
+ env: Dict[str, str] = {
677
+ "AGENT_NAME": svc_name,
678
+ "TEAM_NAME": blueprint.name,
679
+ "AGENT_ROLE": spec.role.value,
680
+ "AGENT_MODEL": spec.model_name or spec.model.value,
681
+ }
682
+ env.update(spec.env)
683
+
684
+ if spec.soul_blueprint:
685
+ env["SOUL_BLUEPRINT"] = spec.soul_blueprint
686
+
687
+ # Wire sovereign infra env vars
688
+ if include_mcp_service:
689
+ env["SKCAPSTONE_MCP_HOST"] = f"{mcp_service_name}:8765"
690
+ elif self._mcp_host:
691
+ env["SKCAPSTONE_MCP_HOST"] = self._mcp_host
692
+
693
+ if has_skcomm:
694
+ env["SKCOMM_HOME"] = "/skcomm"
695
+
696
+ svc_volumes = [f"{volume_name}:/agent"]
697
+ if has_skcomm:
698
+ svc_volumes.append("skcomm-data:/skcomm")
699
+
700
+ deploy_limits: Dict[str, Any] = {
701
+ "resources": {
702
+ "limits": {
703
+ "cpus": str(spec.resources.cores),
704
+ "memory": spec.resources.memory.upper(),
705
+ }
706
+ }
707
+ }
708
+
709
+ service: Dict[str, Any] = {
710
+ "image": self._base_image,
711
+ "container_name": svc_name,
712
+ "environment": env,
713
+ "volumes": svc_volumes,
714
+ "networks": [self._network_name],
715
+ "restart": "unless-stopped",
716
+ "deploy": deploy_limits,
717
+ "labels": [
718
+ "managed_by=skcapstone",
719
+ f"team={blueprint.name}",
720
+ f"agent={svc_name}",
721
+ f"role={spec.role.value}",
722
+ ],
723
+ }
724
+
725
+ if spec.depends_on and include_mcp_service:
726
+ # Always depend on MCP service first when included
727
+ service["depends_on"] = [mcp_service_name] + [
728
+ f"{blueprint.slug}-{dep}".replace("_", "-")
729
+ for dep in spec.depends_on
730
+ ]
731
+ elif spec.depends_on:
732
+ service["depends_on"] = [
733
+ f"{blueprint.slug}-{dep}".replace("_", "-")
734
+ for dep in spec.depends_on
735
+ ]
736
+ elif include_mcp_service:
737
+ service["depends_on"] = [mcp_service_name]
738
+
739
+ services[svc_name] = service
740
+ volumes[volume_name] = {}
741
+
742
+ compose: Dict[str, Any] = {
743
+ "version": "3.9",
744
+ "services": services,
745
+ "volumes": {k: {} for k in volumes},
746
+ "networks": {
747
+ self._network_name: {
748
+ "driver": "bridge",
749
+ }
750
+ },
751
+ }
752
+
753
+ compose_yaml = yaml.dump(compose, default_flow_style=False, sort_keys=False)
754
+
755
+ if output_path:
756
+ Path(output_path).write_text(compose_yaml, encoding="utf-8")
757
+ logger.info("docker-compose.yml written to %s", output_path)
758
+
759
+ return compose_yaml