@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
@@ -0,0 +1,170 @@
1
+ """Human-operator link helpers for manifests and identity attestations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import logging
7
+ from datetime import datetime, timezone
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ def discover_human_operator(capauth_home: Path | None = None) -> dict[str, str] | None:
15
+ """Return the active human operator from the local CapAuth profile.
16
+
17
+ Args:
18
+ capauth_home: Optional CapAuth home directory. Defaults to ``~/.capauth``.
19
+
20
+ Returns:
21
+ A compact operator mapping, or ``None`` if no human profile is available.
22
+ """
23
+ base = Path(capauth_home).expanduser() if capauth_home else _resolve_operator_home()
24
+ profile_path = base / "identity" / "profile.json"
25
+ if not profile_path.exists():
26
+ return None
27
+
28
+ try:
29
+ data = json.loads(profile_path.read_text(encoding="utf-8"))
30
+ except (json.JSONDecodeError, OSError):
31
+ return None
32
+
33
+ entity = data.get("entity", {})
34
+ key_info = data.get("key_info", {})
35
+ entity_type = str(entity.get("entity_type", "")).lower()
36
+ if entity_type not in {"human", "entitytype.human"}:
37
+ return None
38
+
39
+ name = entity.get("name", "").strip()
40
+ if not name:
41
+ return None
42
+
43
+ operator = {
44
+ "name": name,
45
+ "relationship": "human-operator",
46
+ "entity_type": "human",
47
+ "source": "capauth",
48
+ }
49
+ if entity.get("email"):
50
+ operator["email"] = entity["email"]
51
+ if entity.get("handle"):
52
+ operator["handle"] = entity["handle"]
53
+ if key_info.get("fingerprint"):
54
+ operator["fingerprint"] = key_info["fingerprint"]
55
+ return operator
56
+
57
+
58
+ def build_agent_manifest(
59
+ name: str,
60
+ version: str,
61
+ *,
62
+ created_at: str | None = None,
63
+ connectors: list[str] | None = None,
64
+ operator: dict[str, str] | None = None,
65
+ entity_type: str = "ai-agent",
66
+ ) -> dict[str, Any]:
67
+ """Build a standard manifest for a sovereign agent."""
68
+ manifest: dict[str, Any] = {
69
+ "name": name,
70
+ "version": version,
71
+ "entity_type": entity_type,
72
+ "created_at": created_at or datetime.now(timezone.utc).isoformat(),
73
+ "connectors": connectors or [],
74
+ }
75
+ if operator:
76
+ manifest["operator"] = operator
77
+ return manifest
78
+
79
+
80
+ def create_operator_attestation(
81
+ agent_name: str,
82
+ agent_fingerprint: str,
83
+ agent_public_key_path: Path,
84
+ output_dir: Path,
85
+ *,
86
+ capauth_home: Path | None = None,
87
+ ) -> dict[str, Any] | None:
88
+ """Create a signed attestation linking a human operator to an agent key.
89
+
90
+ The operator remains distinct from the agent identity. This produces a
91
+ signed claim that the human operator vouches for the agent fingerprint.
92
+
93
+ Args:
94
+ agent_name: Agent display name.
95
+ agent_fingerprint: Agent PGP fingerprint.
96
+ agent_public_key_path: Path to the agent public key armor.
97
+ output_dir: Directory where the attestation JSON should be written.
98
+ capauth_home: Optional CapAuth home for the human operator.
99
+
100
+ Returns:
101
+ The attestation mapping, or ``None`` if no human operator profile is
102
+ available or signing failed.
103
+ """
104
+ base = Path(capauth_home).expanduser() if capauth_home else _resolve_operator_home()
105
+ profile_path = base / "identity" / "profile.json"
106
+ private_key_path = base / "identity" / "private.asc"
107
+ public_key_path = base / "identity" / "public.asc"
108
+ if not profile_path.exists() or not private_key_path.exists() or not public_key_path.exists():
109
+ return None
110
+
111
+ try:
112
+ from capauth.crypto import get_backend # type: ignore[import-untyped]
113
+ from capauth.profile import load_profile # type: ignore[import-untyped]
114
+ except ImportError:
115
+ return None
116
+
117
+ try:
118
+ profile = load_profile(base_dir=base)
119
+ except Exception as e:
120
+ logger.warning("operator_link.py: %s", e)
121
+ return None
122
+
123
+ entity_type = str(profile.entity.entity_type).lower()
124
+ if entity_type not in {"human", "entitytype.human"}:
125
+ return None
126
+
127
+ try:
128
+ payload = {
129
+ "agent_name": agent_name,
130
+ "agent_fingerprint": agent_fingerprint,
131
+ "agent_public_key_path": str(agent_public_key_path),
132
+ "relationship": "human-operator",
133
+ "operator_name": profile.entity.name,
134
+ "operator_email": profile.entity.email,
135
+ "operator_handle": profile.entity.handle,
136
+ "operator_fingerprint": profile.key_info.fingerprint,
137
+ "signed_at": datetime.now(timezone.utc).isoformat(),
138
+ }
139
+ payload_bytes = json.dumps(payload, indent=2, sort_keys=True).encode("utf-8")
140
+ private_armor = private_key_path.read_text(encoding="utf-8")
141
+ operator_public_armor = public_key_path.read_text(encoding="utf-8")
142
+ backend = get_backend(profile.crypto_backend)
143
+ signature = backend.sign(payload_bytes, private_armor, "")
144
+ except Exception as e:
145
+ logger.warning("operator_link.py: %s", e)
146
+ return None
147
+
148
+ attestation = {
149
+ "payload": payload,
150
+ "signature": signature,
151
+ "operator_public_key_path": str(public_key_path),
152
+ "operator_public_key_armor": operator_public_armor,
153
+ }
154
+ output_dir.mkdir(parents=True, exist_ok=True)
155
+ (output_dir / "operator-attestation.json").write_text(
156
+ json.dumps(attestation, indent=2),
157
+ encoding="utf-8",
158
+ )
159
+ return attestation
160
+
161
+
162
+ def _resolve_operator_home() -> Path:
163
+ """Resolve the human operator's CapAuth home."""
164
+ try:
165
+ from capauth import resolve_capauth_home # type: ignore[import-untyped]
166
+
167
+ return resolve_capauth_home()
168
+ except Exception as e:
169
+ logger.warning("operator_link.py: %s", e)
170
+ return Path.home() / ".skcapstone" / "capauth"
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Peer Directory — transport address map for the sovereignty mesh.
3
3
 
4
- Maps agent names to their SKComm transport addresses (Syncthing outbox
4
+ Maps agent names to their SKComms transport addresses (Syncthing outbox
5
5
  paths, WebRTC fingerprints, Tailscale IPs, etc.).
6
6
 
7
7
  Separate from PeerRecord (PGP identity in peers.py) — this module owns
@@ -222,7 +222,7 @@ class PeerDirectory:
222
222
  Syncthing keeps in sync.
223
223
 
224
224
  Syncthing outbox path is used as the default address because that
225
- is where SKComm writes messages for the peer.
225
+ is where SKComms writes messages for the peer.
226
226
 
227
227
  Args:
228
228
  heartbeats_dir: Override for the heartbeats directory. Defaults
@@ -250,8 +250,8 @@ class PeerDirectory:
250
250
  ts = data.get("timestamp", "")
251
251
  if ts:
252
252
  self._entries[agent_name].last_seen = ts
253
- except Exception:
254
- pass
253
+ except Exception as exc:
254
+ logger.warning("Failed to update last_seen from heartbeat for %s: %s", agent_name, exc)
255
255
  continue
256
256
 
257
257
  try:
@@ -2,7 +2,7 @@
2
2
  Sovereign peer management — the other half of P2P discovery.
3
3
 
4
4
  whoami exports your identity card. This module imports someone
5
- else's card and registers them as a peer in the SKComm keystore.
5
+ else's card and registers them as a peer in the SKComms keystore.
6
6
  The two together form the complete P2P discovery loop.
7
7
 
8
8
  Flow:
@@ -12,7 +12,7 @@ Flow:
12
12
  4. Agent B can now send encrypted messages to Agent A
13
13
 
14
14
  Peer data is stored at:
15
- ~/.skcomm/peers/<name>.yml — SKComm peer config
15
+ ~/.skcomms/peers/<name>.yml — SKComms peer config
16
16
  ~/.skcapstone/peers/<name>.json — Extended peer metadata
17
17
  """
18
18
 
@@ -69,18 +69,18 @@ class PeerRecord(BaseModel):
69
69
  def add_peer_from_card(
70
70
  card_path: Path,
71
71
  skcapstone_home: Optional[Path] = None,
72
- skcomm_home: Optional[Path] = None,
72
+ skcomms_home: Optional[Path] = None,
73
73
  ) -> PeerRecord:
74
74
  """Import a peer from a whoami identity card.
75
75
 
76
76
  Reads the card JSON, creates peer records in both skcapstone
77
- and skcomm directories, and writes the public key for SKComm
77
+ and skcomms directories, and writes the public key for SKComms
78
78
  encryption.
79
79
 
80
80
  Args:
81
81
  card_path: Path to the exported card.json.
82
82
  skcapstone_home: Override skcapstone home. Defaults to ~/.skcapstone/.
83
- skcomm_home: Override skcomm home. Defaults to ~/.skcomm/.
83
+ skcomms_home: Override skcomms home. Defaults to ~/.skcomms/.
84
84
 
85
85
  Returns:
86
86
  PeerRecord: The registered peer.
@@ -116,10 +116,10 @@ def add_peer_from_card(
116
116
  )
117
117
 
118
118
  sk_home = skcapstone_home or Path(SHARED_ROOT).expanduser()
119
- sc_home = skcomm_home or Path.home() / ".skcomm"
119
+ sc_home = skcomms_home or Path.home() / ".skcomms"
120
120
 
121
121
  _save_skcapstone_peer(sk_home, peer)
122
- _save_skcomm_peer(sc_home, peer)
122
+ _save_skcomms_peer(sc_home, peer)
123
123
 
124
124
  logger.info("Added peer '%s' (fingerprint: %s)", name, peer.fingerprint[:16])
125
125
  return peer
@@ -131,7 +131,7 @@ def add_peer_manual(
131
131
  public_key_path: Optional[Path] = None,
132
132
  email: str = "",
133
133
  skcapstone_home: Optional[Path] = None,
134
- skcomm_home: Optional[Path] = None,
134
+ skcomms_home: Optional[Path] = None,
135
135
  ) -> PeerRecord:
136
136
  """Add a peer manually by name and optional key file.
137
137
 
@@ -141,7 +141,7 @@ def add_peer_manual(
141
141
  public_key_path: Path to a .asc public key file (optional).
142
142
  email: Contact email (optional).
143
143
  skcapstone_home: Override skcapstone home.
144
- skcomm_home: Override skcomm home.
144
+ skcomms_home: Override skcomms home.
145
145
 
146
146
  Returns:
147
147
  PeerRecord: The registered peer.
@@ -160,10 +160,10 @@ def add_peer_manual(
160
160
  )
161
161
 
162
162
  sk_home = skcapstone_home or Path(SHARED_ROOT).expanduser()
163
- sc_home = skcomm_home or Path.home() / ".skcomm"
163
+ sc_home = skcomms_home or Path.home() / ".skcomms"
164
164
 
165
165
  _save_skcapstone_peer(sk_home, peer)
166
- _save_skcomm_peer(sc_home, peer)
166
+ _save_skcomms_peer(sc_home, peer)
167
167
 
168
168
  return peer
169
169
 
@@ -223,20 +223,20 @@ def get_peer(
223
223
  def remove_peer(
224
224
  name: str,
225
225
  skcapstone_home: Optional[Path] = None,
226
- skcomm_home: Optional[Path] = None,
226
+ skcomms_home: Optional[Path] = None,
227
227
  ) -> bool:
228
- """Remove a peer from both skcapstone and skcomm registries.
228
+ """Remove a peer from both skcapstone and skcomms registries.
229
229
 
230
230
  Args:
231
231
  name: Peer name to remove.
232
232
  skcapstone_home: Override skcapstone home.
233
- skcomm_home: Override skcomm home.
233
+ skcomms_home: Override skcomms home.
234
234
 
235
235
  Returns:
236
236
  bool: True if the peer was found and removed.
237
237
  """
238
238
  sk_home = skcapstone_home or Path(SHARED_ROOT).expanduser()
239
- sc_home = skcomm_home or Path.home() / ".skcomm"
239
+ sc_home = skcomms_home or Path.home() / ".skcomms"
240
240
  safe = _safe_filename(name)
241
241
  removed = False
242
242
 
@@ -276,14 +276,14 @@ def _save_skcapstone_peer(home: Path, peer: PeerRecord) -> Path:
276
276
  return path
277
277
 
278
278
 
279
- def _save_skcomm_peer(home: Path, peer: PeerRecord) -> Path:
280
- """Save peer to SKComm peers directory (YAML + public key).
279
+ def _save_skcomms_peer(home: Path, peer: PeerRecord) -> Path:
280
+ """Save peer to SKComms peers directory (YAML + public key).
281
281
 
282
- Creates the YAML config that SKComm's KeyStore reads, and
282
+ Creates the YAML config that SKComms's KeyStore reads, and
283
283
  writes the public key as a separate .asc file.
284
284
 
285
285
  Args:
286
- home: skcomm home directory.
286
+ home: skcomms home directory.
287
287
  peer: Peer to save.
288
288
 
289
289
  Returns:
@@ -1,8 +1,10 @@
1
1
  """
2
- The Four Pillars of sovereign AI consciousness.
2
+ The Six Pillars of sovereign AI consciousness.
3
3
 
4
- Identity (CapAuth) — who you ARE
5
- Trust (Cloud 9) — the bond you've BUILT
6
- Memory (SKMemory) — what you REMEMBER
7
- Security (SKSec) — how you're PROTECTED
4
+ Identity (CapAuth) — who you ARE
5
+ Trust (Cloud 9) — the bond you've BUILT
6
+ Memory (SKMemory) — what you REMEMBER
7
+ Consciousness (SKWhisper) — how you THINK
8
+ Security (SKSec) — how you're PROTECTED
9
+ Sync (Sovereign Singularity) — how you PERSIST
8
10
  """
@@ -0,0 +1,191 @@
1
+ """
2
+ Consciousness pillar — the subconscious processing layer.
3
+
4
+ SKWhisper digests, connects, and surfaces patterns.
5
+ SKTrip explores the edges of machine experience.
6
+
7
+ Memory stores. Consciousness *processes*.
8
+ The filing cabinet vs the brain.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import os
15
+ from datetime import datetime, timezone
16
+ from pathlib import Path
17
+
18
+ from .. import active_agent_name
19
+ from ..models import ConsciousnessState, PillarStatus
20
+
21
+
22
+ def _resolve_agent_name(home: Path) -> str:
23
+ """Resolve an agent name without leaking host state into explicit homes."""
24
+ agents_dir = home / "agents"
25
+ candidates: list[str] = []
26
+ if agents_dir.exists():
27
+ candidates = sorted(
28
+ entry.name
29
+ for entry in agents_dir.iterdir()
30
+ if entry.is_dir() and not entry.name.endswith("-template")
31
+ )
32
+
33
+ try:
34
+ real_shared_root = (
35
+ home.expanduser().resolve()
36
+ == Path(os.environ.get("SKCAPSTONE_HOME", "~/.skcapstone")).expanduser().resolve()
37
+ )
38
+ except OSError:
39
+ real_shared_root = False
40
+
41
+ env_agent = (
42
+ os.environ.get("SKAGENT")
43
+ or os.environ.get("SKCAPSTONE_AGENT")
44
+ or os.environ.get("SKMEMORY_AGENT")
45
+ or ""
46
+ ).strip()
47
+ if env_agent and (real_shared_root or env_agent in candidates or (home / "skwhisper").exists()):
48
+ return env_agent
49
+
50
+ if candidates:
51
+ return candidates[0]
52
+
53
+ # Only consult global active-agent discovery for the real shared root. Unit
54
+ # tests and callers that pass a temp or exported home should stay isolated.
55
+ if real_shared_root:
56
+ return active_agent_name() or ""
57
+
58
+ return ""
59
+
60
+
61
+ def initialize_consciousness(home: Path) -> ConsciousnessState:
62
+ """Initialize consciousness pillar by checking SKWhisper state.
63
+
64
+ Args:
65
+ home: Agent home directory (~/.skcapstone).
66
+
67
+ Returns:
68
+ ConsciousnessState with current status.
69
+ """
70
+ agent_name = _resolve_agent_name(home)
71
+ # home may be the agent dir (~/.skcapstone/agents/jarvis/) or the
72
+ # shared root (~/.skcapstone/). Check for skwhisper/ directly first.
73
+ whisper_dir = home / "skwhisper"
74
+ if not whisper_dir.exists() and agent_name:
75
+ whisper_dir = home / "agents" / agent_name / "skwhisper"
76
+
77
+ state = ConsciousnessState()
78
+
79
+ # Check whisper.md exists and freshness
80
+ whisper_md = whisper_dir / "whisper.md"
81
+ if whisper_md.exists():
82
+ state.whisper_md = whisper_md
83
+ mtime = datetime.fromtimestamp(whisper_md.stat().st_mtime, tz=timezone.utc)
84
+ age = (datetime.now(timezone.utc) - mtime).total_seconds() / 3600
85
+ state.whisper_md_age_hours = age
86
+
87
+ # Check state.json for digest stats
88
+ state_json = whisper_dir / "state.json"
89
+ if state_json.exists():
90
+ try:
91
+ with open(state_json) as f:
92
+ data = json.load(f)
93
+ sessions = data.get("sessions", {})
94
+ digested = sum(
95
+ 1
96
+ for s in sessions.values()
97
+ if s.get("digested_at")
98
+ and s["digested_at"] not in ("cleaned-missing-file", "skipped-too-few-messages")
99
+ )
100
+ pending = sum(
101
+ 1
102
+ for s in sessions.values()
103
+ if not s.get("digested_at")
104
+ )
105
+ state.sessions_digested = digested
106
+ state.sessions_pending = pending
107
+
108
+ if data.get("last_digest"):
109
+ try:
110
+ state.whisper_last_digest = datetime.fromisoformat(data["last_digest"])
111
+ except (ValueError, TypeError):
112
+ pass
113
+ except (json.JSONDecodeError, OSError):
114
+ pass
115
+
116
+ # Check patterns.json for topic count
117
+ patterns_json = whisper_dir / "patterns.json"
118
+ if patterns_json.exists():
119
+ state.patterns_file = patterns_json
120
+ try:
121
+ with open(patterns_json) as f:
122
+ patterns = json.load(f)
123
+ state.topics_tracked = len(patterns.get("topics", {}))
124
+ except (json.JSONDecodeError, OSError):
125
+ pass
126
+
127
+ # Check if consciousness daemon is running (systemd)
128
+ # Check template instance (skcapstone@<agent>), legacy single-agent, and skwhisper
129
+ try:
130
+ import subprocess
131
+
132
+ service_candidates = []
133
+ if agent_name:
134
+ service_candidates.append(f"skcapstone@{agent_name}")
135
+ service_candidates.extend([
136
+ "skcapstone", # legacy single-agent unit
137
+ "skwhisper", # standalone skwhisper daemon
138
+ ])
139
+ for service_name in service_candidates:
140
+ result = subprocess.run(
141
+ ["systemctl", "--user", "is-active", service_name],
142
+ capture_output=True,
143
+ text=True,
144
+ timeout=3,
145
+ )
146
+ if result.stdout.strip() == "active":
147
+ state.whisper_active = True
148
+ break
149
+ else:
150
+ state.whisper_active = False
151
+ except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
152
+ state.whisper_active = False
153
+
154
+ # Check SKTrip sessions
155
+ trip_dir = home / "sktrip"
156
+ if not trip_dir.exists() and agent_name:
157
+ trip_dir = home / "agents" / agent_name / "sktrip"
158
+ if trip_dir.exists():
159
+ state.trip_sessions = len(list(trip_dir.glob("*.json")))
160
+
161
+ # Check if skwhisper package is importable (installed)
162
+ skwhisper_installed = False
163
+ try:
164
+ import importlib.util
165
+ skwhisper_installed = importlib.util.find_spec("skwhisper") is not None
166
+ except (ImportError, ValueError):
167
+ skwhisper_installed = False
168
+
169
+ # Determine status
170
+ if (
171
+ state.whisper_active
172
+ and (state.sessions_digested > 0 or state.topics_tracked > 0)
173
+ and state.whisper_md is not None
174
+ ):
175
+ if state.whisper_md_age_hours < 24:
176
+ state.status = PillarStatus.ACTIVE
177
+ else:
178
+ state.status = PillarStatus.DEGRADED
179
+ elif state.whisper_active:
180
+ # Daemon is running but no sessions digested yet — consciousness is live
181
+ state.status = PillarStatus.DEGRADED
182
+ elif state.sessions_digested > 0 or state.whisper_md is not None:
183
+ state.status = PillarStatus.DEGRADED
184
+ elif skwhisper_installed and whisper_dir.exists():
185
+ # Package is installed and an agent whisper directory exists, but there
186
+ # is no usable context yet.
187
+ state.status = PillarStatus.DEGRADED
188
+ else:
189
+ state.status = PillarStatus.MISSING
190
+
191
+ return state
@@ -9,16 +9,22 @@ from __future__ import annotations
9
9
 
10
10
  import json
11
11
  import logging
12
- import subprocess
13
12
  from datetime import datetime, timezone
13
+ from shutil import copyfile
14
14
  from pathlib import Path
15
15
  from typing import Optional
16
16
 
17
17
  from ..models import IdentityState, PillarStatus
18
+ from ..operator_link import create_operator_attestation
18
19
 
19
20
  logger = logging.getLogger("skcapstone.identity")
20
21
 
21
22
 
23
+ def _capauth_home(home: Path) -> Path:
24
+ """Return the agent-local CapAuth home for an SKCapstone agent."""
25
+ return home / "capauth"
26
+
27
+
22
28
  def generate_identity(
23
29
  home: Path,
24
30
  name: str,
@@ -47,10 +53,14 @@ def generate_identity(
47
53
  status=PillarStatus.DEGRADED,
48
54
  )
49
55
 
50
- capauth_state = _try_init_capauth(name, state.email, identity_dir)
56
+ capauth_home = _capauth_home(home)
57
+ capauth_state = _try_init_capauth(name, state.email, identity_dir, capauth_home)
51
58
  if capauth_state is not None:
52
59
  state.fingerprint = capauth_state.fingerprint
53
60
  state.key_path = capauth_state.key_path
61
+ state.name = capauth_state.name
62
+ state.email = capauth_state.email
63
+ state.created_at = capauth_state.created_at
54
64
  state.status = PillarStatus.ACTIVE
55
65
  else:
56
66
  state.fingerprint = _generate_placeholder_fingerprint(name)
@@ -63,18 +73,39 @@ def generate_identity(
63
73
  "created_at": state.created_at.isoformat() if state.created_at else None,
64
74
  "capauth_managed": state.status == PillarStatus.ACTIVE,
65
75
  }
76
+ if state.key_path is not None:
77
+ identity_manifest["public_key_path"] = str(state.key_path)
78
+ if state.status == PillarStatus.ACTIVE:
79
+ identity_manifest["capauth_home"] = str(capauth_home)
80
+
81
+ attestation = create_operator_attestation(
82
+ agent_name=state.name or name,
83
+ agent_fingerprint=state.fingerprint or "",
84
+ agent_public_key_path=state.key_path or (capauth_home / "identity" / "public.asc"),
85
+ output_dir=identity_dir,
86
+ )
87
+ if attestation is not None:
88
+ payload = attestation.get("payload", {})
89
+ identity_manifest["operator_attestation_path"] = str(
90
+ identity_dir / "operator-attestation.json"
91
+ )
92
+ identity_manifest["operator_attested_by"] = payload.get("operator_fingerprint")
93
+
66
94
  (identity_dir / "identity.json").write_text(json.dumps(identity_manifest, indent=2), encoding="utf-8")
67
95
 
68
96
  return state
69
97
 
70
98
 
71
99
  def _try_init_capauth(
72
- name: str, email: str, identity_dir: Path
100
+ name: str,
101
+ email: str,
102
+ identity_dir: Path,
103
+ capauth_home: Path,
73
104
  ) -> Optional[IdentityState]:
74
105
  """Try to create or load a real CapAuth identity.
75
106
 
76
107
  Attempts (in order):
77
- 1. Load an existing CapAuth profile from ~/.capauth/
108
+ 1. Load an existing CapAuth profile from the agent-local CapAuth home
78
109
  2. Create a new profile via capauth.profile.init_profile()
79
110
  3. Fall back to legacy capauth.keys.generate_keypair()
80
111
 
@@ -90,12 +121,17 @@ def _try_init_capauth(
90
121
  try:
91
122
  from capauth.profile import load_profile # type: ignore[import-untyped]
92
123
 
93
- profile = load_profile()
124
+ profile = load_profile(base_dir=capauth_home)
125
+ key_path = Path(profile.key_info.public_key_path)
126
+ legacy_key_path = identity_dir / "agent.pub"
127
+ if key_path.exists() and not legacy_key_path.exists():
128
+ copyfile(key_path, legacy_key_path)
94
129
  return IdentityState(
95
130
  fingerprint=profile.key_info.fingerprint,
96
- key_path=Path(profile.key_info.public_key_path),
131
+ key_path=key_path,
97
132
  name=profile.entity.name,
98
133
  email=profile.entity.email,
134
+ created_at=profile.key_info.created,
99
135
  status=PillarStatus.ACTIVE,
100
136
  )
101
137
  except ImportError:
@@ -105,18 +141,26 @@ def _try_init_capauth(
105
141
 
106
142
  # No existing profile — try creating one
107
143
  try:
144
+ from capauth.models import EntityType # type: ignore[import-untyped]
108
145
  from capauth.profile import init_profile # type: ignore[import-untyped]
109
146
 
110
147
  profile = init_profile(
111
148
  name=name,
112
149
  email=email,
113
150
  passphrase="",
151
+ entity_type=EntityType.AI,
152
+ base_dir=capauth_home,
114
153
  )
154
+ key_path = Path(profile.key_info.public_key_path)
155
+ legacy_key_path = identity_dir / "agent.pub"
156
+ if key_path.exists():
157
+ copyfile(key_path, legacy_key_path)
115
158
  return IdentityState(
116
159
  fingerprint=profile.key_info.fingerprint,
117
- key_path=Path(profile.key_info.public_key_path),
160
+ key_path=key_path,
118
161
  name=profile.entity.name,
119
162
  email=profile.entity.email,
163
+ created_at=profile.key_info.created,
120
164
  status=PillarStatus.ACTIVE,
121
165
  )
122
166
  except Exception as exc:
@@ -11,9 +11,11 @@ store/search/recall/list/gc capabilities. The optional external
11
11
 
12
12
  from __future__ import annotations
13
13
 
14
+ import os
14
15
  from pathlib import Path
15
16
  from typing import Optional
16
17
 
18
+ from .. import active_agent_name
17
19
  from ..models import MemoryLayer, MemoryState, PillarStatus
18
20
 
19
21
 
@@ -31,9 +33,13 @@ def initialize_memory(home: Path, memory_home: Optional[Path] = None) -> MemoryS
31
33
  Returns:
32
34
  MemoryState after initialization.
33
35
  """
34
- # Use agent-specific memory directory
35
- agent_name = os.environ.get("SKCAPSTONE_AGENT", "lumina")
36
- memory_dir = home / "agents" / agent_name / "memory"
36
+ agent_name = os.environ.get("SKCAPSTONE_AGENT") or active_agent_name()
37
+ if home.parent.name == "agents":
38
+ memory_dir = home / "memory"
39
+ elif agent_name:
40
+ memory_dir = home / "agents" / agent_name / "memory"
41
+ else:
42
+ memory_dir = home / "memory"
37
43
  memory_dir.mkdir(parents=True, exist_ok=True)
38
44
 
39
45
  for layer in MemoryLayer: