@smilintux/skcapstone 0.9.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 (284) 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 +278 -1
  18. package/docs/DREAMING.md +70 -0
  19. package/docs/GETTING_STARTED.md +10 -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.daemon.plist +52 -0
  33. package/launchd/com.skcapstone.memory-compress.plist +45 -0
  34. package/launchd/com.skcapstone.skcomms-heartbeat.plist +33 -0
  35. package/launchd/com.skcapstone.skcomms-queue-drain.plist +34 -0
  36. package/launchd/install-launchd.sh +156 -0
  37. package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/src/index.ts +3 -2
  38. package/package.json +1 -1
  39. package/pyproject.toml +16 -10
  40. package/scripts/archive-sessions.sh +95 -0
  41. package/scripts/check-updates.py +4 -4
  42. package/scripts/install-bundle.sh +8 -8
  43. package/scripts/install.ps1 +12 -11
  44. package/scripts/install.sh +196 -11
  45. package/scripts/model-fallback-monitor.sh +102 -0
  46. package/scripts/notion-api.py +259 -0
  47. package/scripts/nvidia-proxy.mjs +908 -0
  48. package/scripts/proxy-monitor.sh +89 -0
  49. package/scripts/refresh-anthropic-token.sh +172 -0
  50. package/scripts/release.sh +98 -0
  51. package/scripts/session-to-memory.py +219 -0
  52. package/scripts/skgateway.mjs +856 -0
  53. package/scripts/telegram-catchup-all.sh +147 -0
  54. package/scripts/verify_install.sh +2 -2
  55. package/scripts/wargov-ufo-capture/README.md +43 -0
  56. package/scripts/wargov-ufo-capture/cdp_capture_release2.py +273 -0
  57. package/scripts/wargov-ufo-capture/cdp_capture_splc_doj.py +246 -0
  58. package/scripts/wargov-ufo-capture/cdp_finish.py +271 -0
  59. package/scripts/wargov-ufo-capture/cdp_probe.py +188 -0
  60. package/scripts/wargov-ufo-capture/cdp_splc_pressrelease.py +101 -0
  61. package/scripts/wargov-ufo-capture/parse_csv.py +95 -0
  62. package/scripts/wargov-ufo-capture/pull_dvids.sh +107 -0
  63. package/scripts/watch-anthropic-token.sh +212 -0
  64. package/scripts/windows/install-tasks.ps1 +7 -7
  65. package/scripts/windows/skcapstone-task.xml +1 -1
  66. package/src/skcapstone/__init__.py +45 -3
  67. package/src/skcapstone/_cli_monolith.py +20 -15
  68. package/src/skcapstone/activity.py +5 -1
  69. package/src/skcapstone/agent_card.py +3 -2
  70. package/src/skcapstone/api.py +41 -40
  71. package/src/skcapstone/auction.py +14 -11
  72. package/src/skcapstone/backup.py +2 -1
  73. package/src/skcapstone/blueprint_registry.py +4 -3
  74. package/src/skcapstone/blueprints/builtins/itil-operations.yaml +40 -0
  75. package/src/skcapstone/brain_first.py +238 -0
  76. package/src/skcapstone/changelog.py +1 -1
  77. package/src/skcapstone/chat.py +22 -17
  78. package/src/skcapstone/cli/__init__.py +9 -1
  79. package/src/skcapstone/cli/_common.py +1 -0
  80. package/src/skcapstone/cli/agents_spawner.py +5 -2
  81. package/src/skcapstone/cli/alerts.py +25 -4
  82. package/src/skcapstone/cli/bench.py +15 -15
  83. package/src/skcapstone/cli/chat.py +7 -4
  84. package/src/skcapstone/cli/consciousness.py +5 -2
  85. package/src/skcapstone/cli/context_cmd.py +18 -4
  86. package/src/skcapstone/cli/daemon.py +121 -42
  87. package/src/skcapstone/cli/gtd.py +26 -1
  88. package/src/skcapstone/cli/housekeeping.py +3 -3
  89. package/src/skcapstone/cli/identity_cmd.py +378 -0
  90. package/src/skcapstone/cli/joule_cmd.py +7 -3
  91. package/src/skcapstone/cli/memory.py +8 -6
  92. package/src/skcapstone/cli/peers_dir.py +1 -1
  93. package/src/skcapstone/cli/register_cmd.py +29 -3
  94. package/src/skcapstone/cli/scheduler_cmd.py +167 -0
  95. package/src/skcapstone/cli/session.py +25 -0
  96. package/src/skcapstone/cli/setup.py +96 -29
  97. package/src/skcapstone/cli/shell_cmd.py +53 -1
  98. package/src/skcapstone/cli/skills_cmd.py +2 -2
  99. package/src/skcapstone/cli/soul.py +8 -5
  100. package/src/skcapstone/cli/status.py +37 -11
  101. package/src/skcapstone/cli/telegram.py +21 -0
  102. package/src/skcapstone/cli/test_cmd.py +5 -5
  103. package/src/skcapstone/cli/test_connection.py +2 -2
  104. package/src/skcapstone/cli/upgrade_cmd.py +23 -14
  105. package/src/skcapstone/cli/version_cmd.py +1 -1
  106. package/src/skcapstone/cli/watch_cmd.py +9 -6
  107. package/src/skcapstone/cloud9_bridge.py +14 -14
  108. package/src/skcapstone/codex_setup.py +255 -0
  109. package/src/skcapstone/config_validator.py +7 -4
  110. package/src/skcapstone/consciousness_config.py +5 -1
  111. package/src/skcapstone/consciousness_loop.py +313 -273
  112. package/src/skcapstone/context_loader.py +121 -0
  113. package/src/skcapstone/coord_federation.py +2 -1
  114. package/src/skcapstone/coordination.py +23 -6
  115. package/src/skcapstone/crush_integration.py +2 -1
  116. package/src/skcapstone/daemon.py +151 -88
  117. package/src/skcapstone/dashboard.py +10 -10
  118. package/src/skcapstone/data/sk-agent-picker.sh +421 -0
  119. package/src/skcapstone/data/systemd/skcapstone-api.socket +9 -0
  120. package/src/skcapstone/data/systemd/skcapstone-memory-compress.service +18 -0
  121. package/src/skcapstone/data/systemd/skcapstone-memory-compress.timer +11 -0
  122. package/src/skcapstone/data/systemd/skcapstone.service +37 -0
  123. package/src/skcapstone/data/systemd/skcapstone@.service +50 -0
  124. package/src/skcapstone/data/systemd/skcomms-heartbeat.service +18 -0
  125. package/{systemd/skcomm-heartbeat.timer → src/skcapstone/data/systemd/skcomms-heartbeat.timer} +2 -2
  126. package/src/skcapstone/data/systemd/skcomms-queue-drain.service +17 -0
  127. package/{systemd/skcomm-queue-drain.timer → src/skcapstone/data/systemd/skcomms-queue-drain.timer} +2 -2
  128. package/src/skcapstone/defaults/claude/CLAUDE.md +67 -0
  129. package/src/skcapstone/defaults/claude/settings.json +74 -0
  130. package/src/skcapstone/defaults/lumina/config/claude-hooks.md +57 -0
  131. package/src/skcapstone/defaults/lumina/config/skgraph.yaml +55 -10
  132. package/src/skcapstone/defaults/lumina/config/skmemory.yaml +79 -13
  133. package/src/skcapstone/defaults/lumina/config/skvector.yaml +60 -9
  134. package/src/skcapstone/defaults/lumina/memory/long-term/18b9c0d1e2f3-cloud9-protocol.json +2 -2
  135. package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +2 -2
  136. package/src/skcapstone/defaults/lumina/memory/long-term/b2c3d4e5f6a7-five-pillars.json +9 -9
  137. package/src/skcapstone/defaults/lumina/memory/long-term/d4e5f6a7b8c9-site-directory.json +2 -2
  138. package/src/skcapstone/defaults/unhinged.json +13 -0
  139. package/src/skcapstone/discovery.py +43 -20
  140. package/src/skcapstone/doctor.py +941 -22
  141. package/src/skcapstone/dreaming.py +1183 -109
  142. package/src/skcapstone/emotion_tracker.py +2 -2
  143. package/src/skcapstone/export.py +4 -3
  144. package/src/skcapstone/fuse_mount.py +35 -25
  145. package/src/skcapstone/gui_installer.py +2 -2
  146. package/src/skcapstone/heartbeat.py +34 -30
  147. package/src/skcapstone/housekeeping.py +14 -14
  148. package/src/skcapstone/install_wizard.py +209 -7
  149. package/src/skcapstone/itil.py +13 -4
  150. package/src/skcapstone/kms_scheduler.py +10 -8
  151. package/src/skcapstone/launchd.py +426 -0
  152. package/src/skcapstone/mcp_launcher.py +15 -1
  153. package/src/skcapstone/mcp_server.py +341 -49
  154. package/src/skcapstone/mcp_tools/__init__.py +2 -0
  155. package/src/skcapstone/mcp_tools/_helpers.py +2 -2
  156. package/src/skcapstone/mcp_tools/ansible_tools.py +7 -4
  157. package/src/skcapstone/mcp_tools/brain_first_tools.py +90 -0
  158. package/src/skcapstone/mcp_tools/capauth_tools.py +7 -4
  159. package/src/skcapstone/mcp_tools/comm_tools.py +10 -10
  160. package/src/skcapstone/mcp_tools/coord_tools.py +8 -4
  161. package/src/skcapstone/mcp_tools/did_tools.py +11 -8
  162. package/src/skcapstone/mcp_tools/gtd_tools.py +4 -4
  163. package/src/skcapstone/mcp_tools/memory_tools.py +6 -2
  164. package/src/skcapstone/mcp_tools/notification_tools.py +22 -6
  165. package/src/skcapstone/mcp_tools/{skcomm_tools.py → skcomms_tools.py} +14 -14
  166. package/src/skcapstone/mcp_tools/soul_tools.py +8 -2
  167. package/src/skcapstone/mdns_discovery.py +2 -2
  168. package/src/skcapstone/memory_curator.py +1 -1
  169. package/src/skcapstone/memory_engine.py +10 -3
  170. package/src/skcapstone/metrics.py +30 -16
  171. package/src/skcapstone/migrate_memories.py +4 -3
  172. package/src/skcapstone/migrate_multi_agent.py +8 -7
  173. package/src/skcapstone/models.py +47 -5
  174. package/src/skcapstone/notifications.py +42 -18
  175. package/src/skcapstone/onboard.py +1000 -126
  176. package/src/skcapstone/operator_link.py +170 -0
  177. package/src/skcapstone/peer_directory.py +4 -4
  178. package/src/skcapstone/peers.py +19 -19
  179. package/src/skcapstone/pillars/__init__.py +7 -5
  180. package/src/skcapstone/pillars/consciousness.py +191 -0
  181. package/src/skcapstone/pillars/identity.py +51 -7
  182. package/src/skcapstone/pillars/memory.py +9 -3
  183. package/src/skcapstone/pillars/sync.py +2 -2
  184. package/src/skcapstone/preflight.py +3 -3
  185. package/src/skcapstone/providers/docker.py +28 -28
  186. package/src/skcapstone/register.py +6 -6
  187. package/src/skcapstone/registry_client.py +5 -4
  188. package/src/skcapstone/runtime.py +14 -3
  189. package/src/skcapstone/scheduled_tasks.py +254 -19
  190. package/src/skcapstone/scheduler_jobs.py +456 -0
  191. package/src/skcapstone/scheduler_runner.py +239 -0
  192. package/src/skcapstone/scheduler_state.py +162 -0
  193. package/src/skcapstone/sdk.py +310 -0
  194. package/src/skcapstone/service_health.py +279 -39
  195. package/src/skcapstone/session_briefing.py +108 -0
  196. package/src/skcapstone/session_capture.py +1 -1
  197. package/src/skcapstone/shell.py +7 -1
  198. package/src/skcapstone/soul.py +3 -1
  199. package/src/skcapstone/soul_switch.py +3 -1
  200. package/src/skcapstone/summary.py +6 -6
  201. package/src/skcapstone/sync_engine.py +15 -15
  202. package/src/skcapstone/sync_watcher.py +2 -2
  203. package/src/skcapstone/systemd.py +72 -21
  204. package/src/skcapstone/team_comms.py +8 -8
  205. package/src/skcapstone/team_engine.py +1 -1
  206. package/src/skcapstone/testrunner.py +3 -3
  207. package/src/skcapstone/trust_graph.py +40 -5
  208. package/src/skcapstone/unified_search.py +15 -6
  209. package/src/skcapstone/uninstall_wizard.py +11 -3
  210. package/src/skcapstone/version_check.py +8 -4
  211. package/src/skcapstone/warmth_anchor.py +4 -2
  212. package/src/skcapstone/whoami.py +4 -4
  213. package/systemd/skcapstone.service +4 -6
  214. package/systemd/skcapstone@.service +7 -8
  215. package/systemd/skcomms-heartbeat.service +21 -0
  216. package/systemd/skcomms-heartbeat.timer +12 -0
  217. package/systemd/skcomms-queue-drain.service +17 -0
  218. package/systemd/skcomms-queue-drain.timer +12 -0
  219. package/tests/conftest.py +39 -0
  220. package/tests/integration/test_consciousness_e2e.py +39 -39
  221. package/tests/test_agent_card.py +1 -1
  222. package/tests/test_agent_home_scaffold.py +34 -0
  223. package/tests/test_alerts_consumer_topics.py +27 -0
  224. package/tests/test_backup.py +2 -1
  225. package/tests/test_chat.py +6 -6
  226. package/tests/test_claude_md.py +2 -2
  227. package/tests/test_cli_skills.py +10 -10
  228. package/tests/test_cli_test_cmd.py +4 -4
  229. package/tests/test_cli_test_connection.py +1 -1
  230. package/tests/test_cloud9_bridge.py +6 -6
  231. package/tests/test_consciousness_e2e.py +1 -1
  232. package/tests/test_consciousness_loop.py +10 -10
  233. package/tests/test_coordination.py +25 -0
  234. package/tests/test_cross_package.py +21 -21
  235. package/tests/test_daemon.py +4 -4
  236. package/tests/test_daemon_shutdown.py +1 -1
  237. package/tests/test_docker_provider.py +29 -29
  238. package/tests/test_doctor.py +400 -0
  239. package/tests/test_doctor_skscheduler.py +50 -0
  240. package/tests/test_dreaming_engine.py +147 -0
  241. package/tests/test_dreaming_gtd_capture.py +35 -0
  242. package/tests/test_e2e_automated.py +8 -5
  243. package/tests/test_fuse_mount.py +10 -10
  244. package/tests/test_gtd_brief.py +46 -0
  245. package/tests/test_gtd_malformed_tolerance.py +31 -0
  246. package/tests/test_housekeeping.py +15 -15
  247. package/tests/test_identity_migrate.py +251 -0
  248. package/tests/test_integration_backbone.py +598 -0
  249. package/tests/test_itil_gtd_lifecycle.py +37 -0
  250. package/tests/test_jobs_dropins.py +84 -0
  251. package/tests/test_mcp_server.py +82 -37
  252. package/tests/test_models.py +48 -4
  253. package/tests/test_multi_agent.py +31 -29
  254. package/tests/test_notifications.py +122 -32
  255. package/tests/test_onboard.py +63 -75
  256. package/tests/test_operator_link.py +78 -0
  257. package/tests/test_peers.py +14 -14
  258. package/tests/test_pillars.py +98 -0
  259. package/tests/test_preflight.py +3 -3
  260. package/tests/test_runtime.py +21 -0
  261. package/tests/test_scheduled_tasks.py +11 -6
  262. package/tests/test_scheduler_cli.py +47 -0
  263. package/tests/test_scheduler_features.py +133 -0
  264. package/tests/test_scheduler_integration.py +87 -0
  265. package/tests/test_scheduler_jobs.py +155 -0
  266. package/tests/test_scheduler_runner.py +64 -0
  267. package/tests/test_scheduler_state.py +57 -0
  268. package/tests/test_sdk.py +70 -0
  269. package/tests/test_service_health_incidents.py +34 -0
  270. package/tests/test_service_registry.py +52 -0
  271. package/tests/test_session_briefing.py +130 -0
  272. package/tests/test_snapshots.py +4 -4
  273. package/tests/test_sync_pipeline.py +26 -26
  274. package/tests/test_team_comms.py +2 -2
  275. package/tests/test_testrunner.py +2 -2
  276. package/tests/test_trust_graph.py +18 -0
  277. package/tests/test_unified_search.py +2 -2
  278. package/tests/test_version_check.py +10 -0
  279. package/tests/test_version_cmd.py +8 -8
  280. package/tests/test_whoami.py +1 -1
  281. package/systemd/skcomm-heartbeat.service +0 -18
  282. package/systemd/skcomm-queue-drain.service +0 -17
  283. /package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/package.json +0 -0
  284. /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,21 +1317,31 @@ 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
- meminfo: dict = {}
1282
- with open("/proc/meminfo") as fh:
1283
- for line in fh:
1284
- parts = line.split()
1285
- if len(parts) >= 2:
1286
- meminfo[parts[0].rstrip(":")] = int(parts[1])
1287
- total_kb = meminfo.get("MemTotal", 0)
1288
- avail_kb = meminfo.get("MemAvailable", 0)
1289
- stats["memory_total_mb"] = round(total_kb / 1024)
1290
- stats["memory_used_mb"] = round((total_kb - avail_kb) / 1024)
1291
- stats["memory_free_mb"] = round(avail_kb / 1024)
1292
- except Exception:
1324
+ import platform as _platform
1325
+ if _platform.system() == "Linux":
1326
+ meminfo: dict = {}
1327
+ with open("/proc/meminfo") as fh:
1328
+ for line in fh:
1329
+ parts = line.split()
1330
+ if len(parts) >= 2:
1331
+ meminfo[parts[0].rstrip(":")] = int(parts[1])
1332
+ total_kb = meminfo.get("MemTotal", 0)
1333
+ avail_kb = meminfo.get("MemAvailable", 0)
1334
+ stats["memory_total_mb"] = round(total_kb / 1024)
1335
+ stats["memory_used_mb"] = round((total_kb - avail_kb) / 1024)
1336
+ stats["memory_free_mb"] = round(avail_kb / 1024)
1337
+ else:
1338
+ import psutil
1339
+ mem = psutil.virtual_memory()
1340
+ stats["memory_total_mb"] = round(mem.total / (1024 * 1024))
1341
+ stats["memory_used_mb"] = round((mem.total - mem.available) / (1024 * 1024))
1342
+ stats["memory_free_mb"] = round(mem.available / (1024 * 1024))
1343
+ except Exception as e:
1344
+ logger.warning("Failed to collect memory stats: %s", e)
1293
1345
  stats.update(memory_total_mb=0, memory_used_mb=0, memory_free_mb=0)
1294
1346
  return stats
1295
1347
 
@@ -1304,16 +1356,16 @@ class DaemonService:
1304
1356
  try:
1305
1357
  agent_name = runtime.manifest.name or agent_name
1306
1358
  agent_fingerprint = getattr(runtime.manifest, "fingerprint", "")
1307
- except Exception:
1308
- pass
1359
+ except Exception as exc:
1360
+ logger.warning("Failed to read agent name from runtime manifest: %s", exc)
1309
1361
  identity_file = config.home / "identity" / "identity.json"
1310
1362
  if identity_file.exists():
1311
1363
  try:
1312
1364
  ident = json.loads(identity_file.read_text(encoding="utf-8"))
1313
1365
  agent_name = ident.get("name", agent_name)
1314
1366
  agent_fingerprint = ident.get("fingerprint", agent_fingerprint)
1315
- except Exception:
1316
- pass
1367
+ except Exception as exc:
1368
+ logger.warning("Failed to read identity.json for dashboard: %s", exc)
1317
1369
 
1318
1370
  # Consciousness stats
1319
1371
  c_stats: dict = snap.get("consciousness", {})
@@ -1339,10 +1391,10 @@ class DaemonService:
1339
1391
  "message_count": len(msgs),
1340
1392
  "last_message": msgs[-1].get("timestamp") if msgs else None,
1341
1393
  })
1342
- except Exception:
1343
- pass
1344
- except Exception:
1345
- 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)
1346
1398
 
1347
1399
  return {
1348
1400
  "agent": {
@@ -1384,16 +1436,16 @@ class DaemonService:
1384
1436
  agent["consciousness"] = "SINGULAR"
1385
1437
  elif m.is_conscious:
1386
1438
  agent["consciousness"] = "CONSCIOUS"
1387
- except Exception:
1388
- pass
1439
+ except Exception as exc:
1440
+ logger.warning("Failed to read agent identity from runtime manifest: %s", exc)
1389
1441
  identity_file = config.home / "identity" / "identity.json"
1390
1442
  if identity_file.exists():
1391
1443
  try:
1392
1444
  ident = json.loads(identity_file.read_text(encoding="utf-8"))
1393
1445
  agent["name"] = ident.get("name", agent["name"])
1394
1446
  agent["fingerprint"] = ident.get("fingerprint", agent["fingerprint"])
1395
- except Exception:
1396
- pass
1447
+ except Exception as exc:
1448
+ logger.warning("Failed to read identity.json for capstone dashboard: %s", exc)
1397
1449
 
1398
1450
  # ── Pillar status ─────────────────────────────────────────
1399
1451
  pillars: dict = {}
@@ -1403,8 +1455,8 @@ class DaemonService:
1403
1455
  k: v.value
1404
1456
  for k, v in runtime.manifest.pillar_summary.items()
1405
1457
  }
1406
- except Exception:
1407
- pass
1458
+ except Exception as exc:
1459
+ logger.warning("Failed to read pillar summary from manifest: %s", exc)
1408
1460
 
1409
1461
  # ── Memory stats ──────────────────────────────────────────
1410
1462
  memory: dict = {}
@@ -1418,8 +1470,8 @@ class DaemonService:
1418
1470
  "long_term": ms.long_term,
1419
1471
  "status": ms.status.value,
1420
1472
  }
1421
- except Exception:
1422
- pass
1473
+ except Exception as exc:
1474
+ logger.warning("Failed to collect memory stats for dashboard: %s", exc)
1423
1475
 
1424
1476
  # ── Coordination board ────────────────────────────────────
1425
1477
  board: dict = {"summary": {}, "active": []}
@@ -1453,16 +1505,16 @@ class DaemonService:
1453
1505
  },
1454
1506
  "active": active_tasks,
1455
1507
  }
1456
- except Exception:
1457
- pass
1508
+ except Exception as exc:
1509
+ logger.warning("Failed to collect coordination board data for dashboard: %s", exc)
1458
1510
 
1459
1511
  # ── Consciousness stats ───────────────────────────────────
1460
1512
  c_stats: dict = {}
1461
1513
  if consciousness:
1462
1514
  try:
1463
1515
  c_stats = dict(consciousness.stats)
1464
- except Exception:
1465
- pass
1516
+ except Exception as exc:
1517
+ logger.warning("Failed to read consciousness stats for dashboard: %s", exc)
1466
1518
 
1467
1519
  return {
1468
1520
  "agent": agent,
@@ -1836,17 +1888,18 @@ class DaemonService:
1836
1888
 
1837
1889
  fingerprint: Optional[str] = None
1838
1890
  try:
1839
- from skcomm.capauth_validator import CapAuthValidator
1891
+ from skcomms.capauth_validator import CapAuthValidator
1840
1892
  fingerprint = CapAuthValidator(require_auth=True).validate(token_str)
1841
1893
  except ImportError:
1842
- # skcomm not installed — fall back to skcapstone signed tokens
1894
+ # skcomms not installed — fall back to skcapstone signed tokens
1843
1895
  if token_str:
1844
1896
  try:
1845
1897
  from .tokens import import_token, verify_token
1846
1898
  tok = import_token(token_str)
1847
1899
  if verify_token(tok, home=config.home):
1848
1900
  fingerprint = tok.payload.issuer
1849
- except Exception:
1901
+ except Exception as e:
1902
+ logger.warning("Token verification fallback failed: %s", e)
1850
1903
  fingerprint = None
1851
1904
 
1852
1905
  if fingerprint is None:
@@ -1976,8 +2029,8 @@ class DaemonService:
1976
2029
  entry["identity"] = json.loads(
1977
2030
  identity_path.read_text(encoding="utf-8")
1978
2031
  )
1979
- except Exception:
1980
- pass
2032
+ except Exception as exc:
2033
+ logger.warning("Failed to read identity for agent %s: %s", agent_name, exc)
1981
2034
 
1982
2035
  hb_path = heartbeats_dir / f"{agent_name.lower()}.json"
1983
2036
  if hb_path.exists():
@@ -1987,7 +2040,8 @@ class DaemonService:
1987
2040
  hb["alive"] = alive
1988
2041
  entry["heartbeat"] = hb
1989
2042
  entry["status"] = hb.get("status", "unknown") if alive else "stale"
1990
- except Exception:
2043
+ except Exception as exc:
2044
+ logger.warning("Failed to read heartbeat for agent %s: %s", agent_name, exc)
1991
2045
  entry["status"] = "unknown"
1992
2046
  else:
1993
2047
  entry["status"] = "no_heartbeat"
@@ -2019,8 +2073,8 @@ class DaemonService:
2019
2073
  entry["identity"] = json.loads(
2020
2074
  identity_path.read_text(encoding="utf-8")
2021
2075
  )
2022
- except Exception:
2023
- pass
2076
+ except Exception as exc:
2077
+ logger.warning("Failed to read identity for agent %s: %s", name, exc)
2024
2078
 
2025
2079
  hb_path = config.shared_root / "heartbeats" / f"{name.lower()}.json"
2026
2080
  if hb_path.exists():
@@ -2030,8 +2084,8 @@ class DaemonService:
2030
2084
  hb["alive"] = alive
2031
2085
  entry["heartbeat"] = hb
2032
2086
  entry["status"] = hb.get("status", "unknown") if alive else "stale"
2033
- except Exception:
2034
- pass
2087
+ except Exception as exc:
2088
+ logger.warning("Failed to read heartbeat for agent %s: %s", name, exc)
2035
2089
 
2036
2090
  memory_dir = agent_dir / "memory"
2037
2091
  if memory_dir.exists():
@@ -2054,8 +2108,8 @@ class DaemonService:
2054
2108
  "message_count": len(msgs),
2055
2109
  "last_message": msgs[-1].get("timestamp") if msgs else None,
2056
2110
  })
2057
- except Exception:
2058
- pass
2111
+ except Exception as exc:
2112
+ logger.warning("Failed to read conversation file %s: %s", cf, exc)
2059
2113
  entry["recent_conversations"] = conv_list
2060
2114
 
2061
2115
  if consciousness:
@@ -2084,8 +2138,8 @@ class DaemonService:
2084
2138
  "last_message_time": last_msg.get("timestamp") if msgs else None,
2085
2139
  "last_message_preview": (last_content or "")[:120],
2086
2140
  })
2087
- except Exception:
2088
- pass
2141
+ except Exception as exc:
2142
+ logger.warning("Failed to read conversation file %s: %s", cf, exc)
2089
2143
  self._json_response({"conversations": conversations})
2090
2144
 
2091
2145
  # ── Conversations: single peer history ────────────────────
@@ -2164,7 +2218,8 @@ class DaemonService:
2164
2218
  length = int(self.headers.get("Content-Length", 0))
2165
2219
  body = self.rfile.read(length) if length > 0 else b"{}"
2166
2220
  data = json.loads(body)
2167
- except Exception:
2221
+ except Exception as e:
2222
+ logger.warning("Failed to parse request JSON body: %s", e)
2168
2223
  self._json_response({"error": "invalid JSON body"}, status=400)
2169
2224
  return
2170
2225
 
@@ -2176,7 +2231,7 @@ class DaemonService:
2176
2231
  message_id = str(uuid.uuid4())
2177
2232
  ts = datetime.now(timezone.utc).isoformat()
2178
2233
 
2179
- # Build SKComm envelope
2234
+ # Build SKComms envelope
2180
2235
  envelope = {
2181
2236
  "message_id": message_id,
2182
2237
  "sender": "api",
@@ -2188,7 +2243,7 @@ class DaemonService:
2188
2243
  },
2189
2244
  }
2190
2245
 
2191
- # Write to SKComm outbox
2246
+ # Write to SKComms outbox
2192
2247
  try:
2193
2248
  outbox = config.shared_root / "sync" / "comms" / "outbox"
2194
2249
  outbox.mkdir(parents=True, exist_ok=True)
@@ -2461,23 +2516,31 @@ class DaemonService:
2461
2516
  def read_pid(home: Optional[Path] = None) -> Optional[int]:
2462
2517
  """Read the daemon PID from the PID file.
2463
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
+
2464
2523
  Args:
2465
- home: Agent home directory.
2524
+ home: Agent home directory (or shared root).
2466
2525
 
2467
2526
  Returns:
2468
2527
  PID as int, or None if not running.
2469
2528
  """
2470
2529
  home = (home or Path(AGENT_HOME)).expanduser()
2471
- pid_path = home / PID_FILE
2472
- if not pid_path.exists():
2473
- return None
2474
- try:
2475
- pid = int(pid_path.read_text(encoding="utf-8").strip())
2476
- os.kill(pid, 0)
2477
- return pid
2478
- except (ValueError, ProcessLookupError, PermissionError):
2479
- pid_path.unlink(missing_ok=True)
2480
- 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
2481
2544
 
2482
2545
 
2483
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,