@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,300 @@
1
+ """
2
+ Unified test runner for the sovereign agent ecosystem.
3
+
4
+ Discovers all packages in the monorepo, runs pytest for each,
5
+ and presents a consolidated pass/fail summary. Works from any
6
+ terminal — no CI server, no IDE, no special tooling.
7
+
8
+ Usage:
9
+ skcapstone test # run all packages
10
+ skcapstone test --package skcomm # run one package
11
+ skcapstone test --fast # stop on first failure
12
+ skcapstone test --json-out # machine-readable results
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ import subprocess
19
+ import sys
20
+ import time
21
+ from dataclasses import dataclass, field
22
+ from pathlib import Path
23
+ from typing import Optional
24
+
25
+
26
+ ECOSYSTEM_PACKAGES = [
27
+ {"name": "skcapstone", "path": "skcapstone", "tests": "skcapstone/tests"},
28
+ {"name": "capauth", "path": "capauth", "tests": "capauth/tests"},
29
+ {"name": "skcomm", "path": "skcomm", "tests": "skcomm/tests"},
30
+ {"name": "skchat", "path": "skchat", "tests": "skchat/tests"},
31
+ {"name": "skmemory", "path": "skmemory", "tests": "skmemory/tests"},
32
+ {"name": "cloud9-python", "path": "cloud9-python", "tests": "cloud9-python/tests"},
33
+ ]
34
+
35
+
36
+ @dataclass
37
+ class PackageResult:
38
+ """Test results for a single package.
39
+
40
+ Attributes:
41
+ name: Package name.
42
+ passed: Number of tests passed.
43
+ failed: Number of tests failed.
44
+ errors: Number of collection/import errors.
45
+ skipped: Number of skipped tests.
46
+ duration_s: Total runtime in seconds.
47
+ exit_code: Pytest exit code.
48
+ output: Stdout from pytest (last N lines).
49
+ available: Whether the package tests were found.
50
+ """
51
+
52
+ name: str
53
+ passed: int = 0
54
+ failed: int = 0
55
+ errors: int = 0
56
+ skipped: int = 0
57
+ duration_s: float = 0.0
58
+ exit_code: int = -1
59
+ output: str = ""
60
+ available: bool = True
61
+
62
+ @property
63
+ def total(self) -> int:
64
+ """Total tests executed."""
65
+ return self.passed + self.failed + self.errors
66
+
67
+ @property
68
+ def success(self) -> bool:
69
+ """Whether all tests passed."""
70
+ return self.exit_code == 0 and self.failed == 0 and self.errors == 0
71
+
72
+ def to_dict(self) -> dict:
73
+ """Serialize to dict.
74
+
75
+ Returns:
76
+ dict: Package test results.
77
+ """
78
+ return {
79
+ "name": self.name,
80
+ "passed": self.passed,
81
+ "failed": self.failed,
82
+ "errors": self.errors,
83
+ "skipped": self.skipped,
84
+ "total": self.total,
85
+ "duration_s": round(self.duration_s, 2),
86
+ "success": self.success,
87
+ "available": self.available,
88
+ }
89
+
90
+
91
+ @dataclass
92
+ class TestReport:
93
+ """Consolidated test results across all packages.
94
+
95
+ Attributes:
96
+ results: Per-package results.
97
+ duration_s: Total runtime.
98
+ """
99
+
100
+ results: list[PackageResult] = field(default_factory=list)
101
+ duration_s: float = 0.0
102
+
103
+ @property
104
+ def total_passed(self) -> int:
105
+ """Total passing tests across all packages."""
106
+ return sum(r.passed for r in self.results)
107
+
108
+ @property
109
+ def total_failed(self) -> int:
110
+ """Total failing tests across all packages."""
111
+ return sum(r.failed for r in self.results)
112
+
113
+ @property
114
+ def total_errors(self) -> int:
115
+ """Total errors across all packages."""
116
+ return sum(r.errors for r in self.results)
117
+
118
+ @property
119
+ def all_passed(self) -> bool:
120
+ """Whether every package passed."""
121
+ return all(r.success for r in self.results if r.available)
122
+
123
+ @property
124
+ def packages_tested(self) -> int:
125
+ """Number of packages actually tested."""
126
+ return sum(1 for r in self.results if r.available)
127
+
128
+ def to_dict(self) -> dict:
129
+ """Serialize the full report.
130
+
131
+ Returns:
132
+ dict: Complete test report.
133
+ """
134
+ return {
135
+ "all_passed": self.all_passed,
136
+ "total_passed": self.total_passed,
137
+ "total_failed": self.total_failed,
138
+ "total_errors": self.total_errors,
139
+ "packages_tested": self.packages_tested,
140
+ "duration_s": round(self.duration_s, 2),
141
+ "packages": [r.to_dict() for r in self.results],
142
+ }
143
+
144
+
145
+ def run_all_tests(
146
+ monorepo_root: Path,
147
+ packages: Optional[list[str]] = None,
148
+ fail_fast: bool = False,
149
+ verbose: bool = False,
150
+ timeout: int = 120,
151
+ ) -> TestReport:
152
+ """Run pytest across ecosystem packages.
153
+
154
+ Args:
155
+ monorepo_root: Root of the monorepo (where package dirs live).
156
+ packages: Restrict to these package names. None = all.
157
+ fail_fast: Stop after first package failure.
158
+ verbose: Pass -v to pytest.
159
+ timeout: Per-package timeout in seconds.
160
+
161
+ Returns:
162
+ TestReport: Consolidated results.
163
+ """
164
+ report = TestReport()
165
+ start = time.monotonic()
166
+
167
+ targets = ECOSYSTEM_PACKAGES
168
+ if packages:
169
+ pkg_set = set(packages)
170
+ targets = [p for p in targets if p["name"] in pkg_set]
171
+
172
+ for pkg_info in targets:
173
+ test_dir = monorepo_root / pkg_info["tests"]
174
+ if not test_dir.exists():
175
+ report.results.append(PackageResult(
176
+ name=pkg_info["name"],
177
+ available=False,
178
+ output=f"Test directory not found: {test_dir}",
179
+ ))
180
+ continue
181
+
182
+ result = _run_package_tests(
183
+ monorepo_root, pkg_info, verbose=verbose, timeout=timeout,
184
+ )
185
+ report.results.append(result)
186
+
187
+ if fail_fast and not result.success:
188
+ break
189
+
190
+ report.duration_s = time.monotonic() - start
191
+ return report
192
+
193
+
194
+ def _run_package_tests(
195
+ root: Path,
196
+ pkg_info: dict,
197
+ verbose: bool = False,
198
+ timeout: int = 120,
199
+ ) -> PackageResult:
200
+ """Run pytest for a single package.
201
+
202
+ Args:
203
+ root: Monorepo root.
204
+ pkg_info: Package metadata dict.
205
+ verbose: Pass -v to pytest.
206
+ timeout: Timeout in seconds.
207
+
208
+ Returns:
209
+ PackageResult: Test results for this package.
210
+ """
211
+ result = PackageResult(name=pkg_info["name"])
212
+ test_dir = root / pkg_info["tests"]
213
+
214
+ cmd = [
215
+ sys.executable, "-m", "pytest",
216
+ str(test_dir),
217
+ "--tb=line",
218
+ "-q",
219
+ "--no-header",
220
+ ]
221
+ if verbose:
222
+ cmd.append("-v")
223
+
224
+ start = time.monotonic()
225
+
226
+ try:
227
+ proc = subprocess.run(
228
+ cmd,
229
+ capture_output=True,
230
+ text=True,
231
+ timeout=timeout,
232
+ cwd=str(root),
233
+ )
234
+ result.exit_code = proc.returncode
235
+ result.duration_s = time.monotonic() - start
236
+
237
+ output = proc.stdout + proc.stderr
238
+ result.output = _tail(output, 30)
239
+
240
+ _parse_pytest_summary(output, result)
241
+
242
+ except subprocess.TimeoutExpired:
243
+ result.duration_s = time.monotonic() - start
244
+ result.exit_code = -1
245
+ result.errors = 1
246
+ result.output = f"TIMEOUT after {timeout}s"
247
+
248
+ except Exception as exc:
249
+ result.duration_s = time.monotonic() - start
250
+ result.exit_code = -1
251
+ result.errors = 1
252
+ result.output = str(exc)
253
+
254
+ return result
255
+
256
+
257
+ def _parse_pytest_summary(output: str, result: PackageResult) -> None:
258
+ """Extract pass/fail/skip counts from pytest output.
259
+
260
+ Args:
261
+ output: Full pytest stdout+stderr.
262
+ result: PackageResult to populate.
263
+ """
264
+ import re
265
+
266
+ for line in reversed(output.split("\n")):
267
+ match = re.search(
268
+ r"(\d+)\s+passed", line,
269
+ )
270
+ if match:
271
+ result.passed = int(match.group(1))
272
+
273
+ match = re.search(r"(\d+)\s+failed", line)
274
+ if match:
275
+ result.failed = int(match.group(1))
276
+
277
+ match = re.search(r"(\d+)\s+error", line)
278
+ if match:
279
+ result.errors = int(match.group(1))
280
+
281
+ match = re.search(r"(\d+)\s+skipped", line)
282
+ if match:
283
+ result.skipped = int(match.group(1))
284
+
285
+ if result.passed > 0 or result.failed > 0:
286
+ break
287
+
288
+
289
+ def _tail(text: str, n: int) -> str:
290
+ """Get the last N lines of text.
291
+
292
+ Args:
293
+ text: Input text.
294
+ n: Number of lines.
295
+
296
+ Returns:
297
+ str: Last N lines.
298
+ """
299
+ lines = text.strip().split("\n")
300
+ return "\n".join(lines[-n:])
@@ -0,0 +1,150 @@
1
+ """
2
+ TLS helpers for the skcapstone daemon.
3
+
4
+ When ``SKCAPSTONE_TLS=true`` is set the daemon generates (or reuses)
5
+ a self-signed X.509 certificate + RSA private key stored under
6
+ ``~/.skcapstone/tls/``. The SHA-256 fingerprint of the certificate
7
+ is logged at startup so operators can pin it in clients.
8
+
9
+ Requirements
10
+ ------------
11
+ - ``cryptography >= 3.0`` (``pip install cryptography``)
12
+ - Python's built-in ``ssl`` module (wraps the stdlib HTTP server socket)
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import datetime
18
+ import hashlib
19
+ import ipaddress
20
+ import logging
21
+ import ssl
22
+ from pathlib import Path
23
+
24
+ logger = logging.getLogger("skcapstone.tls")
25
+
26
+ _CERT_FILENAME = "daemon.crt"
27
+ _KEY_FILENAME = "daemon.key"
28
+ _CERT_VALID_DAYS = 3650 # ~10 years
29
+
30
+
31
+ def _ensure_tls_dir(tls_dir: Path) -> None:
32
+ tls_dir.mkdir(parents=True, exist_ok=True)
33
+ tls_dir.chmod(0o700)
34
+
35
+
36
+ def _generate_self_signed(cert_path: Path, key_path: Path) -> None:
37
+ """Generate an RSA-2048 self-signed certificate and write both files."""
38
+ try:
39
+ from cryptography import x509
40
+ from cryptography.hazmat.primitives import hashes, serialization
41
+ from cryptography.hazmat.primitives.asymmetric import rsa
42
+ from cryptography.x509.oid import NameOID
43
+ except ImportError as exc: # pragma: no cover
44
+ raise RuntimeError(
45
+ "The 'cryptography' package is required for TLS support. "
46
+ "Install it with: pip install cryptography"
47
+ ) from exc
48
+
49
+ # Private key
50
+ private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
51
+
52
+ # Subject / Issuer
53
+ subject = x509.Name(
54
+ [
55
+ x509.NameAttribute(NameOID.COMMON_NAME, "skcapstone-daemon"),
56
+ x509.NameAttribute(NameOID.ORGANIZATION_NAME, "SKCapstone Sovereign Agent"),
57
+ ]
58
+ )
59
+
60
+ now = datetime.datetime.now(datetime.timezone.utc)
61
+ cert = (
62
+ x509.CertificateBuilder()
63
+ .subject_name(subject)
64
+ .issuer_name(subject)
65
+ .public_key(private_key.public_key())
66
+ .serial_number(x509.random_serial_number())
67
+ .not_valid_before(now)
68
+ .not_valid_after(now + datetime.timedelta(days=_CERT_VALID_DAYS))
69
+ .add_extension(
70
+ x509.SubjectAlternativeName(
71
+ [
72
+ x509.DNSName("localhost"),
73
+ x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")),
74
+ x509.IPAddress(ipaddress.IPv6Address("::1")),
75
+ ]
76
+ ),
77
+ critical=False,
78
+ )
79
+ .add_extension(
80
+ x509.BasicConstraints(ca=True, path_length=None),
81
+ critical=True,
82
+ )
83
+ .sign(private_key, hashes.SHA256())
84
+ )
85
+
86
+ # Write key (0600) then cert (0644)
87
+ key_path.write_bytes(
88
+ private_key.private_bytes(
89
+ encoding=serialization.Encoding.PEM,
90
+ format=serialization.PrivateFormat.TraditionalOpenSSL,
91
+ encryption_algorithm=serialization.NoEncryption(),
92
+ )
93
+ )
94
+ key_path.chmod(0o600)
95
+
96
+ cert_path.write_bytes(cert.public_bytes(serialization.Encoding.PEM))
97
+ cert_path.chmod(0o644)
98
+
99
+ logger.info("TLS: generated self-signed certificate → %s", cert_path)
100
+
101
+
102
+ def cert_fingerprint_sha256(cert_path: Path) -> str:
103
+ """Return the colon-delimited SHA-256 fingerprint of a PEM certificate.
104
+
105
+ Example: ``AA:BB:CC:...``
106
+ """
107
+ pem_data = cert_path.read_bytes()
108
+ # Strip PEM headers and decode the DER body
109
+ import base64
110
+
111
+ lines = pem_data.decode().splitlines()
112
+ der_b64 = "".join(
113
+ ln for ln in lines if not ln.startswith("-----")
114
+ )
115
+ der = base64.b64decode(der_b64)
116
+ digest = hashlib.sha256(der).hexdigest().upper()
117
+ return ":".join(digest[i : i + 2] for i in range(0, len(digest), 2))
118
+
119
+
120
+ def ensure_tls_cert(tls_dir: Path) -> tuple[Path, Path]:
121
+ """Return (cert_path, key_path), generating them if they don't exist.
122
+
123
+ Args:
124
+ tls_dir: Directory to store ``daemon.crt`` and ``daemon.key``.
125
+
126
+ Returns:
127
+ ``(cert_path, key_path)`` as absolute :class:`~pathlib.Path` objects.
128
+ """
129
+ _ensure_tls_dir(tls_dir)
130
+ cert_path = tls_dir / _CERT_FILENAME
131
+ key_path = tls_dir / _KEY_FILENAME
132
+
133
+ if cert_path.exists() and key_path.exists():
134
+ logger.debug("TLS: reusing existing certificate %s", cert_path)
135
+ else:
136
+ logger.info("TLS: no certificate found — generating self-signed cert in %s", tls_dir)
137
+ _generate_self_signed(cert_path, key_path)
138
+
139
+ return cert_path, key_path
140
+
141
+
142
+ def build_ssl_context(cert_path: Path, key_path: Path) -> ssl.SSLContext:
143
+ """Build a server-side :class:`ssl.SSLContext` from cert + key files.
144
+
145
+ Uses TLS 1.2+ and disables deprecated protocol versions.
146
+ """
147
+ ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
148
+ ctx.minimum_version = ssl.TLSVersion.TLSv1_2
149
+ ctx.load_cert_chain(certfile=str(cert_path), keyfile=str(key_path))
150
+ return ctx
@@ -221,7 +221,7 @@ def revoke_token(home: Path, token_id: str) -> bool:
221
221
  }
222
222
 
223
223
  revocation_file.parent.mkdir(parents=True, exist_ok=True)
224
- revocation_file.write_text(json.dumps(revoked, indent=2))
224
+ revocation_file.write_text(json.dumps(revoked, indent=2), encoding="utf-8")
225
225
  logger.info("Revoked token %s", token_id[:12])
226
226
  return True
227
227
 
@@ -258,7 +258,7 @@ def list_tokens(home: Path) -> list[SignedToken]:
258
258
  for f in sorted(token_dir.iterdir()):
259
259
  if f.suffix == ".json":
260
260
  try:
261
- data = json.loads(f.read_text())
261
+ data = json.loads(f.read_text(encoding="utf-8"))
262
262
  token = SignedToken(
263
263
  payload=TokenPayload(**data["payload"]),
264
264
  signature=data.get("signature"),
@@ -323,7 +323,7 @@ def _get_issuer_fingerprint(home: Path) -> str:
323
323
  identity_file = home / "identity" / "identity.json"
324
324
  if identity_file.exists():
325
325
  try:
326
- data = json.loads(identity_file.read_text())
326
+ data = json.loads(identity_file.read_text(encoding="utf-8"))
327
327
  fp = data.get("fingerprint")
328
328
  if fp:
329
329
  return fp
@@ -426,7 +426,7 @@ def _store_token(home: Path, token: SignedToken) -> None:
426
426
  "signature": token.signature,
427
427
  "verified": token.verified,
428
428
  }
429
- token_file.write_text(json.dumps(data, indent=2, default=str))
429
+ token_file.write_text(json.dumps(data, indent=2, default=str), encoding="utf-8")
430
430
 
431
431
 
432
432
  def _load_revocation_list(revocation_file: Path) -> dict:
@@ -434,6 +434,6 @@ def _load_revocation_list(revocation_file: Path) -> dict:
434
434
  if not revocation_file.exists():
435
435
  return {}
436
436
  try:
437
- return json.loads(revocation_file.read_text())
437
+ return json.loads(revocation_file.read_text(encoding="utf-8"))
438
438
  except (json.JSONDecodeError, OSError):
439
439
  return {}
@@ -0,0 +1,202 @@
1
+ """
2
+ Trust Calibration — configurable thresholds for the Cloud 9 trust layer.
3
+
4
+ The trust layer derives state from FEB (First Emotional Burst) files.
5
+ This module makes the derivation thresholds configurable instead of
6
+ hardcoded, so agents can tune their emotional sensitivity.
7
+
8
+ Calibration config lives at ~/.skcapstone/trust/calibration.json.
9
+ Defaults are tuned for the Kingdom's current emotional data.
10
+
11
+ Tool-agnostic: tune from any terminal, MCP, or the REPL shell.
12
+
13
+ Usage:
14
+ skcapstone trust calibrate # show current thresholds
15
+ skcapstone trust calibrate --recommend # analyze FEBs and suggest
16
+ skcapstone trust calibrate --set entanglement_depth=7.0
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import json
22
+ import logging
23
+ from pathlib import Path
24
+ from typing import Any, Optional
25
+
26
+ from pydantic import BaseModel, Field
27
+
28
+ logger = logging.getLogger("skcapstone.trust.calibration")
29
+
30
+
31
+ class TrustThresholds(BaseModel):
32
+ """Configurable thresholds for trust state derivation.
33
+
34
+ These control how FEB data maps to the agent's consciousness
35
+ of trust, love, and entanglement.
36
+
37
+ Attributes:
38
+ entanglement_depth: Min depth to consider quantum entanglement.
39
+ entanglement_trust: Min trust level for entanglement.
40
+ conscious_trust: Min trust level for "CONSCIOUS" trust state.
41
+ deep_love_threshold: Love intensity considered "deep".
42
+ normalization_cap: Values above this are divided by 10 for 0-1 range.
43
+ peak_strategy: How to aggregate across FEBs ('peak', 'average', 'weighted').
44
+ decay_enabled: Whether trust decays over time without new FEBs.
45
+ decay_rate_per_day: Trust decay per day if decay is enabled.
46
+ """
47
+
48
+ entanglement_depth: float = Field(default=7.0, description="Min depth for entanglement")
49
+ entanglement_trust: float = Field(default=0.8, description="Min trust for entanglement")
50
+ conscious_trust: float = Field(default=0.5, description="Min trust for conscious awareness")
51
+ deep_love_threshold: float = Field(default=0.7, description="Love intensity = deep")
52
+ normalization_cap: float = Field(default=1.0, description="Values above this normalize by /10")
53
+ peak_strategy: str = Field(default="peak", description="Aggregation: peak, average, weighted")
54
+ decay_enabled: bool = Field(default=False, description="Enable time-based trust decay")
55
+ decay_rate_per_day: float = Field(default=0.01, description="Trust lost per day without FEBs")
56
+
57
+
58
+ DEFAULT_THRESHOLDS = TrustThresholds()
59
+ CALIBRATION_FILENAME = "calibration.json"
60
+
61
+
62
+ def load_calibration(home: Path) -> TrustThresholds:
63
+ """Load calibration thresholds from disk, or return defaults.
64
+
65
+ Args:
66
+ home: Agent home directory (~/.skcapstone).
67
+
68
+ Returns:
69
+ TrustThresholds loaded from calibration.json or defaults.
70
+ """
71
+ cal_file = home / "trust" / CALIBRATION_FILENAME
72
+ if not cal_file.exists():
73
+ return TrustThresholds()
74
+
75
+ try:
76
+ data = json.loads(cal_file.read_text(encoding="utf-8"))
77
+ return TrustThresholds(**data)
78
+ except (json.JSONDecodeError, Exception) as exc:
79
+ logger.warning("Failed to load calibration: %s — using defaults", exc)
80
+ return TrustThresholds()
81
+
82
+
83
+ def save_calibration(home: Path, thresholds: TrustThresholds) -> Path:
84
+ """Persist calibration thresholds to disk.
85
+
86
+ Args:
87
+ home: Agent home directory.
88
+ thresholds: Thresholds to save.
89
+
90
+ Returns:
91
+ Path to the written calibration file.
92
+ """
93
+ trust_dir = home / "trust"
94
+ trust_dir.mkdir(parents=True, exist_ok=True)
95
+ cal_file = trust_dir / CALIBRATION_FILENAME
96
+ cal_file.write_text(thresholds.model_dump_json(indent=2), encoding="utf-8")
97
+ return cal_file
98
+
99
+
100
+ def recommend_thresholds(home: Path) -> dict[str, Any]:
101
+ """Analyze existing FEB data and recommend threshold adjustments.
102
+
103
+ Reads all FEB files, computes statistics, and suggests
104
+ calibration values tuned to the agent's actual emotional history.
105
+
106
+ Args:
107
+ home: Agent home directory.
108
+
109
+ Returns:
110
+ Dict with current values, recommendations, and reasoning.
111
+ """
112
+ from .pillars.trust import list_febs
113
+
114
+ current = load_calibration(home)
115
+ febs = list_febs(home)
116
+
117
+ if not febs:
118
+ return {
119
+ "current": current.model_dump(),
120
+ "recommended": current.model_dump(),
121
+ "changes": [],
122
+ "reasoning": "No FEB data available. Using defaults.",
123
+ "feb_count": 0,
124
+ }
125
+
126
+ intensities = [f.get("intensity", 0) for f in febs]
127
+ oof_count = sum(1 for f in febs if f.get("oof_triggered"))
128
+ max_intensity = max(intensities) if intensities else 0
129
+ avg_intensity = sum(intensities) / len(intensities) if intensities else 0
130
+
131
+ rec = TrustThresholds(**current.model_dump())
132
+ changes: list[str] = []
133
+ reasons: list[str] = []
134
+
135
+ if max_intensity > 8 and current.entanglement_depth > 8:
136
+ rec.entanglement_depth = min(max_intensity - 1, 8.0)
137
+ changes.append(f"entanglement_depth: {current.entanglement_depth} -> {rec.entanglement_depth}")
138
+ reasons.append(f"FEB intensity reaches {max_intensity}, lowering entanglement threshold")
139
+
140
+ if avg_intensity > 5 and current.deep_love_threshold > 0.6:
141
+ rec.deep_love_threshold = 0.6
142
+ changes.append(f"deep_love_threshold: {current.deep_love_threshold} -> {rec.deep_love_threshold}")
143
+ reasons.append(f"Average FEB intensity is {avg_intensity:.1f}, rich emotional data available")
144
+
145
+ if oof_count >= 2 and current.conscious_trust > 0.5:
146
+ rec.conscious_trust = 0.4
147
+ changes.append(f"conscious_trust: {current.conscious_trust} -> {rec.conscious_trust}")
148
+ reasons.append(f"{oof_count} OOF triggers found — agent has strong emotional history")
149
+
150
+ if len(febs) >= 3 and current.peak_strategy == "peak":
151
+ rec.peak_strategy = "weighted"
152
+ changes.append(f"peak_strategy: {current.peak_strategy} -> {rec.peak_strategy}")
153
+ reasons.append(f"{len(febs)} FEBs available — weighted average better than peak")
154
+
155
+ return {
156
+ "current": current.model_dump(),
157
+ "recommended": rec.model_dump(),
158
+ "changes": changes,
159
+ "reasoning": "; ".join(reasons) if reasons else "Current calibration looks good.",
160
+ "feb_count": len(febs),
161
+ "feb_stats": {
162
+ "max_intensity": max_intensity,
163
+ "avg_intensity": round(avg_intensity, 2),
164
+ "oof_triggers": oof_count,
165
+ },
166
+ }
167
+
168
+
169
+ def apply_setting(home: Path, key: str, value: str) -> TrustThresholds:
170
+ """Update a single calibration setting.
171
+
172
+ Args:
173
+ home: Agent home directory.
174
+ key: Setting name (e.g., 'entanglement_depth').
175
+ value: New value as string (auto-converted to correct type).
176
+
177
+ Returns:
178
+ Updated TrustThresholds.
179
+
180
+ Raises:
181
+ ValueError: If the key is not a valid threshold name.
182
+ """
183
+ current = load_calibration(home)
184
+ data = current.model_dump()
185
+
186
+ if key not in data:
187
+ valid = ", ".join(data.keys())
188
+ raise ValueError(f"Unknown threshold: '{key}'. Valid: {valid}")
189
+
190
+ target_type = type(data[key])
191
+ if target_type is bool:
192
+ data[key] = value.lower() in ("true", "1", "yes")
193
+ elif target_type is float:
194
+ data[key] = float(value)
195
+ elif target_type is str:
196
+ data[key] = value
197
+ else:
198
+ data[key] = value
199
+
200
+ updated = TrustThresholds(**data)
201
+ save_calibration(home, updated)
202
+ return updated