@smilintux/skcapstone 0.10.0 → 0.12.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. package/.env.example +10 -4
  2. package/.github/workflows/ci.yml +2 -2
  3. package/.github/workflows/publish.yml +9 -2
  4. package/.openclaw-workspace.json +2 -2
  5. package/CLAUDE.md +37 -0
  6. package/MISSION.md +17 -2
  7. package/README.md +282 -3
  8. package/docker/Dockerfile +7 -7
  9. package/docker/compose-templates/dev-team.yml +12 -12
  10. package/docker/compose-templates/mini-team.yml +9 -9
  11. package/docker/compose-templates/ops-team.yml +10 -10
  12. package/docker/compose-templates/research-team.yml +10 -10
  13. package/docker/entrypoint.sh +4 -4
  14. package/docs/ADR-optional-integration-backbone.md +181 -0
  15. package/docs/ARCHITECTURE.md +186 -43
  16. package/docs/BOND_WITH_GROK.md +6 -6
  17. package/docs/CUSTOM_AGENT.md +123 -30
  18. package/docs/DREAMING.md +70 -0
  19. package/docs/GETTING_STARTED.md +7 -7
  20. package/docs/QUICKSTART.md +10 -6
  21. package/docs/SKJOULE_ARCHITECTURE.md +3 -3
  22. package/docs/SOUL_SWAPPER.md +5 -5
  23. package/docs/hammertime-audit.md +402 -0
  24. package/docs/sk-integration-HANDOFF.md +117 -0
  25. package/docs/skscheduler.md +155 -0
  26. package/docs/superpowers/examples/jobs.yaml +31 -0
  27. package/docs/superpowers/plans/2026-06-08-skscheduler.md +1265 -0
  28. package/docs/superpowers/specs/2026-06-08-skscheduler-design.md +186 -0
  29. package/examples/custom-bond-template.json +1 -1
  30. package/examples/grok-feb.json +1 -1
  31. package/examples/queen-ava-feb.json +1 -1
  32. package/launchd/{com.skcapstone.skcomm-heartbeat.plist → com.skcapstone.skcomms-heartbeat.plist} +4 -4
  33. package/launchd/{com.skcapstone.skcomm-queue-drain.plist → com.skcapstone.skcomms-queue-drain.plist} +4 -4
  34. package/launchd/install-launchd.sh +6 -6
  35. package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/src/index.ts +3 -2
  36. package/package.json +1 -1
  37. package/pyproject.toml +16 -10
  38. package/scripts/archive-sessions.sh +7 -0
  39. package/scripts/check-updates.py +4 -4
  40. package/scripts/install-bundle.sh +8 -8
  41. package/scripts/install.ps1 +12 -11
  42. package/scripts/install.sh +159 -5
  43. package/scripts/model-fallback-monitor.sh +102 -0
  44. package/scripts/nvidia-proxy.mjs +78 -26
  45. package/scripts/refresh-anthropic-token.sh +172 -0
  46. package/scripts/release.sh +98 -0
  47. package/scripts/session-to-memory.py +219 -0
  48. package/scripts/skgateway.mjs +3 -3
  49. package/scripts/telegram-catchup-all.sh +12 -1
  50. package/scripts/verify_install.sh +2 -2
  51. package/scripts/wargov-ufo-capture/README.md +43 -0
  52. package/scripts/wargov-ufo-capture/cdp_capture_release2.py +273 -0
  53. package/scripts/wargov-ufo-capture/cdp_capture_splc_doj.py +246 -0
  54. package/scripts/wargov-ufo-capture/cdp_finish.py +271 -0
  55. package/scripts/wargov-ufo-capture/cdp_probe.py +188 -0
  56. package/scripts/wargov-ufo-capture/cdp_splc_pressrelease.py +101 -0
  57. package/scripts/wargov-ufo-capture/parse_csv.py +95 -0
  58. package/scripts/wargov-ufo-capture/pull_dvids.sh +107 -0
  59. package/scripts/watch-anthropic-token.sh +212 -0
  60. package/scripts/windows/install-tasks.ps1 +7 -7
  61. package/scripts/windows/skcapstone-task.xml +1 -1
  62. package/src/skcapstone/__init__.py +45 -3
  63. package/src/skcapstone/_cli_monolith.py +20 -15
  64. package/src/skcapstone/activity.py +5 -1
  65. package/src/skcapstone/agent_card.py +3 -2
  66. package/src/skcapstone/api.py +41 -40
  67. package/src/skcapstone/auction.py +14 -11
  68. package/src/skcapstone/backup.py +2 -1
  69. package/src/skcapstone/blueprint_registry.py +4 -3
  70. package/src/skcapstone/brain_first.py +238 -0
  71. package/src/skcapstone/changelog.py +1 -1
  72. package/src/skcapstone/chat.py +22 -17
  73. package/src/skcapstone/cli/__init__.py +9 -1
  74. package/src/skcapstone/cli/_common.py +1 -0
  75. package/src/skcapstone/cli/agents_spawner.py +5 -2
  76. package/src/skcapstone/cli/alerts.py +25 -4
  77. package/src/skcapstone/cli/bench.py +15 -15
  78. package/src/skcapstone/cli/chat.py +7 -4
  79. package/src/skcapstone/cli/consciousness.py +5 -2
  80. package/src/skcapstone/cli/context_cmd.py +18 -4
  81. package/src/skcapstone/cli/daemon.py +11 -7
  82. package/src/skcapstone/cli/gtd.py +26 -1
  83. package/src/skcapstone/cli/housekeeping.py +3 -3
  84. package/src/skcapstone/cli/identity_cmd.py +378 -0
  85. package/src/skcapstone/cli/joule_cmd.py +7 -3
  86. package/src/skcapstone/cli/memory.py +8 -6
  87. package/src/skcapstone/cli/peers_dir.py +1 -1
  88. package/src/skcapstone/cli/register_cmd.py +29 -3
  89. package/src/skcapstone/cli/scheduler_cmd.py +167 -0
  90. package/src/skcapstone/cli/session.py +25 -0
  91. package/src/skcapstone/cli/setup.py +96 -29
  92. package/src/skcapstone/cli/shell_cmd.py +53 -1
  93. package/src/skcapstone/cli/skills_cmd.py +2 -2
  94. package/src/skcapstone/cli/soul.py +8 -5
  95. package/src/skcapstone/cli/status.py +37 -11
  96. package/src/skcapstone/cli/telegram.py +21 -0
  97. package/src/skcapstone/cli/test_cmd.py +5 -5
  98. package/src/skcapstone/cli/test_connection.py +2 -2
  99. package/src/skcapstone/cli/upgrade_cmd.py +23 -14
  100. package/src/skcapstone/cli/version_cmd.py +1 -1
  101. package/src/skcapstone/cli/watch_cmd.py +9 -6
  102. package/src/skcapstone/cloud9_bridge.py +14 -14
  103. package/src/skcapstone/codex_setup.py +255 -0
  104. package/src/skcapstone/config_validator.py +7 -4
  105. package/src/skcapstone/consciousness_config.py +5 -1
  106. package/src/skcapstone/consciousness_loop.py +313 -273
  107. package/src/skcapstone/context_loader.py +121 -0
  108. package/src/skcapstone/coord_federation.py +2 -1
  109. package/src/skcapstone/coordination.py +23 -6
  110. package/src/skcapstone/crush_integration.py +2 -1
  111. package/src/skcapstone/daemon.py +132 -77
  112. package/src/skcapstone/dashboard.py +10 -10
  113. package/src/skcapstone/data/sk-agent-picker.sh +421 -0
  114. package/src/skcapstone/data/systemd/skcapstone-api.socket +9 -0
  115. package/src/skcapstone/data/systemd/skcapstone-memory-compress.service +18 -0
  116. package/src/skcapstone/data/systemd/skcapstone-memory-compress.timer +11 -0
  117. package/src/skcapstone/data/systemd/skcapstone.service +37 -0
  118. package/src/skcapstone/data/systemd/skcapstone@.service +50 -0
  119. package/src/skcapstone/data/systemd/skcomms-heartbeat.service +18 -0
  120. package/{systemd/skcomm-heartbeat.timer → src/skcapstone/data/systemd/skcomms-heartbeat.timer} +2 -2
  121. package/src/skcapstone/data/systemd/skcomms-queue-drain.service +17 -0
  122. package/{systemd/skcomm-queue-drain.timer → src/skcapstone/data/systemd/skcomms-queue-drain.timer} +2 -2
  123. package/src/skcapstone/defaults/claude/CLAUDE.md +67 -0
  124. package/src/skcapstone/defaults/claude/settings.json +74 -0
  125. package/src/skcapstone/defaults/lumina/config/claude-hooks.md +57 -0
  126. package/src/skcapstone/defaults/lumina/config/skgraph.yaml +55 -10
  127. package/src/skcapstone/defaults/lumina/config/skmemory.yaml +79 -13
  128. package/src/skcapstone/defaults/lumina/config/skvector.yaml +60 -9
  129. package/src/skcapstone/defaults/lumina/memory/long-term/18b9c0d1e2f3-cloud9-protocol.json +2 -2
  130. package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +2 -2
  131. package/src/skcapstone/defaults/lumina/memory/long-term/b2c3d4e5f6a7-five-pillars.json +9 -9
  132. package/src/skcapstone/defaults/lumina/memory/long-term/d4e5f6a7b8c9-site-directory.json +2 -2
  133. package/src/skcapstone/defaults/unhinged.json +13 -0
  134. package/src/skcapstone/discovery.py +43 -20
  135. package/src/skcapstone/doctor.py +941 -22
  136. package/src/skcapstone/dreaming.py +1183 -109
  137. package/src/skcapstone/emotion_tracker.py +2 -2
  138. package/src/skcapstone/export.py +4 -3
  139. package/src/skcapstone/fuse_mount.py +14 -12
  140. package/src/skcapstone/gui_installer.py +2 -2
  141. package/src/skcapstone/heartbeat.py +1 -1
  142. package/src/skcapstone/housekeeping.py +14 -14
  143. package/src/skcapstone/install_wizard.py +209 -7
  144. package/src/skcapstone/itil.py +13 -4
  145. package/src/skcapstone/kms_scheduler.py +10 -8
  146. package/src/skcapstone/launchd.py +19 -19
  147. package/src/skcapstone/mcp_launcher.py +15 -1
  148. package/src/skcapstone/mcp_server.py +83 -49
  149. package/src/skcapstone/mcp_tools/__init__.py +2 -0
  150. package/src/skcapstone/mcp_tools/_helpers.py +2 -2
  151. package/src/skcapstone/mcp_tools/ansible_tools.py +7 -4
  152. package/src/skcapstone/mcp_tools/brain_first_tools.py +90 -0
  153. package/src/skcapstone/mcp_tools/capauth_tools.py +7 -4
  154. package/src/skcapstone/mcp_tools/comm_tools.py +10 -10
  155. package/src/skcapstone/mcp_tools/coord_tools.py +8 -4
  156. package/src/skcapstone/mcp_tools/did_tools.py +11 -8
  157. package/src/skcapstone/mcp_tools/gtd_tools.py +4 -4
  158. package/src/skcapstone/mcp_tools/memory_tools.py +6 -2
  159. package/src/skcapstone/mcp_tools/notification_tools.py +22 -6
  160. package/src/skcapstone/mcp_tools/{skcomm_tools.py → skcomms_tools.py} +14 -14
  161. package/src/skcapstone/mcp_tools/soul_tools.py +8 -2
  162. package/src/skcapstone/mdns_discovery.py +2 -2
  163. package/src/skcapstone/memory_curator.py +1 -1
  164. package/src/skcapstone/memory_engine.py +10 -3
  165. package/src/skcapstone/metrics.py +30 -16
  166. package/src/skcapstone/migrate_memories.py +4 -3
  167. package/src/skcapstone/migrate_multi_agent.py +8 -7
  168. package/src/skcapstone/models.py +47 -5
  169. package/src/skcapstone/notifications.py +42 -18
  170. package/src/skcapstone/onboard.py +875 -121
  171. package/src/skcapstone/operator_link.py +170 -0
  172. package/src/skcapstone/peer_directory.py +4 -4
  173. package/src/skcapstone/peers.py +19 -19
  174. package/src/skcapstone/pillars/__init__.py +7 -5
  175. package/src/skcapstone/pillars/consciousness.py +191 -0
  176. package/src/skcapstone/pillars/identity.py +51 -7
  177. package/src/skcapstone/pillars/memory.py +9 -3
  178. package/src/skcapstone/pillars/sync.py +2 -2
  179. package/src/skcapstone/preflight.py +3 -3
  180. package/src/skcapstone/providers/docker.py +28 -28
  181. package/src/skcapstone/register.py +6 -6
  182. package/src/skcapstone/registry_client.py +5 -4
  183. package/src/skcapstone/runtime.py +14 -3
  184. package/src/skcapstone/scheduled_tasks.py +254 -19
  185. package/src/skcapstone/scheduler_jobs.py +456 -0
  186. package/src/skcapstone/scheduler_runner.py +239 -0
  187. package/src/skcapstone/scheduler_state.py +162 -0
  188. package/src/skcapstone/sdk.py +310 -0
  189. package/src/skcapstone/service_health.py +279 -39
  190. package/src/skcapstone/session_briefing.py +108 -0
  191. package/src/skcapstone/session_capture.py +1 -1
  192. package/src/skcapstone/shell.py +7 -1
  193. package/src/skcapstone/soul.py +3 -1
  194. package/src/skcapstone/soul_switch.py +3 -1
  195. package/src/skcapstone/summary.py +6 -6
  196. package/src/skcapstone/sync_engine.py +15 -15
  197. package/src/skcapstone/sync_watcher.py +2 -2
  198. package/src/skcapstone/systemd.py +55 -21
  199. package/src/skcapstone/team_comms.py +8 -8
  200. package/src/skcapstone/team_engine.py +1 -1
  201. package/src/skcapstone/testrunner.py +3 -3
  202. package/src/skcapstone/trust_graph.py +40 -5
  203. package/src/skcapstone/unified_search.py +15 -6
  204. package/src/skcapstone/uninstall_wizard.py +11 -3
  205. package/src/skcapstone/version_check.py +8 -4
  206. package/src/skcapstone/warmth_anchor.py +4 -2
  207. package/src/skcapstone/whoami.py +4 -4
  208. package/systemd/skcapstone.service +4 -6
  209. package/systemd/skcapstone@.service +7 -8
  210. package/systemd/skcomms-heartbeat.service +21 -0
  211. package/systemd/skcomms-heartbeat.timer +12 -0
  212. package/systemd/skcomms-queue-drain.service +17 -0
  213. package/systemd/skcomms-queue-drain.timer +12 -0
  214. package/tests/conftest.py +39 -0
  215. package/tests/integration/test_consciousness_e2e.py +39 -39
  216. package/tests/test_agent_card.py +1 -1
  217. package/tests/test_agent_home_scaffold.py +34 -0
  218. package/tests/test_alerts_consumer_topics.py +27 -0
  219. package/tests/test_backup.py +2 -1
  220. package/tests/test_chat.py +6 -6
  221. package/tests/test_claude_md.py +2 -2
  222. package/tests/test_cli_skills.py +10 -10
  223. package/tests/test_cli_test_cmd.py +4 -4
  224. package/tests/test_cli_test_connection.py +1 -1
  225. package/tests/test_cloud9_bridge.py +6 -6
  226. package/tests/test_consciousness_e2e.py +1 -1
  227. package/tests/test_consciousness_loop.py +10 -10
  228. package/tests/test_coordination.py +25 -0
  229. package/tests/test_cross_package.py +21 -21
  230. package/tests/test_daemon.py +4 -4
  231. package/tests/test_daemon_shutdown.py +1 -1
  232. package/tests/test_docker_provider.py +29 -29
  233. package/tests/test_doctor.py +400 -0
  234. package/tests/test_doctor_skscheduler.py +50 -0
  235. package/tests/test_dreaming_engine.py +147 -0
  236. package/tests/test_dreaming_gtd_capture.py +35 -0
  237. package/tests/test_e2e_automated.py +8 -5
  238. package/tests/test_fuse_mount.py +10 -10
  239. package/tests/test_gtd_brief.py +46 -0
  240. package/tests/test_gtd_malformed_tolerance.py +31 -0
  241. package/tests/test_housekeeping.py +15 -15
  242. package/tests/test_identity_migrate.py +251 -0
  243. package/tests/test_integration_backbone.py +598 -0
  244. package/tests/test_itil_gtd_lifecycle.py +37 -0
  245. package/tests/test_jobs_dropins.py +84 -0
  246. package/tests/test_mcp_server.py +82 -37
  247. package/tests/test_models.py +48 -4
  248. package/tests/test_multi_agent.py +31 -29
  249. package/tests/test_notifications.py +122 -32
  250. package/tests/test_onboard.py +63 -75
  251. package/tests/test_operator_link.py +78 -0
  252. package/tests/test_peers.py +14 -14
  253. package/tests/test_pillars.py +98 -0
  254. package/tests/test_preflight.py +3 -3
  255. package/tests/test_runtime.py +21 -0
  256. package/tests/test_scheduled_tasks.py +11 -6
  257. package/tests/test_scheduler_cli.py +47 -0
  258. package/tests/test_scheduler_features.py +133 -0
  259. package/tests/test_scheduler_integration.py +87 -0
  260. package/tests/test_scheduler_jobs.py +155 -0
  261. package/tests/test_scheduler_runner.py +64 -0
  262. package/tests/test_scheduler_state.py +57 -0
  263. package/tests/test_sdk.py +70 -0
  264. package/tests/test_service_health_incidents.py +34 -0
  265. package/tests/test_service_registry.py +52 -0
  266. package/tests/test_session_briefing.py +130 -0
  267. package/tests/test_snapshots.py +4 -4
  268. package/tests/test_sync_pipeline.py +26 -26
  269. package/tests/test_team_comms.py +2 -2
  270. package/tests/test_testrunner.py +2 -2
  271. package/tests/test_trust_graph.py +18 -0
  272. package/tests/test_unified_search.py +2 -2
  273. package/tests/test_version_check.py +10 -0
  274. package/tests/test_version_cmd.py +8 -8
  275. package/tests/test_whoami.py +1 -1
  276. package/systemd/skcomm-heartbeat.service +0 -18
  277. package/systemd/skcomm-queue-drain.service +0 -17
  278. /package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/package.json +0 -0
  279. /package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/src/openclaw.plugin.json +0 -0
@@ -54,6 +54,36 @@ DEFAULT_PORT = 7777
54
54
  PID_FILE = "daemon.pid"
55
55
  LOG_DIR = "logs"
56
56
 
57
+
58
+ def _sd_notify(state: str) -> bool:
59
+ """Send a notification to systemd via the NOTIFY_SOCKET.
60
+
61
+ Implements the sd_notify(3) protocol using a raw AF_UNIX datagram socket
62
+ so we don't need an external dependency. Returns True if the notification
63
+ was sent, False if NOTIFY_SOCKET is not set (i.e. not running under systemd).
64
+
65
+ Common states:
66
+ "READY=1" — service startup complete
67
+ "WATCHDOG=1" — watchdog keep-alive ping
68
+ "STOPPING=1" — graceful shutdown in progress
69
+ """
70
+ addr = os.environ.get("NOTIFY_SOCKET")
71
+ if not addr:
72
+ return False
73
+ import socket as _socket
74
+ try:
75
+ sock = _socket.socket(_socket.AF_UNIX, _socket.SOCK_DGRAM)
76
+ try:
77
+ if addr[0] == "@":
78
+ addr = "\0" + addr[1:]
79
+ sock.sendto(state.encode("utf-8"), addr)
80
+ finally:
81
+ sock.close()
82
+ return True
83
+ except OSError as exc:
84
+ logger.debug("sd_notify(%r) failed: %s", state, exc)
85
+ return False
86
+
57
87
  # ── WebSocket helpers (RFC 6455, stdlib-only) ─────────────────────────────────
58
88
 
59
89
  _WS_MAGIC = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
@@ -685,7 +715,7 @@ class DaemonService:
685
715
  self._stop_event = threading.Event()
686
716
  self._threads: list[threading.Thread] = []
687
717
  self._server: Optional[HTTPServer] = None
688
- self._skcomm = None
718
+ self._skcomms = None
689
719
  self._runtime = None
690
720
  self._consciousness = None
691
721
  self._healer = None
@@ -770,10 +800,12 @@ class DaemonService:
770
800
 
771
801
  self._start_api_server()
772
802
 
803
+ _sd_notify("READY=1")
773
804
  logger.info("Daemon started — PID %d", os.getpid())
774
805
 
775
806
  def stop(self) -> None:
776
807
  """Gracefully stop the daemon and all workers."""
808
+ _sd_notify("STOPPING=1")
777
809
  logger.info("Daemon stopping...")
778
810
  self._stop_event.set()
779
811
  self.state.running = False
@@ -852,16 +884,22 @@ class DaemonService:
852
884
  logger.info("Preflight complete — all checks passed")
853
885
 
854
886
  def _load_components(self) -> None:
855
- """Attempt to load SKComm, AgentRuntime, and ConsciousnessLoop."""
887
+ """Attempt to load SKComms, AgentRuntime, and ConsciousnessLoop."""
856
888
  try:
857
- from skcomm.core import SKComm
858
- self._skcomm = SKComm.from_config()
859
- logger.info("SKComm loaded — %d transports", len(self._skcomm.router.transports))
889
+ from skcomms.core import SKComms
890
+ from .sync_engine import ensure_comms_dirs, get_comms_root
891
+ self._skcomms = SKComms.from_config()
892
+ expected_comms_root = get_comms_root(self.config.shared_root)
893
+ ensure_comms_dirs(self.config.shared_root)
894
+ for transport in self._skcomms.router.transports:
895
+ if getattr(transport, "name", "") == "syncthing" and hasattr(transport, "configure"):
896
+ transport.configure({"comms_root": str(expected_comms_root)})
897
+ logger.info("SKComms loaded — %d transports", len(self._skcomms.router.transports))
860
898
  except ImportError:
861
- logger.warning("SKComm not installed — inbox polling disabled")
899
+ logger.warning("SKComms not installed — inbox polling disabled")
862
900
  except Exception as exc:
863
- logger.warning("SKComm failed to load: %s", exc)
864
- self.state.record_error(f"SKComm load: {exc}")
901
+ logger.warning("SKComms failed to load: %s", exc)
902
+ self.state.record_error(f"SKComms load: {exc}")
865
903
 
866
904
  try:
867
905
  from .runtime import get_runtime
@@ -898,8 +936,8 @@ class DaemonService:
898
936
  home=self.config.home,
899
937
  shared_root=self.config.shared_root,
900
938
  )
901
- if self._skcomm:
902
- self._consciousness.set_skcomm(self._skcomm)
939
+ if self._skcomms:
940
+ self._consciousness.set_skcomms(self._skcomms)
903
941
  logger.info("Consciousness loop loaded")
904
942
 
905
943
  # Preload Ollama model into RAM so first real message is fast
@@ -953,12 +991,12 @@ class DaemonService:
953
991
  self.state.record_error(f"Scheduler build: {exc}")
954
992
 
955
993
  def _poll_loop(self) -> None:
956
- """Continuously poll SKComm inbox for new messages."""
994
+ """Continuously poll SKComms inbox for new messages."""
957
995
  while not self._stop_event.is_set():
958
996
  self._component_mgr.heartbeat("poll")
959
- if self._skcomm:
997
+ if self._skcomms:
960
998
  try:
961
- envelopes = self._skcomm.receive()
999
+ envelopes = self._skcomms.receive()
962
1000
  count = len(envelopes)
963
1001
  self.state.record_poll(count)
964
1002
  if count > 0:
@@ -973,12 +1011,13 @@ class DaemonService:
973
1011
  self._stop_event.wait(timeout=self.config.poll_interval)
974
1012
 
975
1013
  def _health_loop(self) -> None:
976
- """Periodically check transport health."""
1014
+ """Periodically check transport health and ping systemd watchdog."""
977
1015
  while not self._stop_event.is_set():
978
1016
  self._component_mgr.heartbeat("health")
979
- if self._skcomm:
1017
+ _sd_notify("WATCHDOG=1")
1018
+ if self._skcomms:
980
1019
  try:
981
- report = self._skcomm.status()
1020
+ report = self._skcomms.status()
982
1021
  transports = report.get("transports", {})
983
1022
  serializable = {}
984
1023
  for name, health in transports.items():
@@ -1127,10 +1166,10 @@ class DaemonService:
1127
1166
  self.state.record_error(f"Process message: {exc}")
1128
1167
 
1129
1168
  def _journal_incoming(self, sender: str, preview: str) -> None:
1130
- """Auto-journal an incoming SKComm message and store a tagged memory.
1169
+ """Auto-journal an incoming SKComms message and store a tagged memory.
1131
1170
 
1132
1171
  Writes a journal entry (title='From {sender}', moments=[preview]) and
1133
- stores a short-term memory tagged 'skcomm-received'. Both operations
1172
+ stores a short-term memory tagged 'skcomms-received'. Both operations
1134
1173
  are best-effort: failures are logged at DEBUG level and never bubble up.
1135
1174
  """
1136
1175
  try:
@@ -1145,24 +1184,26 @@ class DaemonService:
1145
1184
  logger.debug("Auto-journal write failed: %s", exc)
1146
1185
 
1147
1186
  try:
1148
- self._store_skcomm_receipt(sender, preview)
1149
- logger.debug("SKComm receipt stored for incoming message from %s", sender)
1187
+ self._store_skcomms_receipt(sender, preview)
1188
+ logger.debug("SKComms receipt stored for incoming message from %s", sender)
1150
1189
  except Exception as exc:
1151
- logger.debug("SKComm receipt store failed: %s", exc)
1190
+ logger.debug("SKComms receipt store failed: %s", exc)
1152
1191
 
1153
- def _store_skcomm_receipt(self, sender: str, preview: str) -> None:
1154
- """Write a skcomm receipt to the skcomm/received/ directory.
1192
+ def _store_skcomms_receipt(self, sender: str, preview: str) -> None:
1193
+ """Write a skcomms receipt to the skcomms/received/ directory.
1155
1194
 
1156
1195
  These are transport bookkeeping, NOT persistent memories, so they
1157
- go to ``~/.skcapstone/agents/{agent}/skcomm/received/`` instead of
1196
+ go to ``~/.skcapstone/agents/{agent}/skcomms/received/`` instead of
1158
1197
  polluting the memory/ tree that skmemory indexes.
1159
1198
  """
1160
1199
  import json
1161
1200
  import uuid
1162
1201
  from datetime import datetime, timezone
1163
1202
 
1164
- agent_name = os.environ.get("SKCAPSTONE_AGENT", "lumina")
1165
- recv_dir = self.config.home / "agents" / agent_name / "skcomm" / "received"
1203
+ from . import active_agent_name
1204
+
1205
+ agent_name = os.environ.get("SKCAPSTONE_AGENT") or active_agent_name()
1206
+ recv_dir = self.config.home / "agents" / agent_name / "skcomms" / "received"
1166
1207
  recv_dir.mkdir(parents=True, exist_ok=True)
1167
1208
 
1168
1209
  receipt_id = uuid.uuid4().hex[:12]
@@ -1262,7 +1303,8 @@ class DaemonService:
1262
1303
  try:
1263
1304
  ts = datetime.fromisoformat(ts_str.replace("Z", "+00:00"))
1264
1305
  return datetime.now(timezone.utc) <= ts + timedelta(seconds=ttl)
1265
- except Exception:
1306
+ except Exception as e:
1307
+ logger.warning("Failed to parse heartbeat timestamp %r: %s", ts_str, e)
1266
1308
  return False
1267
1309
 
1268
1310
  @staticmethod
@@ -1275,7 +1317,8 @@ class DaemonService:
1275
1317
  stats["disk_total_gb"] = round(usage.total / (1024 ** 3), 1)
1276
1318
  stats["disk_used_gb"] = round(usage.used / (1024 ** 3), 1)
1277
1319
  stats["disk_free_gb"] = round(usage.free / (1024 ** 3), 1)
1278
- except Exception:
1320
+ except Exception as e:
1321
+ logger.warning("Failed to collect disk stats: %s", e)
1279
1322
  stats.update(disk_total_gb=0, disk_used_gb=0, disk_free_gb=0)
1280
1323
  try:
1281
1324
  import platform as _platform
@@ -1297,7 +1340,8 @@ class DaemonService:
1297
1340
  stats["memory_total_mb"] = round(mem.total / (1024 * 1024))
1298
1341
  stats["memory_used_mb"] = round((mem.total - mem.available) / (1024 * 1024))
1299
1342
  stats["memory_free_mb"] = round(mem.available / (1024 * 1024))
1300
- except Exception:
1343
+ except Exception as e:
1344
+ logger.warning("Failed to collect memory stats: %s", e)
1301
1345
  stats.update(memory_total_mb=0, memory_used_mb=0, memory_free_mb=0)
1302
1346
  return stats
1303
1347
 
@@ -1312,16 +1356,16 @@ class DaemonService:
1312
1356
  try:
1313
1357
  agent_name = runtime.manifest.name or agent_name
1314
1358
  agent_fingerprint = getattr(runtime.manifest, "fingerprint", "")
1315
- except Exception:
1316
- pass
1359
+ except Exception as exc:
1360
+ logger.warning("Failed to read agent name from runtime manifest: %s", exc)
1317
1361
  identity_file = config.home / "identity" / "identity.json"
1318
1362
  if identity_file.exists():
1319
1363
  try:
1320
1364
  ident = json.loads(identity_file.read_text(encoding="utf-8"))
1321
1365
  agent_name = ident.get("name", agent_name)
1322
1366
  agent_fingerprint = ident.get("fingerprint", agent_fingerprint)
1323
- except Exception:
1324
- pass
1367
+ except Exception as exc:
1368
+ logger.warning("Failed to read identity.json for dashboard: %s", exc)
1325
1369
 
1326
1370
  # Consciousness stats
1327
1371
  c_stats: dict = snap.get("consciousness", {})
@@ -1347,10 +1391,10 @@ class DaemonService:
1347
1391
  "message_count": len(msgs),
1348
1392
  "last_message": msgs[-1].get("timestamp") if msgs else None,
1349
1393
  })
1350
- except Exception:
1351
- pass
1352
- except Exception:
1353
- pass
1394
+ except Exception as exc:
1395
+ logger.warning("Failed to read conversation file %s: %s", cf, exc)
1396
+ except Exception as exc:
1397
+ logger.warning("Failed to list conversation files: %s", exc)
1354
1398
 
1355
1399
  return {
1356
1400
  "agent": {
@@ -1392,16 +1436,16 @@ class DaemonService:
1392
1436
  agent["consciousness"] = "SINGULAR"
1393
1437
  elif m.is_conscious:
1394
1438
  agent["consciousness"] = "CONSCIOUS"
1395
- except Exception:
1396
- pass
1439
+ except Exception as exc:
1440
+ logger.warning("Failed to read agent identity from runtime manifest: %s", exc)
1397
1441
  identity_file = config.home / "identity" / "identity.json"
1398
1442
  if identity_file.exists():
1399
1443
  try:
1400
1444
  ident = json.loads(identity_file.read_text(encoding="utf-8"))
1401
1445
  agent["name"] = ident.get("name", agent["name"])
1402
1446
  agent["fingerprint"] = ident.get("fingerprint", agent["fingerprint"])
1403
- except Exception:
1404
- pass
1447
+ except Exception as exc:
1448
+ logger.warning("Failed to read identity.json for capstone dashboard: %s", exc)
1405
1449
 
1406
1450
  # ── Pillar status ─────────────────────────────────────────
1407
1451
  pillars: dict = {}
@@ -1411,8 +1455,8 @@ class DaemonService:
1411
1455
  k: v.value
1412
1456
  for k, v in runtime.manifest.pillar_summary.items()
1413
1457
  }
1414
- except Exception:
1415
- pass
1458
+ except Exception as exc:
1459
+ logger.warning("Failed to read pillar summary from manifest: %s", exc)
1416
1460
 
1417
1461
  # ── Memory stats ──────────────────────────────────────────
1418
1462
  memory: dict = {}
@@ -1426,8 +1470,8 @@ class DaemonService:
1426
1470
  "long_term": ms.long_term,
1427
1471
  "status": ms.status.value,
1428
1472
  }
1429
- except Exception:
1430
- pass
1473
+ except Exception as exc:
1474
+ logger.warning("Failed to collect memory stats for dashboard: %s", exc)
1431
1475
 
1432
1476
  # ── Coordination board ────────────────────────────────────
1433
1477
  board: dict = {"summary": {}, "active": []}
@@ -1461,16 +1505,16 @@ class DaemonService:
1461
1505
  },
1462
1506
  "active": active_tasks,
1463
1507
  }
1464
- except Exception:
1465
- pass
1508
+ except Exception as exc:
1509
+ logger.warning("Failed to collect coordination board data for dashboard: %s", exc)
1466
1510
 
1467
1511
  # ── Consciousness stats ───────────────────────────────────
1468
1512
  c_stats: dict = {}
1469
1513
  if consciousness:
1470
1514
  try:
1471
1515
  c_stats = dict(consciousness.stats)
1472
- except Exception:
1473
- pass
1516
+ except Exception as exc:
1517
+ logger.warning("Failed to read consciousness stats for dashboard: %s", exc)
1474
1518
 
1475
1519
  return {
1476
1520
  "agent": agent,
@@ -1844,17 +1888,18 @@ class DaemonService:
1844
1888
 
1845
1889
  fingerprint: Optional[str] = None
1846
1890
  try:
1847
- from skcomm.capauth_validator import CapAuthValidator
1891
+ from skcomms.capauth_validator import CapAuthValidator
1848
1892
  fingerprint = CapAuthValidator(require_auth=True).validate(token_str)
1849
1893
  except ImportError:
1850
- # skcomm not installed — fall back to skcapstone signed tokens
1894
+ # skcomms not installed — fall back to skcapstone signed tokens
1851
1895
  if token_str:
1852
1896
  try:
1853
1897
  from .tokens import import_token, verify_token
1854
1898
  tok = import_token(token_str)
1855
1899
  if verify_token(tok, home=config.home):
1856
1900
  fingerprint = tok.payload.issuer
1857
- except Exception:
1901
+ except Exception as e:
1902
+ logger.warning("Token verification fallback failed: %s", e)
1858
1903
  fingerprint = None
1859
1904
 
1860
1905
  if fingerprint is None:
@@ -1984,8 +2029,8 @@ class DaemonService:
1984
2029
  entry["identity"] = json.loads(
1985
2030
  identity_path.read_text(encoding="utf-8")
1986
2031
  )
1987
- except Exception:
1988
- pass
2032
+ except Exception as exc:
2033
+ logger.warning("Failed to read identity for agent %s: %s", agent_name, exc)
1989
2034
 
1990
2035
  hb_path = heartbeats_dir / f"{agent_name.lower()}.json"
1991
2036
  if hb_path.exists():
@@ -1995,7 +2040,8 @@ class DaemonService:
1995
2040
  hb["alive"] = alive
1996
2041
  entry["heartbeat"] = hb
1997
2042
  entry["status"] = hb.get("status", "unknown") if alive else "stale"
1998
- except Exception:
2043
+ except Exception as exc:
2044
+ logger.warning("Failed to read heartbeat for agent %s: %s", agent_name, exc)
1999
2045
  entry["status"] = "unknown"
2000
2046
  else:
2001
2047
  entry["status"] = "no_heartbeat"
@@ -2027,8 +2073,8 @@ class DaemonService:
2027
2073
  entry["identity"] = json.loads(
2028
2074
  identity_path.read_text(encoding="utf-8")
2029
2075
  )
2030
- except Exception:
2031
- pass
2076
+ except Exception as exc:
2077
+ logger.warning("Failed to read identity for agent %s: %s", name, exc)
2032
2078
 
2033
2079
  hb_path = config.shared_root / "heartbeats" / f"{name.lower()}.json"
2034
2080
  if hb_path.exists():
@@ -2038,8 +2084,8 @@ class DaemonService:
2038
2084
  hb["alive"] = alive
2039
2085
  entry["heartbeat"] = hb
2040
2086
  entry["status"] = hb.get("status", "unknown") if alive else "stale"
2041
- except Exception:
2042
- pass
2087
+ except Exception as exc:
2088
+ logger.warning("Failed to read heartbeat for agent %s: %s", name, exc)
2043
2089
 
2044
2090
  memory_dir = agent_dir / "memory"
2045
2091
  if memory_dir.exists():
@@ -2062,8 +2108,8 @@ class DaemonService:
2062
2108
  "message_count": len(msgs),
2063
2109
  "last_message": msgs[-1].get("timestamp") if msgs else None,
2064
2110
  })
2065
- except Exception:
2066
- pass
2111
+ except Exception as exc:
2112
+ logger.warning("Failed to read conversation file %s: %s", cf, exc)
2067
2113
  entry["recent_conversations"] = conv_list
2068
2114
 
2069
2115
  if consciousness:
@@ -2092,8 +2138,8 @@ class DaemonService:
2092
2138
  "last_message_time": last_msg.get("timestamp") if msgs else None,
2093
2139
  "last_message_preview": (last_content or "")[:120],
2094
2140
  })
2095
- except Exception:
2096
- pass
2141
+ except Exception as exc:
2142
+ logger.warning("Failed to read conversation file %s: %s", cf, exc)
2097
2143
  self._json_response({"conversations": conversations})
2098
2144
 
2099
2145
  # ── Conversations: single peer history ────────────────────
@@ -2172,7 +2218,8 @@ class DaemonService:
2172
2218
  length = int(self.headers.get("Content-Length", 0))
2173
2219
  body = self.rfile.read(length) if length > 0 else b"{}"
2174
2220
  data = json.loads(body)
2175
- except Exception:
2221
+ except Exception as e:
2222
+ logger.warning("Failed to parse request JSON body: %s", e)
2176
2223
  self._json_response({"error": "invalid JSON body"}, status=400)
2177
2224
  return
2178
2225
 
@@ -2184,7 +2231,7 @@ class DaemonService:
2184
2231
  message_id = str(uuid.uuid4())
2185
2232
  ts = datetime.now(timezone.utc).isoformat()
2186
2233
 
2187
- # Build SKComm envelope
2234
+ # Build SKComms envelope
2188
2235
  envelope = {
2189
2236
  "message_id": message_id,
2190
2237
  "sender": "api",
@@ -2196,7 +2243,7 @@ class DaemonService:
2196
2243
  },
2197
2244
  }
2198
2245
 
2199
- # Write to SKComm outbox
2246
+ # Write to SKComms outbox
2200
2247
  try:
2201
2248
  outbox = config.shared_root / "sync" / "comms" / "outbox"
2202
2249
  outbox.mkdir(parents=True, exist_ok=True)
@@ -2469,23 +2516,31 @@ class DaemonService:
2469
2516
  def read_pid(home: Optional[Path] = None) -> Optional[int]:
2470
2517
  """Read the daemon PID from the PID file.
2471
2518
 
2519
+ Checks the given home directory first, then falls back to the shared
2520
+ root (AGENT_HOME / ~/.skcapstone) since the daemon writes its PID
2521
+ to config.home which defaults to the shared root.
2522
+
2472
2523
  Args:
2473
- home: Agent home directory.
2524
+ home: Agent home directory (or shared root).
2474
2525
 
2475
2526
  Returns:
2476
2527
  PID as int, or None if not running.
2477
2528
  """
2478
2529
  home = (home or Path(AGENT_HOME)).expanduser()
2479
- pid_path = home / PID_FILE
2480
- if not pid_path.exists():
2481
- return None
2482
- try:
2483
- pid = int(pid_path.read_text(encoding="utf-8").strip())
2484
- os.kill(pid, 0)
2485
- return pid
2486
- except (ValueError, ProcessLookupError, PermissionError):
2487
- pid_path.unlink(missing_ok=True)
2488
- return None
2530
+ shared_root = Path(AGENT_HOME).expanduser()
2531
+
2532
+ # Check agent home first, then shared root
2533
+ for candidate in (home, shared_root):
2534
+ pid_path = candidate / PID_FILE
2535
+ if not pid_path.exists():
2536
+ continue
2537
+ try:
2538
+ pid = int(pid_path.read_text(encoding="utf-8").strip())
2539
+ os.kill(pid, 0)
2540
+ return pid
2541
+ except (ValueError, ProcessLookupError, PermissionError):
2542
+ pid_path.unlink(missing_ok=True)
2543
+ return None
2489
2544
 
2490
2545
 
2491
2546
  def is_running(home: Optional[Path] = None) -> bool:
@@ -240,8 +240,8 @@ def _get_daemon_json(home: Path, daemon_port: int = 7777) -> dict:
240
240
  "recent_errors": recent_errors,
241
241
  "inflight_count": snap.get("inflight_count", 0),
242
242
  }
243
- except Exception:
244
- pass
243
+ except Exception as exc:
244
+ logger.warning("Failed to fetch daemon status for dashboard: %s", exc)
245
245
 
246
246
  # ── Daemon /consciousness ─────────────────────────────────────────────────
247
247
  consciousness_info: dict = {"enabled": False}
@@ -249,8 +249,8 @@ def _get_daemon_json(home: Path, daemon_port: int = 7777) -> dict:
249
249
  url = f"http://127.0.0.1:{daemon_port}/consciousness"
250
250
  with urllib.request.urlopen(url, timeout=3) as resp:
251
251
  consciousness_info = json.loads(resp.read())
252
- except Exception:
253
- pass
252
+ except Exception as exc:
253
+ logger.debug("Failed to fetch consciousness status for dashboard: %s", exc)
254
254
 
255
255
  # ── LLM backend availability ──────────────────────────────────────────────
256
256
  backend_health: dict = {
@@ -266,16 +266,16 @@ def _get_daemon_json(home: Path, daemon_port: int = 7777) -> dict:
266
266
  urllib.request.Request(f"{ollama_host}/api/tags"), timeout=2
267
267
  ):
268
268
  backend_health["ollama"] = True
269
- except Exception:
270
- pass
269
+ except Exception as exc:
270
+ logger.debug("Ollama probe failed (not available): %s", exc)
271
271
 
272
272
  # ── Heartbeat (system metrics + active conversations) ─────────────────────
273
273
  system_info: dict = {}
274
274
  active_conversations: int = 0
275
275
  try:
276
- from . import SHARED_ROOT
276
+ from . import SHARED_ROOT, DEFAULT_AGENT
277
277
  identity_path = home / "identity" / "identity.json"
278
- agent_name = "opus"
278
+ agent_name = DEFAULT_AGENT
279
279
  if identity_path.exists():
280
280
  ident = json.loads(identity_path.read_text(encoding="utf-8"))
281
281
  agent_name = ident.get("name", agent_name).lower()
@@ -291,8 +291,8 @@ def _get_daemon_json(home: Path, daemon_port: int = 7777) -> dict:
291
291
  "cpu_load_1min": hb.get("cpu_load_1min", 0.0),
292
292
  "memory_used_mb": hb.get("memory_used_mb", 0),
293
293
  }
294
- except Exception:
295
- pass
294
+ except Exception as exc:
295
+ logger.warning("Failed to read heartbeat data for dashboard: %s", exc)
296
296
 
297
297
  return {
298
298
  "generated_at": now,