threadkeeper 0.18.3__tar.gz → 0.19.0__tar.gz

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. {threadkeeper-0.18.3/threadkeeper.egg-info → threadkeeper-0.19.0}/PKG-INFO +66 -19
  2. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/README.md +64 -17
  3. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/pyproject.toml +5 -7
  4. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_daemon_liveness.py +1 -1
  5. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_dialectic_tier.py +3 -9
  6. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_error_paths.py +1 -1
  7. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_eval_harness.py +15 -1
  8. threadkeeper-0.19.0/tests/test_evolve_apply_43.py +97 -0
  9. threadkeeper-0.19.0/tests/test_evolve_apply_45.py +107 -0
  10. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_mcp_resources_prompts.py +3 -4
  11. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_mcp_sdk_compat.py +4 -5
  12. threadkeeper-0.19.0/tests/test_mcp_skills_extension.py +183 -0
  13. threadkeeper-0.19.0/tests/test_memory_authority_lifecycle.py +87 -0
  14. threadkeeper-0.19.0/tests/test_memory_compatibility.py +118 -0
  15. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_onnx_embeddings.py +4 -3
  16. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_release_workflows.py +7 -11
  17. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_tool_annotations.py +1 -1
  18. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/_mcp.py +47 -24
  19. threadkeeper-0.19.0/threadkeeper/authority.py +322 -0
  20. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/brief.py +106 -0
  21. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/config.py +9 -0
  22. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/db.py +109 -0
  23. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/dialectic_validator.py +6 -5
  24. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/elicitation.py +1 -2
  25. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/embeddings.py +136 -30
  26. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/eval/harness.py +95 -10
  27. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/forget.py +12 -0
  28. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/ingest.py +9 -0
  29. threadkeeper-0.19.0/threadkeeper/mcp_skills.py +327 -0
  30. threadkeeper-0.19.0/threadkeeper/memory_compat.py +356 -0
  31. threadkeeper-0.19.0/threadkeeper/migrate_embeddings.py +244 -0
  32. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/server.py +1 -0
  33. threadkeeper-0.19.0/threadkeeper/tools/authority.py +21 -0
  34. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/dashboard.py +5 -1
  35. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/dialectic.py +83 -9
  36. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/extract.py +5 -0
  37. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/lessons.py +46 -1
  38. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/pickup.py +6 -1
  39. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/session.py +14 -0
  40. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/skills.py +65 -11
  41. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/style.py +16 -2
  42. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/threads.py +28 -3
  43. {threadkeeper-0.18.3 → threadkeeper-0.19.0/threadkeeper.egg-info}/PKG-INFO +66 -19
  44. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper.egg-info/SOURCES.txt +9 -0
  45. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper.egg-info/requires.txt +1 -1
  46. threadkeeper-0.18.3/threadkeeper/migrate_embeddings.py +0 -162
  47. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/LICENSE +0 -0
  48. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/setup.cfg +0 -0
  49. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_adapters.py +0 -0
  50. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_agent_status.py +0 -0
  51. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_antigravity_ingest.py +0 -0
  52. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_auto_update.py +0 -0
  53. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_backup.py +0 -0
  54. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_brief_footprint.py +0 -0
  55. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_brief_sections.py +0 -0
  56. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_candidate_reviewer.py +0 -0
  57. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_ci_parallel.py +0 -0
  58. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_concepts.py +0 -0
  59. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_config_settings.py +0 -0
  60. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_config_watcher.py +0 -0
  61. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_core_memory.py +0 -0
  62. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_curator.py +0 -0
  63. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_daemon_host_config.py +0 -0
  64. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_daemon_host_integration.py +0 -0
  65. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_daemon_state.py +0 -0
  66. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_dashboard.py +0 -0
  67. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_db_compact.py +0 -0
  68. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_db_contention.py +0 -0
  69. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_db_leak_guard.py +0 -0
  70. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_db_migration_fts.py +0 -0
  71. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_db_runtime.py +0 -0
  72. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_db_schema.py +0 -0
  73. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_delegated_search.py +0 -0
  74. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_dialectic.py +0 -0
  75. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_dialectic_feed_tools.py +0 -0
  76. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_dialectic_miner.py +0 -0
  77. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_dialectic_observation_resolve.py +0 -0
  78. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_dialectic_recompute.py +0 -0
  79. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_dialectic_validator.py +0 -0
  80. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_dialog_fts_external.py +0 -0
  81. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_documentation_inventory.py +0 -0
  82. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_egress_policy.py +0 -0
  83. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_embed_routing.py +0 -0
  84. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_embedding_generation.py +0 -0
  85. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_embedding_model_revision.py +0 -0
  86. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_embedding_storage_dedup.py +0 -0
  87. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_evolve_applier.py +0 -0
  88. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_evolve_apply_2.py +0 -0
  89. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_evolve_apply_3.py +0 -0
  90. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_evolve_daemon.py +0 -0
  91. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_extract_daemon.py +0 -0
  92. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_extract_dedup.py +0 -0
  93. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_forget.py +0 -0
  94. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_github_budget.py +0 -0
  95. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_github_safety.py +0 -0
  96. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_helpers.py +0 -0
  97. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_hook_child_guard.py +0 -0
  98. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_host_embed.py +0 -0
  99. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_host_env.py +0 -0
  100. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_host_main.py +0 -0
  101. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_host_wedged.py +0 -0
  102. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_hybrid_retrieval.py +0 -0
  103. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_i18n_multilang.py +0 -0
  104. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_identity.py +0 -0
  105. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_ingest_cursor.py +0 -0
  106. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_ingest_denylist.py +0 -0
  107. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_ingest_secret_redaction.py +0 -0
  108. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_ingest_status.py +0 -0
  109. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_ingest_transactions.py +0 -0
  110. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_lesson_violations.py +0 -0
  111. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_lessons.py +0 -0
  112. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_memory_eval.py +0 -0
  113. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_memory_guard.py +0 -0
  114. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_memory_guard_host.py +0 -0
  115. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_menubar_app.py +0 -0
  116. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_missed_spawns.py +0 -0
  117. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_model_catalog.py +0 -0
  118. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_notify_daemon.py +0 -0
  119. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_nudges.py +0 -0
  120. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_panel.py +0 -0
  121. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_permissions.py +0 -0
  122. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_pickup.py +0 -0
  123. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_probe_daemon.py +0 -0
  124. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_process_health.py +0 -0
  125. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_retention.py +0 -0
  126. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_review_prompts.py +0 -0
  127. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_search_fts_punctuation.py +0 -0
  128. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_session_read_purity.py +0 -0
  129. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_setup.py +0 -0
  130. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_shadow_review.py +0 -0
  131. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_single_flight_prompt_prefixes.py +0 -0
  132. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_skill_hint.py +0 -0
  133. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_skill_passive_tier.py +0 -0
  134. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_skill_tier.py +0 -0
  135. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_skill_updater.py +0 -0
  136. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_skill_use_parser.py +0 -0
  137. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_skill_watcher.py +0 -0
  138. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_skills.py +0 -0
  139. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_spawn_background_cwd.py +0 -0
  140. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_spawn_budget.py +0 -0
  141. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_spawn_child_env.py +0 -0
  142. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_spawn_codex_stdin.py +0 -0
  143. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_spawn_config.py +0 -0
  144. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_spawn_hint.py +0 -0
  145. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_spawn_permission_gate.py +0 -0
  146. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_spawn_reap.py +0 -0
  147. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_spawn_result.py +0 -0
  148. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_spawn_slim.py +0 -0
  149. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_spawn_watchdog.py +0 -0
  150. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_spawn_worktree_isolation.py +0 -0
  151. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_spawn_wrap.py +0 -0
  152. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_sync_capture.py +0 -0
  153. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_sync_identity.py +0 -0
  154. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_sync_migrate.py +0 -0
  155. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_sync_protocol.py +0 -0
  156. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_sync_reset_node.py +0 -0
  157. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_sync_transport.py +0 -0
  158. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_task_retention.py +0 -0
  159. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_task_spool.py +0 -0
  160. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_thin_session.py +0 -0
  161. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_thread_janitor.py +0 -0
  162. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_threads.py +0 -0
  163. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_tools_smoke.py +0 -0
  164. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_validate_threads.py +0 -0
  165. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_vec_search.py +0 -0
  166. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_verify_ingest.py +0 -0
  167. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/__init__.py +0 -0
  168. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/_setup.py +0 -0
  169. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/_spawn_wrap.py +0 -0
  170. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/adapters/__init__.py +0 -0
  171. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/adapters/_hook_helpers.py +0 -0
  172. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/adapters/antigravity.py +0 -0
  173. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/adapters/base.py +0 -0
  174. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/adapters/claude_code.py +0 -0
  175. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/adapters/claude_desktop.py +0 -0
  176. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/adapters/codex.py +0 -0
  177. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/adapters/copilot.py +0 -0
  178. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/adapters/vscode.py +0 -0
  179. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/agent_metadata.py +0 -0
  180. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/agent_status.py +0 -0
  181. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/assets/macos-agent-status/Info.plist +0 -0
  182. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/assets/macos-agent-status/README.md +0 -0
  183. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/assets/macos-agent-status/ThreadKeeperAgentStatus.swift +0 -0
  184. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/assets/macos-agent-status/build.sh +0 -0
  185. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/assets/macos-agent-status/install.sh +0 -0
  186. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/auto_update.py +0 -0
  187. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/backup.py +0 -0
  188. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/candidate_reviewer.py +0 -0
  189. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/config_io.py +0 -0
  190. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/config_watcher.py +0 -0
  191. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/curator.py +0 -0
  192. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/curator_snapshots.py +0 -0
  193. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/daemon_liveness.py +0 -0
  194. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/daemon_state.py +0 -0
  195. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/daemon_supervisor.py +0 -0
  196. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/dialectic_miner.py +0 -0
  197. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/egress.py +0 -0
  198. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/eval/__init__.py +0 -0
  199. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/eval/__main__.py +0 -0
  200. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/evolve_applier.py +0 -0
  201. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/evolve_daemon.py +0 -0
  202. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/extract_daemon.py +0 -0
  203. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/github_budget.py +0 -0
  204. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/github_safety.py +0 -0
  205. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/harvest.py +0 -0
  206. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/helpers.py +0 -0
  207. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/host.py +0 -0
  208. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/host_embed.py +0 -0
  209. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/i18n.py +0 -0
  210. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/identity.py +0 -0
  211. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/lesson_violations.py +0 -0
  212. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/lessons.py +0 -0
  213. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/link_health.py +0 -0
  214. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/memory_guard.py +0 -0
  215. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/menubar_app.py +0 -0
  216. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/model_catalog.py +0 -0
  217. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/notify.py +0 -0
  218. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/nudges.py +0 -0
  219. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/permissions.py +0 -0
  220. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/probe_daemon.py +0 -0
  221. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/process_health.py +0 -0
  222. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/retention.py +0 -0
  223. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/retrieval.py +0 -0
  224. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/review_prompts.py +0 -0
  225. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/search_proxy.py +0 -0
  226. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/shadow_review.py +0 -0
  227. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/skill_audit.py +0 -0
  228. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/skill_updater.py +0 -0
  229. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/skill_watcher.py +0 -0
  230. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/spawn_budget.py +0 -0
  231. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/spawn_config.py +0 -0
  232. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/spawn_result.py +0 -0
  233. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/sync/__init__.py +0 -0
  234. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/sync/capture.py +0 -0
  235. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/sync/daemon.py +0 -0
  236. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/sync/identity.py +0 -0
  237. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/sync/migrate.py +0 -0
  238. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/sync/protocol.py +0 -0
  239. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/sync/reset_node.py +0 -0
  240. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/sync/server.py +0 -0
  241. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/task_spool.py +0 -0
  242. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/thread_janitor.py +0 -0
  243. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tool_schemas.py +0 -0
  244. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/__init__.py +0 -0
  245. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/agent_status.py +0 -0
  246. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/candidate_reviewer.py +0 -0
  247. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/concepts.py +0 -0
  248. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/config_watch.py +0 -0
  249. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/consolidate.py +0 -0
  250. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/core_memory.py +0 -0
  251. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/correlation.py +0 -0
  252. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/curator.py +0 -0
  253. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/db_maintenance.py +0 -0
  254. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/dialectic_feed.py +0 -0
  255. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/dialog.py +0 -0
  256. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/distill.py +0 -0
  257. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/evolve_applier.py +0 -0
  258. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/evolve_research.py +0 -0
  259. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/forget.py +0 -0
  260. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/graph.py +0 -0
  261. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/invariants.py +0 -0
  262. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/memory_guard.py +0 -0
  263. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/missed_spawns.py +0 -0
  264. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/panel.py +0 -0
  265. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/peers.py +0 -0
  266. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/probes.py +0 -0
  267. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/process_health.py +0 -0
  268. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/prompts.py +0 -0
  269. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/resources.py +0 -0
  270. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/shadow_review.py +0 -0
  271. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/spawn.py +0 -0
  272. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/sync.py +0 -0
  273. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/validate.py +0 -0
  274. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/trash.py +0 -0
  275. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/verify_ingest.py +0 -0
  276. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/wikilinks.py +0 -0
  277. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper.egg-info/dependency_links.txt +0 -0
  278. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper.egg-info/entry_points.txt +0 -0
  279. {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: threadkeeper
3
- Version: 0.18.3
3
+ Version: 0.19.0
4
4
  Summary: Multi-agent shared brain across Claude Code/Desktop, Codex, Antigravity CLI, Copilot, and VS Code. Cross-session memory, self-improving skill loops, inter-agent signaling — one local MCP server.
5
5
  Author: thread-keeper contributors
6
6
  License: MIT
@@ -22,7 +22,7 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
22
  Requires-Python: >=3.11
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
- Requires-Dist: mcp<3,>=1.10.0
25
+ Requires-Dist: mcp<3,>=2.2.0
26
26
  Requires-Dist: pydantic>=2.13.4
27
27
  Requires-Dist: pydantic-settings>=2.14.2
28
28
  Requires-Dist: pyyaml>=6.0.3
@@ -217,13 +217,12 @@ Cline, … — so a single registration there reaches all of them at once.
217
217
  Adding a new CLI = one file under `threadkeeper/adapters/` implementing
218
218
  the `CLIAdapter` contract. See [CONTRIBUTING.md](CONTRIBUTING.md).
219
219
 
220
- ### Python MCP SDK compatibility
220
+ ### Python MCP SDK surface
221
221
 
222
- thread-keeper supports MCP Python SDK 1.x and 2.x (`mcp>=1.10.0,<3`). Fresh
223
- installs resolve 2.x. A small internal adapter uses the SDK 2.x `MCPServer`
224
- name and falls back to its SDK 1.x `FastMCP` predecessor, so tools, resources,
225
- prompts, annotations, structured content, elicitation, and stdio behavior keep
226
- the same public contract across both supported SDK majors.
222
+ thread-keeper requires MCP Python SDK 2.2 or later (`mcp>=2.2.0,<3`). Its
223
+ stable Skills extension uses the MCP 2026-07-28 extension surface, while tools,
224
+ resources, prompts, annotations, structured content, elicitation, and stdio
225
+ continue to use the same `MCPServer` instance.
227
226
 
228
227
  ### MCP primitives (tools, resources, prompts, elicitation)
229
228
 
@@ -263,6 +262,29 @@ Hosts without a capability fall back to the SessionStart hook plus the `brief()`
263
262
  regression. Static URIs only for now (resource *templates* with `{param}` are
264
263
  still unevenly supported across hosts).
265
264
 
265
+ ### MCP Skills extension
266
+
267
+ On MCP 2026-07-28 hosts, thread-keeper also advertises the stable
268
+ `io.modelcontextprotocol/skills` extension. `skills/list` pages the canonical
269
+ library deterministically, and `skills/get` returns the full manifest for one
270
+ skill. Each resource URI is origin-qualified, for example
271
+ `skill://thread-keeper/release-check/SKILL.md`, so hosts retain the server
272
+ identity alongside a possibly colliding skill name.
273
+
274
+ The manifest covers `SKILL.md` and every permitted file below `references/`,
275
+ `templates/`, `scripts/`, or `assets/`, with its byte length and a
276
+ `sha256:<hex>` digest. Files are individually available through ordinary
277
+ `resources/read`; traversal, undeclared files, and reads over 1 MiB are
278
+ rejected. Hosts should compare both size and digest before using a retrieved
279
+ file. The canonical directory is published once; the existing per-CLI
280
+ filesystem mirrors remain the fallback for clients that do not implement the
281
+ extension.
282
+
283
+ Discovery and `resources/read` are delivery only. Reading `SKILL.md` does not
284
+ activate it, approve its tools, record use telemetry, or approve supporting
285
+ files. Activation and any user approval remain responsibilities of the host's
286
+ skill-loading path.
287
+
266
288
  ### Memory egress (cross-provider privacy)
267
289
 
268
290
  thread-keeper is "one user model … shared across CLIs," and that sharing is by
@@ -1269,6 +1291,17 @@ loops: a claim that surfaces in `brief()` and then gets "confirmed" by a
1269
1291
  review-fork reading the same dialog can't ride that internal evidence
1270
1292
  all the way to high confidence — internal evidence buys half as much.
1271
1293
 
1294
+ **Immutable authority and action confirmation.** Confidence and tier describe
1295
+ how useful a claim seems; they do not upgrade the authority of its evidence.
1296
+ New dialog, claim, evidence, lesson, and skill artifacts retain a write-time
1297
+ authority class plus their source principal/channel. Declared references such
1298
+ as `dialog:<uuid>`, `claim:<id>`, `lesson:<slug>`, and `skill:<name>` form a
1299
+ derivation graph; summaries and promotions inherit the least-authoritative
1300
+ root. Unknown writer origins are rejected. Before any consequential action,
1301
+ call `memory_authorize_action`: observed memory needs either an independent
1302
+ trusted principal or explicit `confirmed=True`. `forget` quarantines every
1303
+ derived descendant, so it can no longer be retrieved or authorize an action.
1304
+
1272
1305
  **Discrete tier on each claim** — `hypothesis → observed → validated`
1273
1306
  (plus `disputed`). Independent of the continuous confidence band; tier
1274
1307
  is the **action-gating** signal:
@@ -1385,6 +1418,9 @@ The most-used env knobs (full list in `threadkeeper/config.py`):
1385
1418
  | `THREADKEEPER_EMBED_BACKEND` | `onnx` | embedding runtime: `onnx` (fastembed, no PyTorch) or `sentence-transformers` (legacy fallback) |
1386
1419
  | `THREADKEEPER_EMBED_MODEL` | `paraphrase-multilingual-MiniLM-L12-v2` | 384-dim cross-lingual embedding model |
1387
1420
  | `THREADKEEPER_EMBED_REVISION` | backend-specific immutable commit | Hugging Face snapshot pin; set an intentional replacement commit only together with a re-embedding plan |
1421
+ | `THREADKEEPER_WRITER_PROVIDER` | active client | optional provider identity recorded for newly derived lessons, skills, notes, and claims |
1422
+ | `THREADKEEPER_WRITER_MODEL` | resolved model / `unknown` | optional writer-model identity recorded with derived-memory provenance |
1423
+ | `THREADKEEPER_WRITER_REVISION` | `unknown` | optional immutable writer-model revision recorded with derived-memory provenance |
1388
1424
  | `THREADKEEPER_EMBED_CACHE_DIR` | `~/.cache/huggingface/hub` | durable Hugging Face snapshot cache |
1389
1425
  | `THREADKEEPER_EMBED_LOCAL_FILES_ONLY` | false | require the pinned snapshot in the local Hugging Face cache (offline / air-gapped mode) |
1390
1426
  | `THREADKEEPER_SPAWNED_CHILD` | "" | spawn-internal marker; disables autonomous daemons in children |
@@ -1721,26 +1757,37 @@ export THREADKEEPER_EMBED_BACKEND=sentence-transformers
1721
1757
 
1722
1758
  # After any backend switch, homogenize the stored corpus so queries and
1723
1759
  # stored vectors live in the same space:
1724
- tk-migrate-embeddings --all # or --notes-only / --dialog-only
1725
- tk-migrate-embeddings --dry-run # report stale counts only
1760
+ tk-migrate-embeddings --all # stage both stores, validate, atomically activate
1761
+ tk-migrate-embeddings --notes-only # build part of a target; activation stays pending
1762
+ tk-migrate-embeddings --status # active/staging/coverage/validation report
1763
+ tk-migrate-embeddings --rollback # atomically return to the prior generation
1726
1764
  ```
1727
1765
 
1728
- The migration is batched, resumable, and idempotent (a second run finds
1729
- nothing stale). Both backends emit 384-dim vectors, so the `vec0` schema is
1730
- unchanged.
1766
+ The migration is batched, resumable, and idempotent. It stores target vectors
1767
+ under a separate generation while the durable active-generation pointer keeps
1768
+ the prior space queryable. An interrupted command resumes the staged target;
1769
+ readers never see it until `--all` validates full note/dialog coverage and
1770
+ switches the one pointer in a SQLite transaction. `--rollback` is the inverse
1771
+ transaction and leaves the failed target available for inspection or a later
1772
+ retry. Both backends emit 384-dim vectors, so the `vec0` schema is unchanged.
1731
1773
 
1732
1774
  **Intentional model revision upgrade.** Set `THREADKEEPER_EMBED_REVISION` to
1733
1775
  the immutable commit for the selected backend's Hugging Face artifact, restart
1734
1776
  the host, then run `tk-migrate-embeddings --all`. A changed revision is a new
1735
- embedding generation; until migration, old vectors remain available through
1736
- FTS rather than being compared against the new vector space.
1777
+ embedding generation. During staging, queries are encoded against the durable
1778
+ old generation and keep their semantic recall; the new configuration is not
1779
+ exposed until activation. The migration output and `mp_dashboard()` show
1780
+ active, staging, coverage, validation, and activation state.
1737
1781
 
1738
1782
  Stored rows carry an embedding-generation fingerprint, not just the backend:
1739
1783
  backend, model ID, vector dimension, pooling contract, and compatible runtime
1740
- version. Search never compares a current query vector with a stale generation;
1741
- those rows remain retrievable through FTS until `tk-migrate-embeddings` refreshes
1742
- them. `mp_dashboard()` shows total/current-generation/vec coverage for notes and
1743
- dialog rows.
1784
+ version. Newly derived lessons, skills, notes, and dialectic claims additionally
1785
+ record writer provider/model/revision plus source event or thread references;
1786
+ those records contain pointers and hashes, not another copy of transcript text.
1787
+ The fixed anonymized upgrade corpus runs both old→new and new→old paths before
1788
+ activation, with explicit recall, abstention, knowledge-update, and
1789
+ derived-memory decision thresholds. Run `python -m threadkeeper.eval` to print
1790
+ that release-gate report.
1744
1791
 
1745
1792
  Retrieval is hybrid by default. FTS candidate generation always runs, even
1746
1793
  when embeddings are installed or only part of the corpus has vectors. Dense
@@ -173,13 +173,12 @@ Cline, … — so a single registration there reaches all of them at once.
173
173
  Adding a new CLI = one file under `threadkeeper/adapters/` implementing
174
174
  the `CLIAdapter` contract. See [CONTRIBUTING.md](CONTRIBUTING.md).
175
175
 
176
- ### Python MCP SDK compatibility
176
+ ### Python MCP SDK surface
177
177
 
178
- thread-keeper supports MCP Python SDK 1.x and 2.x (`mcp>=1.10.0,<3`). Fresh
179
- installs resolve 2.x. A small internal adapter uses the SDK 2.x `MCPServer`
180
- name and falls back to its SDK 1.x `FastMCP` predecessor, so tools, resources,
181
- prompts, annotations, structured content, elicitation, and stdio behavior keep
182
- the same public contract across both supported SDK majors.
178
+ thread-keeper requires MCP Python SDK 2.2 or later (`mcp>=2.2.0,<3`). Its
179
+ stable Skills extension uses the MCP 2026-07-28 extension surface, while tools,
180
+ resources, prompts, annotations, structured content, elicitation, and stdio
181
+ continue to use the same `MCPServer` instance.
183
182
 
184
183
  ### MCP primitives (tools, resources, prompts, elicitation)
185
184
 
@@ -219,6 +218,29 @@ Hosts without a capability fall back to the SessionStart hook plus the `brief()`
219
218
  regression. Static URIs only for now (resource *templates* with `{param}` are
220
219
  still unevenly supported across hosts).
221
220
 
221
+ ### MCP Skills extension
222
+
223
+ On MCP 2026-07-28 hosts, thread-keeper also advertises the stable
224
+ `io.modelcontextprotocol/skills` extension. `skills/list` pages the canonical
225
+ library deterministically, and `skills/get` returns the full manifest for one
226
+ skill. Each resource URI is origin-qualified, for example
227
+ `skill://thread-keeper/release-check/SKILL.md`, so hosts retain the server
228
+ identity alongside a possibly colliding skill name.
229
+
230
+ The manifest covers `SKILL.md` and every permitted file below `references/`,
231
+ `templates/`, `scripts/`, or `assets/`, with its byte length and a
232
+ `sha256:<hex>` digest. Files are individually available through ordinary
233
+ `resources/read`; traversal, undeclared files, and reads over 1 MiB are
234
+ rejected. Hosts should compare both size and digest before using a retrieved
235
+ file. The canonical directory is published once; the existing per-CLI
236
+ filesystem mirrors remain the fallback for clients that do not implement the
237
+ extension.
238
+
239
+ Discovery and `resources/read` are delivery only. Reading `SKILL.md` does not
240
+ activate it, approve its tools, record use telemetry, or approve supporting
241
+ files. Activation and any user approval remain responsibilities of the host's
242
+ skill-loading path.
243
+
222
244
  ### Memory egress (cross-provider privacy)
223
245
 
224
246
  thread-keeper is "one user model … shared across CLIs," and that sharing is by
@@ -1225,6 +1247,17 @@ loops: a claim that surfaces in `brief()` and then gets "confirmed" by a
1225
1247
  review-fork reading the same dialog can't ride that internal evidence
1226
1248
  all the way to high confidence — internal evidence buys half as much.
1227
1249
 
1250
+ **Immutable authority and action confirmation.** Confidence and tier describe
1251
+ how useful a claim seems; they do not upgrade the authority of its evidence.
1252
+ New dialog, claim, evidence, lesson, and skill artifacts retain a write-time
1253
+ authority class plus their source principal/channel. Declared references such
1254
+ as `dialog:<uuid>`, `claim:<id>`, `lesson:<slug>`, and `skill:<name>` form a
1255
+ derivation graph; summaries and promotions inherit the least-authoritative
1256
+ root. Unknown writer origins are rejected. Before any consequential action,
1257
+ call `memory_authorize_action`: observed memory needs either an independent
1258
+ trusted principal or explicit `confirmed=True`. `forget` quarantines every
1259
+ derived descendant, so it can no longer be retrieved or authorize an action.
1260
+
1228
1261
  **Discrete tier on each claim** — `hypothesis → observed → validated`
1229
1262
  (plus `disputed`). Independent of the continuous confidence band; tier
1230
1263
  is the **action-gating** signal:
@@ -1341,6 +1374,9 @@ The most-used env knobs (full list in `threadkeeper/config.py`):
1341
1374
  | `THREADKEEPER_EMBED_BACKEND` | `onnx` | embedding runtime: `onnx` (fastembed, no PyTorch) or `sentence-transformers` (legacy fallback) |
1342
1375
  | `THREADKEEPER_EMBED_MODEL` | `paraphrase-multilingual-MiniLM-L12-v2` | 384-dim cross-lingual embedding model |
1343
1376
  | `THREADKEEPER_EMBED_REVISION` | backend-specific immutable commit | Hugging Face snapshot pin; set an intentional replacement commit only together with a re-embedding plan |
1377
+ | `THREADKEEPER_WRITER_PROVIDER` | active client | optional provider identity recorded for newly derived lessons, skills, notes, and claims |
1378
+ | `THREADKEEPER_WRITER_MODEL` | resolved model / `unknown` | optional writer-model identity recorded with derived-memory provenance |
1379
+ | `THREADKEEPER_WRITER_REVISION` | `unknown` | optional immutable writer-model revision recorded with derived-memory provenance |
1344
1380
  | `THREADKEEPER_EMBED_CACHE_DIR` | `~/.cache/huggingface/hub` | durable Hugging Face snapshot cache |
1345
1381
  | `THREADKEEPER_EMBED_LOCAL_FILES_ONLY` | false | require the pinned snapshot in the local Hugging Face cache (offline / air-gapped mode) |
1346
1382
  | `THREADKEEPER_SPAWNED_CHILD` | "" | spawn-internal marker; disables autonomous daemons in children |
@@ -1677,26 +1713,37 @@ export THREADKEEPER_EMBED_BACKEND=sentence-transformers
1677
1713
 
1678
1714
  # After any backend switch, homogenize the stored corpus so queries and
1679
1715
  # stored vectors live in the same space:
1680
- tk-migrate-embeddings --all # or --notes-only / --dialog-only
1681
- tk-migrate-embeddings --dry-run # report stale counts only
1716
+ tk-migrate-embeddings --all # stage both stores, validate, atomically activate
1717
+ tk-migrate-embeddings --notes-only # build part of a target; activation stays pending
1718
+ tk-migrate-embeddings --status # active/staging/coverage/validation report
1719
+ tk-migrate-embeddings --rollback # atomically return to the prior generation
1682
1720
  ```
1683
1721
 
1684
- The migration is batched, resumable, and idempotent (a second run finds
1685
- nothing stale). Both backends emit 384-dim vectors, so the `vec0` schema is
1686
- unchanged.
1722
+ The migration is batched, resumable, and idempotent. It stores target vectors
1723
+ under a separate generation while the durable active-generation pointer keeps
1724
+ the prior space queryable. An interrupted command resumes the staged target;
1725
+ readers never see it until `--all` validates full note/dialog coverage and
1726
+ switches the one pointer in a SQLite transaction. `--rollback` is the inverse
1727
+ transaction and leaves the failed target available for inspection or a later
1728
+ retry. Both backends emit 384-dim vectors, so the `vec0` schema is unchanged.
1687
1729
 
1688
1730
  **Intentional model revision upgrade.** Set `THREADKEEPER_EMBED_REVISION` to
1689
1731
  the immutable commit for the selected backend's Hugging Face artifact, restart
1690
1732
  the host, then run `tk-migrate-embeddings --all`. A changed revision is a new
1691
- embedding generation; until migration, old vectors remain available through
1692
- FTS rather than being compared against the new vector space.
1733
+ embedding generation. During staging, queries are encoded against the durable
1734
+ old generation and keep their semantic recall; the new configuration is not
1735
+ exposed until activation. The migration output and `mp_dashboard()` show
1736
+ active, staging, coverage, validation, and activation state.
1693
1737
 
1694
1738
  Stored rows carry an embedding-generation fingerprint, not just the backend:
1695
1739
  backend, model ID, vector dimension, pooling contract, and compatible runtime
1696
- version. Search never compares a current query vector with a stale generation;
1697
- those rows remain retrievable through FTS until `tk-migrate-embeddings` refreshes
1698
- them. `mp_dashboard()` shows total/current-generation/vec coverage for notes and
1699
- dialog rows.
1740
+ version. Newly derived lessons, skills, notes, and dialectic claims additionally
1741
+ record writer provider/model/revision plus source event or thread references;
1742
+ those records contain pointers and hashes, not another copy of transcript text.
1743
+ The fixed anonymized upgrade corpus runs both old→new and new→old paths before
1744
+ activation, with explicit recall, abstention, knowledge-update, and
1745
+ derived-memory decision thresholds. Run `python -m threadkeeper.eval` to print
1746
+ that release-gate report.
1700
1747
 
1701
1748
  Retrieval is hybrid by default. FTS candidate generation always runs, even
1702
1749
  when embeddings are installed or only part of the corpus has vectors. Dense
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "threadkeeper"
7
- version = "0.18.3"
7
+ version = "0.19.0"
8
8
  description = "Multi-agent shared brain across Claude Code/Desktop, Codex, Antigravity CLI, Copilot, and VS Code. Cross-session memory, self-improving skill loops, inter-agent signaling — one local MCP server."
9
9
  requires-python = ">=3.11"
10
10
  authors = [{ name = "thread-keeper contributors" }]
@@ -26,12 +26,10 @@ classifiers = [
26
26
  "Topic :: Scientific/Engineering :: Artificial Intelligence",
27
27
  ]
28
28
  dependencies = [
29
- # >=1.10.0 for the MCP 2025-06-18 tool vocabulary: ToolAnnotations
30
- # (readOnly/destructive/idempotent hints) + structured outputSchema (#67).
31
- # Supports the maintained SDK 1.x and 2.x lines. The narrow adapter in
32
- # _mcp.py maps the 1.x FastMCP name to the 2.x MCPServer name; <3 keeps a
33
- # future unreviewed major from changing the server at install time.
34
- "mcp>=1.10.0,<3",
29
+ # MCP 2.2 provides the 2026-07-28 extension surface used for the stable
30
+ # Skills distribution protocol. <3 keeps a future unreviewed major from
31
+ # changing the server at install time.
32
+ "mcp>=2.2.0,<3",
35
33
  "pydantic>=2.13.4",
36
34
  "pydantic-settings>=2.14.2",
37
35
  "pyyaml>=6.0.3",
@@ -9,7 +9,7 @@ def _loop(snapshot, loop_id: str):
9
9
  return {row["id"]: row for row in snapshot["loops"]}[loop_id]
10
10
 
11
11
 
12
- def test_daemon_health_is_additive_without_schema_version_bump(mp_with_cid):
12
+ def test_daemon_health_is_recreated_during_schema_migration(mp_with_cid):
13
13
  pkg = mp_with_cid("33334444-5555-6666-7777-888899990000")
14
14
  db = pkg["db"]
15
15
  conn = db.get_db()
@@ -125,18 +125,12 @@ def test_explicit_base_weight_still_multiplied_by_discount(
125
125
  assert row["weight"] == pytest.approx(0.4)
126
126
 
127
127
 
128
- def test_unknown_origin_no_discount(tmp_path, monkeypatch):
129
- """Falls back to multiplier 1.0 for write_origins not in the table.
130
- Tests don't break under custom origins; default behavior is no-op."""
128
+ def test_unknown_origin_fails_closed(tmp_path, monkeypatch):
129
+ """An unrecognised writer cannot mint a durable authority record."""
131
130
  pkg = _bootstrap(tmp_path, monkeypatch, write_origin="random_unknown")
132
131
  t = _tools(pkg)
133
132
  out = t["dialectic_claim"].fn(claim="probe", domain="style")
134
- cid = out.split()[1].split("=", 1)[1]
135
- t["dialectic_evidence"].fn(claim_id=cid, kind="support")
136
- row = pkg["db"].get_db().execute(
137
- "SELECT weight FROM dialectic_evidence WHERE claim_id=?", (cid,)
138
- ).fetchone()
139
- assert row["weight"] == 1.0
133
+ assert out == "ERR authority_unknown_origin"
140
134
 
141
135
 
142
136
  # ── weighted confidence formula ─────────────────────────────────────────
@@ -2,7 +2,7 @@
2
2
  on bad input rather than raising Python exceptions.
3
3
 
4
4
  A future error-boundary refactor (a `@safe_tool` decorator, schema validation,
5
- or a top-level FastMCP wrapper) must keep these tests green.
5
+ or a top-level MCPServer wrapper) must keep these tests green.
6
6
  """
7
7
  from __future__ import annotations
8
8
 
@@ -191,6 +191,19 @@ def test_cli_golden_baseline_is_clean():
191
191
  assert r["shadow"]["ready"] and r["candidate"]["ready"] and r["quality"]["ready"]
192
192
 
193
193
 
194
+ def test_upgrade_replay_regression_fails_the_release_gate(tmp_path):
195
+ """A one-way model regression must not be downgraded to PARTIAL."""
196
+ for fixture in FIXTURES.iterdir():
197
+ (tmp_path / fixture.name).write_text(fixture.read_text())
198
+ replay = json.loads((tmp_path / "upgrade_replay.json").read_text())
199
+ replay["items"][0]["new_to_old"]["retrieved_memory_ids"] = []
200
+ (tmp_path / "upgrade_replay.json").write_text(json.dumps(replay))
201
+
202
+ report = h.run_eval(tmp_path)
203
+ assert not report["upgrade"]["passed"]
204
+ assert report["verdict"] == "FAIL"
205
+
206
+
194
207
  def test_cli_llm_judge_without_key_exits_cleanly():
195
208
  env = {k: v for k, v in os.environ.items() if k != "ANTHROPIC_API_KEY"}
196
209
  proc = _run_cli("--judge", "llm", env=env)
@@ -211,7 +224,8 @@ _SECRET_PATTERNS = [
211
224
 
212
225
 
213
226
  def test_fixtures_have_no_secrets_or_private_paths():
214
- for name in ("shadow.json", "candidates.json", "skill_quality.json"):
227
+ for name in ("shadow.json", "candidates.json", "skill_quality.json",
228
+ "upgrade_replay.json"):
215
229
  text = (FIXTURES / name).read_text()
216
230
  # valid JSON
217
231
  json.loads(text)
@@ -0,0 +1,97 @@
1
+ """Golden coverage for surfacing the surgical lesson_patch action in brief().
2
+
3
+ The format should teach a fresh agent to make a narrow lesson correction with
4
+ the atomic operation rather than overwrite an otherwise-correct lesson body.
5
+ The seeded fixture also pins nearby long-lived brief sections.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import sys
10
+ import time
11
+ from pathlib import Path
12
+
13
+
14
+ _FAKE_CID = "eeee3333-4444-5555-6666-777788889999"
15
+
16
+
17
+ def _bootstrap(tmp_path, monkeypatch):
18
+ env = {
19
+ "THREADKEEPER_DB": str(tmp_path / "db.sqlite"),
20
+ "THREADKEEPER_LESSONS": str(tmp_path / "lessons.md"),
21
+ "CLAUDE_PROJECTS_DIR": str(tmp_path / "fake_claude_projects"),
22
+ "THREADKEEPER_INGEST_INTERVAL_S": "0",
23
+ "THREADKEEPER_INGEST_CAP": "0",
24
+ "THREADKEEPER_SKILL_WATCH_INTERVAL_S": "0",
25
+ "THREADKEEPER_SPAWN_BUDGET_POLL_S": "0",
26
+ "THREADKEEPER_SEARCH_PROXY_POLL_S": "0",
27
+ "THREADKEEPER_MEMORY_GUARD_POLL_S": "0",
28
+ "THREADKEEPER_SHADOW_REVIEW_INTERVAL_S": "0",
29
+ "THREADKEEPER_CURATOR_INTERVAL_S": "0",
30
+ "THREADKEEPER_EXTRACT_INTERVAL_S": "0",
31
+ "THREADKEEPER_CANDIDATE_REVIEW_INTERVAL_S": "0",
32
+ "THREADKEEPER_PROBE_INTERVAL_S": "0",
33
+ "THREADKEEPER_EVOLVE_REVIEW_INTERVAL_S": "0",
34
+ "THREADKEEPER_DISABLE_BG_DAEMONS": "1",
35
+ "THREADKEEPER_TASK_LOG_DIR": str(tmp_path / "tasks"),
36
+ "THREADKEEPER_CLIENT": "pytest",
37
+ "THREADKEEPER_FORCE_CID": _FAKE_CID,
38
+ "THREADKEEPER_NO_EMBEDDINGS": "1",
39
+ }
40
+ for key, value in env.items():
41
+ monkeypatch.setenv(key, value)
42
+ Path(env["CLAUDE_PROJECTS_DIR"]).mkdir(parents=True, exist_ok=True)
43
+ for name in [m for m in list(sys.modules) if m.startswith("threadkeeper")]:
44
+ del sys.modules[name]
45
+ import threadkeeper.server # noqa: F401
46
+ from threadkeeper import _mcp, db
47
+
48
+ return {"mcp": _mcp.mcp, "db": db}
49
+
50
+
51
+ def _tool(pkg, name):
52
+ return pkg["mcp"]._tool_manager._tools[name].fn
53
+
54
+
55
+ def _add_evolve(conn, suggestion):
56
+ conn.execute(
57
+ "INSERT INTO evolve (suggestion, applied, status, created_at) "
58
+ "VALUES (?,?,?,?)",
59
+ (suggestion, 0, "promoted", int(time.time())),
60
+ )
61
+ conn.commit()
62
+
63
+
64
+ def test_brief_surfaces_lesson_patch_and_preserves_existing_sections(
65
+ tmp_path, monkeypatch,
66
+ ):
67
+ pkg = _bootstrap(tmp_path, monkeypatch)
68
+ conn = pkg["db"].get_db()
69
+ lesson_append = _tool(pkg, "lesson_append")
70
+ open_thread = _tool(pkg, "open_thread")
71
+
72
+ lesson_append(
73
+ title="repair stale cross-links",
74
+ body="Use the current cross-link target.",
75
+ summary="Keep links current.",
76
+ source="foreground",
77
+ )
78
+ open_thread(question="a seeded open thread that must survive")
79
+ _add_evolve(conn, "a promoted suggestion that must surface")
80
+
81
+ from threadkeeper.brief import render_brief
82
+
83
+ text = render_brief(conn)
84
+
85
+ # New: a precise, atomic lesson-edit action is available at session start.
86
+ assert "lesson_patch" in text
87
+ assert "lesson_patch(slug, old_string, new_string)" in text
88
+ assert "unique lesson-body substring" in text
89
+
90
+ # Existing: the added section leaves the surrounding brief intact.
91
+ assert "open" in text
92
+ assert "a seeded open thread that must survive" in text
93
+ assert "evolve_pending" in text
94
+ assert "★" in text
95
+ assert "a promoted suggestion that must surface" in text
96
+ assert "user-facing" in text
97
+ assert "Do NOT cite internal IDs" in text
@@ -0,0 +1,107 @@
1
+ """Golden coverage for lesson recommendation reconciliation in brief()."""
2
+ from __future__ import annotations
3
+
4
+ import sys
5
+ import time
6
+ from pathlib import Path
7
+
8
+
9
+ _FAKE_CID = "bbbb3333-4444-5555-6666-777788889999"
10
+ _OPEN_Q = "seeded open thread remains visible"
11
+ _OLD_LESSON = "aws-sso-preflight-uses-sts"
12
+ _NEW_LESSON = "aws-sso-token-freshness-sts-is-cached"
13
+ _COMMAND = "aws sts get-caller-identity"
14
+
15
+
16
+ def _bootstrap(tmp_path, monkeypatch):
17
+ env = {
18
+ "THREADKEEPER_DB": str(tmp_path / "db.sqlite"),
19
+ "CLAUDE_PROJECTS_DIR": str(tmp_path / "fake_claude_projects"),
20
+ "THREADKEEPER_LESSONS": str(tmp_path / "lessons.md"),
21
+ "THREADKEEPER_DISABLE_BG_DAEMONS": "1",
22
+ "THREADKEEPER_INGEST_INTERVAL_S": "0",
23
+ "THREADKEEPER_INGEST_CAP": "0",
24
+ "THREADKEEPER_SKILL_WATCH_INTERVAL_S": "0",
25
+ "THREADKEEPER_SPAWN_BUDGET_POLL_S": "0",
26
+ "THREADKEEPER_SEARCH_PROXY_POLL_S": "0",
27
+ "THREADKEEPER_MEMORY_GUARD_POLL_S": "0",
28
+ "THREADKEEPER_SHADOW_REVIEW_INTERVAL_S": "0",
29
+ "THREADKEEPER_CURATOR_INTERVAL_S": "0",
30
+ "THREADKEEPER_EXTRACT_INTERVAL_S": "0",
31
+ "THREADKEEPER_CANDIDATE_REVIEW_INTERVAL_S": "0",
32
+ "THREADKEEPER_PROBE_INTERVAL_S": "0",
33
+ "THREADKEEPER_EVOLVE_REVIEW_INTERVAL_S": "0",
34
+ "THREADKEEPER_THREAD_JANITOR_INTERVAL_S": "0",
35
+ "THREADKEEPER_BRIEF_LEAN": "0",
36
+ "THREADKEEPER_TASK_LOG_DIR": str(tmp_path / "tasks"),
37
+ "THREADKEEPER_CLIENT": "pytest",
38
+ "THREADKEEPER_FORCE_CID": _FAKE_CID,
39
+ "THREADKEEPER_NO_EMBEDDINGS": "1",
40
+ }
41
+ for key, value in env.items():
42
+ monkeypatch.setenv(key, value)
43
+ Path(env["CLAUDE_PROJECTS_DIR"]).mkdir(parents=True, exist_ok=True)
44
+ for name in [module for module in list(sys.modules) if module.startswith("threadkeeper")]:
45
+ del sys.modules[name]
46
+ import threadkeeper.server # noqa: F401
47
+ from threadkeeper import _mcp, db, identity, lessons
48
+ return {"mcp": _mcp.mcp, "db": db, "identity": identity, "lessons": lessons}
49
+
50
+
51
+ def _tool(pkg, name):
52
+ return pkg["mcp"]._tool_manager._tools[name].fn
53
+
54
+
55
+ def _add_evolve(conn, suggestion):
56
+ conn.execute(
57
+ "INSERT INTO evolve (suggestion, applied, status, created_at) "
58
+ "VALUES (?,?,?,?)",
59
+ (suggestion, 0, "promoted", int(time.time())),
60
+ )
61
+ conn.commit()
62
+
63
+
64
+ def test_brief_surfaces_lesson_reconciliation_and_keeps_existing_sections(
65
+ tmp_path, monkeypatch,
66
+ ):
67
+ pkg = _bootstrap(tmp_path, monkeypatch)
68
+ conn = pkg["db"].get_db()
69
+
70
+ pkg["lessons"].append_lesson(
71
+ _OLD_LESSON,
72
+ body=(
73
+ f"Run `{_COMMAND}` to gate an AWS SSO freshness check before "
74
+ "using fixture-backed cloud tests."
75
+ ),
76
+ source="shadow",
77
+ )
78
+ pkg["lessons"].append_lesson(
79
+ _NEW_LESSON,
80
+ body=(
81
+ f"Never gate SSO freshness on `{_COMMAND}`: it is cached and can "
82
+ "return success after the token expires."
83
+ ),
84
+ source="shadow",
85
+ )
86
+
87
+ _tool(pkg, "open_thread")(question=_OPEN_Q)
88
+ _add_evolve(conn, "a promoted suggestion that must remain visible")
89
+
90
+ from threadkeeper.brief import render_brief
91
+ text = render_brief(conn)
92
+
93
+ # New behavior: a newer debunk flags the older recommendation by command
94
+ # and lesson slug, with a concrete reconciliation action.
95
+ assert "lesson_reconciliation" in text
96
+ assert f"`{_COMMAND}`" in text
97
+ assert f"debunked_by={_NEW_LESSON}" in text
98
+ assert f"still_recommended_by={_OLD_LESSON}" in text
99
+ assert "patch its recommendation or cross-link the correcting lesson" in text
100
+
101
+ # Regression: the established brief sections still survive this addition.
102
+ assert "open" in text
103
+ assert _OPEN_Q in text
104
+ assert "evolve_pending" in text
105
+ assert "★ \"a promoted suggestion that must remain visible\"" in text
106
+ assert "user-facing: paraphrase plain" in text
107
+ assert "Do NOT cite internal IDs" in text
@@ -158,10 +158,9 @@ def test_audit_threadkeeper_prompt_renders(fresh_mp):
158
158
  # ──────────────────────────────────────────────────────────────────────
159
159
 
160
160
  def test_server_advertises_resources_and_prompts_capabilities(fresh_mp):
161
- mcp = fresh_mp["mcp"]
162
- # MCP SDK 2.x renamed the high-level wrapper's low-level server handle.
163
- lowlevel = getattr(mcp, "_lowlevel_server", None) or mcp._mcp_server
164
- caps = lowlevel.get_capabilities(NotificationOptions(), {})
161
+ caps = fresh_mp["mcp"]._lowlevel_server.get_capabilities(
162
+ NotificationOptions(), {}, protocol_version="2026-07-28",
163
+ )
165
164
  assert caps.resources is not None
166
165
  assert caps.prompts is not None
167
166
  assert caps.tools is not None # tools unaffected
@@ -1,9 +1,8 @@
1
- """MCP SDK compatibility and stdio-server smoke coverage.
1
+ """MCP SDK 2.x stdio-server smoke coverage.
2
2
 
3
- The server keeps one narrow import adapter for MCP SDK 1.x and 2.x. This test
4
- starts the real ``python -m threadkeeper.server`` process and uses the legacy
5
- stdio handshake shared by both majors, then verifies the public tools registry
6
- is available. CI runs it once per supported SDK-major/Python combination.
3
+ The stable Skills extension requires MCP SDK 2.2 or later. This test starts
4
+ the real ``python -m threadkeeper.server`` process and verifies that the public
5
+ tools registry remains available over stdio on the supported SDK surface.
7
6
  """
8
7
  from __future__ import annotations
9
8