@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,117 @@
1
+ # sk* ⇄ skcapstone Integration — Orchestration Handoff
2
+
3
+ **Last updated:** 2026-06-09 (Lumina closeout session — EPIC COMPLETE)
4
+ **Epic:** coord `fca7f138` — "EPIC: sk* ⇄ skcapstone optional integration backbone"
5
+ **Design:** [`docs/ADR-optional-integration-backbone.md`](./ADR-optional-integration-backbone.md)
6
+ **Goal:** every sk* service uses skcapstone's **sk-alert** + **skscheduler** by default *when skcapstone is installed*, and runs fully standalone when it is not. Default-on by presence; `SK_STANDALONE=1` forces native.
7
+
8
+ > **EPIC CLOSED 2026-06-09:** Backbone + 8 adapters + dual-mode harness (114 tests green) + README docs DONE.
9
+ > Only skchat (`ad4f721a`) remains, owned by a separate thread.
10
+
11
+ ---
12
+
13
+ ## 1. Status at a glance
14
+
15
+ | Layer | State |
16
+ |---|---|
17
+ | Backbone (skcapstone) | ✅ **DONE** — 4/4 tasks, 208 tests green, pushed to `github/main` |
18
+ | Reference adapter (skmemory) | ✅ **DONE** — pushed `skmemory@docs-first-principles` |
19
+ | sksecurity adapter | ✅ **DONE** — pushed `sksecurity@main` |
20
+ | skgateway adapter (Node) | ✅ **DONE** — pushed `skgateway@main` |
21
+ | skcomms / capauth / skvoice / skseed / cloud9 adapters | ✅ **DONE** — all landed |
22
+ | skchat adapter | ⬜ OPEN — **owned by a separate thread**, do not touch |
23
+ | Dual-mode test harness (`71186ebb`) | ✅ **DONE** — `tests/test_integration_backbone.py` 114 green |
24
+ | Per-repo README docs (`4065db2b`) | ✅ **DONE** — all 8 repos have "Integration modes" section |
25
+
26
+ coord: All sk-integration tasks DONE except skchat (separate thread). EPIC `fca7f138` CLOSED.
27
+
28
+ ---
29
+
30
+ ## 2. The backbone (DONE — this is the stable contract everything builds on)
31
+
32
+ All in `skcapstone` (`src/skcapstone/`), committed `c5d8d7c`, `9d9f93d`, `d90dfbf` on `main` (pushed to `github`):
33
+
34
+ - **`sdk.py`** — the ONLY public surface consumers import. Semver-frozen:
35
+ - `is_available() -> bool`
36
+ - `alert(topic, payload, *, level='info', notify=None, ttl_seconds=86400) -> bool`
37
+ - `register_job(spec, home=None) -> str` / `unregister_job(name, home=None) -> bool`
38
+ - `coord_create(title, **kw) -> str`
39
+ - `register_service(name, health_url=None, pid_file=None, home=None) -> str`
40
+ - **`scheduler_jobs.py`** — `load_jobs_with_dropins()` merges `jobs.yaml` + `jobs.d/*.yaml`; `register_job()`/`unregister_job()` write atomic per-job fragments. Runtime callers (daemon `scheduled_tasks.py`, `cli/scheduler_cmd.py`, `doctor.py`) all repointed to the merged loader. **Honours `SKCAPSTONE_HOME`** (was a bug, fixed in `9d9f93d`).
41
+ - **`cli/alerts.py`** — `skcapstone alerts` subscribes to `*.critical`/`*.error`/`*.warn` and styles consumer topics by severity suffix.
42
+ - **`service_health.py`** — `check_all_services()` unions `~/.skcapstone/registry/*.json` (written by `register_service`) with the built-in checks.
43
+ - Tests: `tests/test_sdk.py`, `test_jobs_dropins.py`, `test_alerts_consumer_topics.py`, `test_service_registry.py`.
44
+
45
+ ---
46
+
47
+ ## 3. The adapter pattern (copy this for every remaining consumer)
48
+
49
+ **Canonical reference: `skmemory/skmemory/integration.py`** (commit `be33179`). Each adapter:
50
+
51
+ 1. Add `<pkg>/integration.py` with the optional-import guard:
52
+ ```python
53
+ try:
54
+ from skcapstone import sdk as _sdk
55
+ except Exception:
56
+ _sdk = None
57
+ def is_present() -> bool:
58
+ if os.environ.get("SK_STANDALONE"): return False
59
+ if _sdk is None: return False
60
+ try: return bool(_sdk.is_available())
61
+ except Exception: return False
62
+ ```
63
+ 2. `alert(event, payload, level)` → `_sdk.alert(f"{SERVICE}.{level}", {"event": event, **payload}, level=level, notify=level in {"warn","error","critical"})` when present, else structured log.
64
+ **CRITICAL CONVENTION:** topic is `<service>.<severity>` (e.g. `skvoice.error`), and the semantic **event name goes in the payload `event` field — NOT the topic suffix.** Otherwise `skcapstone alerts`' `*.error`/`*.critical`/`*.warn` wildcards never match it. (This was a real bug caught building skmemory.)
65
+ 3. `ensure_schedule()` → `_sdk.register_job({...})` (a `type: shell` job running the service's periodic CLI command) when present, else rely on the service's native systemd timer / thread loop.
66
+ 4. `register_self(pid_file=None)` → `_sdk.register_service(SERVICE, pid_file=...)`.
67
+ 5. **Wire into real call sites:** alert into the service's failure path; `ensure_schedule()` + `register_self()` into its startup / post-install lifecycle.
68
+ 6. Add an optional `[skcapstone]` extra to `pyproject.toml` (`skcapstone = ["skcapstone>=0.6.8"]`). **Never a hard dependency.**
69
+ 7. Tests `tests/test_integration_adapter.py`: standalone (`SK_STANDALONE=1`), absent (`monkeypatch.setattr(integration, "_sdk", None)`), integrated (sandbox `SKCAPSTONE_HOME` to `tmp_path` + `monkeypatch.setattr(skcapstone, "AGENT_HOME", str(tmp_path))`).
70
+
71
+ **Node/non-Python services (reference: `skgateway/src/integration.mjs`, commit `cc7bf1a`):** can't import the SDK — integrate **file-based** by writing the same `~/.skcapstone/pubsub/topics/<topic>/msg-*.json` and `~/.skcapstone/registry/<name>.json` formats. Validated round-trip: Node `alert()` → Python `PubSub.poll()` reads it back intact. Presence = shared home exists + `SK_STANDALONE` unset.
72
+
73
+ ---
74
+
75
+ ## 4. What's left (the next session's work-list)
76
+
77
+ All tagged `sk-integration` on the coord board. Run `skcapstone coord status` / inspect `~/.skcapstone/coordination/tasks/`.
78
+
79
+ ### Consumer adapters (mechanical mirrors of skmemory — for sonnet)
80
+ | Task | Repo | Native fallback to preserve | Suggested scheduled job |
81
+ |---|---|---|---|
82
+ | `eae9b815` | **skcomms** (`skcapstone-repos/skcomms`) | peer `_notify_others()` / log; daemon heartbeat thread + systemd | heartbeat/health; note skcomms is folding INTO skcomms (canonical) |
83
+ | `44b11628` | **capauth** | log (no native alerting yet) | key-rotation check (signing-daemon TODO — ensure_schedule may be a stub) |
84
+ | `66881a86` | **skvoice** | log; `while True` loop + skvoice systemd | service health / TTS-cache prune |
85
+ | `aaafe0d8` | **skseed** | log; pure kernel (periodic task type only) | belief-audit / germination; already duck-types AdaptedPrompt at `llm.py:40` |
86
+ | `fb925612` | **cloud9** | log; systemd `cloud9-daemon.timer` + launchd plist | rehydration/FEB-state check |
87
+ | `ad4f721a` | **skchat** | **OWNED BY ANOTHER THREAD — leave it.** Note: it already soft-bridges skcapstone MCP at `memory_bridge.py:24`; fold that into the one adapter. |
88
+
89
+ ### Cross-cutting
90
+ - `71186ebb` — **dual-mode test harness**: parametrized over all consumers, asserts standalone AND integrated mode. This is the system acceptance gate for the EPIC.
91
+ - `4065db2b` — per-repo README "Integration modes" + `~/.skcapstone/` filesystem-contract section.
92
+ - `6b9a41a1` — note task (reference-pattern pointer); close once all adapters land.
93
+
94
+ ### Final step
95
+ Once all adapters + cross-cutting are done, **complete the EPIC `fca7f138`** and consider squashing the `skcomms` superseded task notes.
96
+
97
+ ---
98
+
99
+ ## 5. Gotchas / things to know
100
+
101
+ - **coord CLI:** `skcapstone coord claim <id> --agent <name>` and `complete <id> --agent <name>` (the `--agent` flag is required). `create` uses `--by`. Tasks are immutable after creation (no edit/update subcommand) — supersede by completing + creating a replacement.
102
+ - **Don't commit the other threads' work.** In `skcapstone` the skcomms→skcomms migration shares the repo; in `skmemory` the mxbai-cutover changes (`backends/pgvector_backend.py`, `cli.py`) are unstaged from another thread. Stage only your adapter files explicitly — never `git add -A`.
103
+ - **skcapstone remotes:** `github` is canonical (per `~/clawd/scripts/push-pending.sh`); `origin` and `forgejo` also exist and may be stale (the local `@{u}` tracks `forgejo`, which lags — verify against `github/main`).
104
+ - **Test sandboxing:** consumers without a conftest that sets `SKCAPSTONE_HOME` must sandbox it per-test, AND `monkeypatch.setattr(skcapstone, "AGENT_HOME", str(tmp_path))` because skcapstone captures `AGENT_HOME` at import.
105
+ - **Pre-existing broken tests (NOT yours):** `sksecurity/tests/test_truth_engine.py` fails to collect (imports a missing `_check_skmemory`); `skgateway/tests/classifier.test.mjs` has 2 pre-existing failures. Run adapter tests by file to avoid these.
106
+ - **Leak check after integrated tests:** `ls ~/.skcapstone/config/jobs.d/<svc>_*.yaml ~/.skcapstone/registry/<svc>.json` should be clean — if a fragment leaks to the real home, a test isn't sandboxing `SKCAPSTONE_HOME`.
107
+
108
+ ---
109
+
110
+ ## 6. Commits / branches (all pushed)
111
+
112
+ | Repo | Branch | Commits |
113
+ |---|---|---|
114
+ | skcapstone | `main` (→ `github`) | `c5d8d7c` backbone · `9d9f93d` home-fix+convention · `d90dfbf` ADR §3.5 |
115
+ | skmemory | `docs-first-principles` | `be33179` reference adapter |
116
+ | sksecurity | `main` | `e65979b` threat-sharing adapter |
117
+ | skgateway | `main` | `cc7bf1a` Node file-based bridge |
@@ -0,0 +1,155 @@
1
+ # skscheduler — Unified Fleet Job Scheduler
2
+
3
+ > The single declarative registry + per-node runner for all recurring jobs across the
4
+ > SK fleet. One `jobs.yaml`, synced everywhere; each node runs its own scheduler thread
5
+ > (inside the skcapstone daemon) and fires only the jobs whose affinity includes it.
6
+ > Design spec: [`docs/superpowers/specs/2026-06-08-skscheduler-design.md`](superpowers/specs/2026-06-08-skscheduler-design.md).
7
+
8
+ ## Why it exists
9
+ Scheduling was fragmented across **four** mechanisms (skcapstone `TaskScheduler` interval
10
+ callbacks, legacy crontab, systemd user timers, Claude-Code crons) with no single place to
11
+ define, run, or observe jobs — and `service_health` running on multiple nodes caused
12
+ Syncthing `.sync-conflict-*` files (the root incident). skscheduler unifies this: **one
13
+ registry, per-job node-affinity** (so a job runs on exactly the intended node — preventing
14
+ the multi-writer class), cron **and** interval, and agent-judgment jobs (not just Python).
15
+
16
+ ## Architecture
17
+
18
+ ```mermaid
19
+ flowchart TD
20
+ Y["~/.skcapstone/config/jobs.yaml<br/>(Syncthing-synced registry)"] -->|read by every node| D
21
+ subgraph node["each fleet node (.158, .41, …)"]
22
+ D["skcapstone daemon<br/>(systemd user service)"] --> TS["TaskScheduler<br/>tick loop (5s)"]
23
+ TS -->|interval callbacks| IC["heartbeat · memory_promotion<br/>dreaming_reflection · reprobe"]
24
+ TS -->|config jobs| DUE{"due? (cron|interval)<br/>AND node in affinity?"}
25
+ DUE -->|yes| LK["per-job overlap lock<br/>(singleton; skip if running)"]
26
+ LK --> JIT["jitter splay"] --> RUN["JobRunner.run + retries/backoff"]
27
+ RUN --> DISP{type}
28
+ DISP -->|python| PY["import module:fn()"]
29
+ DISP -->|shell| SH["subprocess (timeout)"]
30
+ DISP -->|agent| AG["claude -p --agent NAME"]
31
+ RUN --> STATE["SchedulerState.record_run<br/>(node-local, NEVER synced)"]
32
+ RUN --> NOTIFY["notify? → sk-alert (Telegram)"]
33
+ end
34
+ STATE --> SJ["~/.skcapstone/scheduler/&lt;host&gt;/state.json"]
35
+ RUN --> LOG["~/.skcapstone/scheduler/&lt;host&gt;/logs/&lt;job&gt;-&lt;ts&gt;.log"]
36
+ ```
37
+
38
+ **Sync-safety invariant:** the registry (`jobs.yaml`) is synced; **run-state and logs are
39
+ node-local and never synced** (`~/.skcapstone/.stignore` excludes `scheduler/`). The
40
+ scheduler can never become a sync-conflict source.
41
+
42
+ ## Job lifecycle (one due job)
43
+
44
+ ```mermaid
45
+ sequenceDiagram
46
+ participant Loop as TaskScheduler tick
47
+ participant Due as is_due()
48
+ participant Aff as job_runs_here()
49
+ participant Lock as overlap lock
50
+ participant Run as JobRunner
51
+ participant St as SchedulerState
52
+ participant Al as sk-alert
53
+ Loop->>Due: cron slot passed / interval elapsed since last_run?
54
+ Due-->>Loop: due
55
+ Loop->>Aff: this node's alias in job.nodes?
56
+ Aff-->>Loop: yes
57
+ Loop->>Lock: acquire (non-blocking)
58
+ alt previous run still going
59
+ Lock-->>Loop: busy → skip (no double-run)
60
+ else free
61
+ Lock-->>Loop: got it (in a worker thread)
62
+ Loop->>Run: jitter splay, then attempt 1..N
63
+ loop until ok or retries exhausted
64
+ Run->>Run: dispatch(type) with timeout
65
+ Run-->>Loop: ok? else backoff + retry
66
+ end
67
+ Loop->>St: record_run(ok, error, fire_time)
68
+ Loop->>Al: notify policy met? → DM Chef
69
+ end
70
+ ```
71
+
72
+ ## Due-check logic
73
+
74
+ ```mermaid
75
+ flowchart LR
76
+ A{schedule set?} -->|cron| C["most-recent cron slot ≤ now"]
77
+ C --> C2{"last_run < that slot?"}
78
+ C2 -->|yes| DUE([due])
79
+ C2 -->|no| NO([not due])
80
+ A -->|every_seconds| I{"now − last_run ≥ interval?"}
81
+ I -->|yes| DUE
82
+ I -->|no| NO
83
+ A -->|neither| NO
84
+ ```
85
+ Cron uses `croniter` (a declared dependency — if missing, **cron jobs silently can't fire**;
86
+ `pip install -e .` in the venv fixes it). Missed slots are caught up once on next tick
87
+ (misfire catch-up); per-job `catchup: false` opts out.
88
+
89
+ ## `jobs.yaml` schema (complete)
90
+
91
+ ```yaml
92
+ jobs:
93
+ <job-name>: # YAML key = unique job id
94
+ # — scheduling (exactly one) —
95
+ schedule: "0 6 * * *" # cron (m h dom mon dow)
96
+ every: 5m # OR interval: 30s | 5m | 1h | 1d | <seconds>
97
+ # — what to run (by type) —
98
+ type: agent # agent | shell | python (default python)
99
+ agent: lumina # agent type → runs `claude -p "<prompt>" --agent <name>`
100
+ prompt: "…" # agent prompt
101
+ command: "skmem-pg-backup"# shell type → subprocess
102
+ callback: "module.path:fn"# python type → import & call
103
+ # — placement —
104
+ nodes: [".41"] # affinity: "all" | [host aliases] (alias = SK_NODE_ALIAS)
105
+ # — limits / reliability (added 2026-06-09) —
106
+ timeout: 900 # hard-kill seconds (default 900)
107
+ retries: 0 # extra attempts on failure (linear)
108
+ retry_backoff: 0 # seconds between attempts
109
+ jitter: 0 # max random splay (s) before dispatch — fleet anti-stampede
110
+ notify: off # off | on_failure | on_success | always → sk-alert (Telegram)
111
+ notify_level: warn # sk-alert level for failures (info|warn|crit)
112
+ catchup: true # run once on a missed slot (default true)
113
+ enabled: true
114
+ ```
115
+
116
+ ## Reliability features (2026-06-09)
117
+ | field | purpose | infra it serves |
118
+ |---|---|---|
119
+ | `retries` + `retry_backoff` | re-attempt on transient failure | flaky network/registry/LLM endpoints |
120
+ | `jitter` | random splay before dispatch | many nodes sharing a cron slot hitting one resource |
121
+ | `notify` (+`notify_level`) | sk-alert on `on_failure`/`on_success`/`always` with output tail | unattended jobs; result delivery (e.g. sktrip) |
122
+ | `catchup` | catch a missed slot once | laptops/asleep nodes (vs strict real-time jobs) |
123
+ | overlap lock | singleton per job | long runs that exceed their interval |
124
+ | node affinity | exactly-one-node execution | stateful/single-writer jobs (the original conflict fix) |
125
+
126
+ ## State, logs, observability
127
+ - **State** (node-local): `~/.skcapstone/scheduler/<host>/state.json` — `last_run`, `ok`, `error`, run counts. Never synced.
128
+ - **Logs** (node-local): `~/.skcapstone/scheduler/<host>/logs/<job>-<ts>.log` — captured stdout+stderr.
129
+ - **CLI**: `skcapstone scheduler …` (list / run-now / status). `skcapstone doctor` validates scheduler health.
130
+
131
+ ## Operating it
132
+ ```bash
133
+ systemctl --user status skcapstone.service # the daemon that runs the scheduler
134
+ systemctl --user start skcapstone.service # ⚠ if inactive, NO jobs.yaml jobs fire
135
+ echo "$SK_NODE_ALIAS" # must match a value in a job's nodes: list
136
+ skcapstone scheduler status # last run / ok / errors per job
137
+ ```
138
+
139
+ ## Troubleshooting
140
+ | symptom | cause | fix |
141
+ |---|---|---|
142
+ | no jobs fire | `skcapstone.service` inactive | `systemctl --user start skcapstone.service` |
143
+ | cron jobs never fire (interval ok) | `croniter` not installed in venv | `~/.skenv/bin/pip install -e .` |
144
+ | job runs on wrong/extra node | `nodes:` affinity / `SK_NODE_ALIAS` mismatch | set the env / fix the list |
145
+ | job overlaps itself | run time > interval | raise interval, or rely on the overlap lock (it skips) |
146
+ | no failure alerts | `notify: off` | set `notify: on_failure` |
147
+
148
+ ## Relationship to other schedulers
149
+ - **systemd user timers** — keep for OS-level/boot jobs and as the process supervisor for the daemon itself; migrate *application* recurring jobs here.
150
+ - **Claude-Code crons** — separate system (cloud agents); documented, not migrated.
151
+ - **legacy crontab** — retire into `jobs.yaml`.
152
+
153
+ ## Roadmap (not yet implemented)
154
+ job dependencies (`after:`), dead-man's-switch staleness alerts, per-job env/secrets injection,
155
+ a web/TUI run-history view, and distributed locking (currently affinity-by-declaration, not election).
@@ -0,0 +1,31 @@
1
+ # Reference for ~/.skcapstone/config/jobs.yaml — the skscheduler registry.
2
+ #
3
+ # This file is Syncthing-synced across the fleet. Each node runs its own
4
+ # scheduler (inside the skcapstone daemon) and fires ONLY the jobs whose
5
+ # `nodes` affinity includes that host. Scheduler run-state is node-local and
6
+ # never synced (see ~/.skcapstone/.stignore -> `scheduler`).
7
+ #
8
+ # Job fields:
9
+ # schedule: "<cron>" OR every: <30s|5m|1h|1d|seconds> (one of the two)
10
+ # type: agent | shell | python
11
+ # nodes: all | [<host alias>, ...] (alias via SK_NODE_ALIAS env)
12
+ # agent: <name> (agent type) — runs `claude -p "<prompt>" --agent <name>`
13
+ # prompt: "<text>" (agent type)
14
+ # command: "<cmd>" (shell type)
15
+ # callback: module:fn (python type)
16
+ # timeout: <seconds> (default 900)
17
+ # enabled: true|false
18
+
19
+ jobs:
20
+ gtd-inbox-triage:
21
+ schedule: "0 6 * * *" # daily 06:00
22
+ type: agent
23
+ nodes: [".41"]
24
+ agent: lumina
25
+ prompt: >
26
+ Triage the GTD inbox: for each item, clarify into next-action /
27
+ project / someday-maybe, or archive noise; move resolved-ITIL items
28
+ to done; surface stale projects. Use the gtd_* and itil_* MCP tools.
29
+ Keep it concise.
30
+ timeout: 900
31
+ enabled: true