threadkeeper 0.17.7__tar.gz → 0.18.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 (268) hide show
  1. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/PKG-INFO +170 -73
  2. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/README.md +168 -71
  3. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/pyproject.toml +5 -6
  4. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_adapters.py +64 -0
  5. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_agent_status.py +57 -0
  6. threadkeeper-0.18.0/tests/test_antigravity_ingest.py +217 -0
  7. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_auto_update.py +27 -0
  8. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_candidate_reviewer.py +45 -0
  9. threadkeeper-0.18.0/tests/test_ci_parallel.py +48 -0
  10. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_config_settings.py +4 -1
  11. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_curator.py +709 -12
  12. threadkeeper-0.18.0/tests/test_db_leak_guard.py +136 -0
  13. threadkeeper-0.18.0/tests/test_documentation_inventory.py +60 -0
  14. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_evolve_applier.py +292 -28
  15. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_evolve_daemon.py +93 -6
  16. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_i18n_multilang.py +23 -1
  17. threadkeeper-0.18.0/tests/test_lesson_violations.py +91 -0
  18. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_mcp_resources_prompts.py +4 -1
  19. threadkeeper-0.18.0/tests/test_mcp_sdk_compat.py +144 -0
  20. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_menubar_app.py +1 -1
  21. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_notify_daemon.py +68 -2
  22. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_panel.py +20 -0
  23. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_pickup.py +18 -0
  24. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_probe_daemon.py +16 -0
  25. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_release_workflows.py +20 -14
  26. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_single_flight_prompt_prefixes.py +10 -0
  27. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_skill_watcher.py +41 -0
  28. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_skills.py +25 -0
  29. threadkeeper-0.18.0/tests/test_spawn_background_cwd.py +138 -0
  30. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_spawn_budget.py +89 -0
  31. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_spawn_codex_stdin.py +49 -4
  32. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_spawn_permission_gate.py +61 -7
  33. threadkeeper-0.18.0/tests/test_spawn_result.py +17 -0
  34. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_spawn_watchdog.py +41 -0
  35. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_thread_janitor.py +20 -0
  36. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_tool_annotations.py +39 -16
  37. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_verify_ingest.py +13 -0
  38. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/_mcp.py +20 -4
  39. threadkeeper-0.18.0/threadkeeper/adapters/antigravity.py +387 -0
  40. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/adapters/base.py +28 -0
  41. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/adapters/codex.py +50 -1
  42. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/agent_metadata.py +12 -0
  43. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/agent_status.py +25 -5
  44. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/auto_update.py +22 -2
  45. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/candidate_reviewer.py +38 -11
  46. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/config.py +43 -0
  47. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/curator.py +1379 -276
  48. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/db.py +198 -19
  49. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/elicitation.py +2 -1
  50. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/evolve_applier.py +181 -31
  51. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/evolve_daemon.py +28 -14
  52. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/github_safety.py +10 -4
  53. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/host.py +31 -0
  54. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/i18n.py +51 -18
  55. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/ingest.py +25 -7
  56. threadkeeper-0.18.0/threadkeeper/lesson_violations.py +99 -0
  57. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/notify.py +35 -5
  58. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/probe_daemon.py +8 -2
  59. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/server.py +1 -1
  60. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/shadow_review.py +12 -5
  61. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/skill_watcher.py +65 -2
  62. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/spawn_budget.py +42 -20
  63. threadkeeper-0.18.0/threadkeeper/spawn_result.py +47 -0
  64. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/thread_janitor.py +28 -9
  65. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tool_schemas.py +1 -1
  66. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/curator.py +125 -5
  67. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/dashboard.py +31 -0
  68. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/dialectic.py +1 -3
  69. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/lessons.py +40 -0
  70. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/panel.py +6 -3
  71. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/pickup.py +26 -8
  72. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/process_health.py +5 -0
  73. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/resources.py +1 -1
  74. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/skills.py +6 -1
  75. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/spawn.py +345 -167
  76. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/verify_ingest.py +11 -7
  77. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper.egg-info/PKG-INFO +170 -73
  78. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper.egg-info/SOURCES.txt +10 -0
  79. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper.egg-info/requires.txt +1 -1
  80. threadkeeper-0.17.7/threadkeeper/adapters/antigravity.py +0 -193
  81. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/LICENSE +0 -0
  82. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/setup.cfg +0 -0
  83. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_backup.py +0 -0
  84. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_brief_footprint.py +0 -0
  85. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_brief_sections.py +0 -0
  86. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_concepts.py +0 -0
  87. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_config_watcher.py +0 -0
  88. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_core_memory.py +0 -0
  89. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_daemon_host_config.py +0 -0
  90. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_daemon_host_integration.py +0 -0
  91. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_daemon_liveness.py +0 -0
  92. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_daemon_state.py +0 -0
  93. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_dashboard.py +0 -0
  94. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_db_compact.py +0 -0
  95. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_db_contention.py +0 -0
  96. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_db_migration_fts.py +0 -0
  97. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_db_runtime.py +0 -0
  98. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_db_schema.py +0 -0
  99. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_delegated_search.py +0 -0
  100. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_dialectic.py +0 -0
  101. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_dialectic_feed_tools.py +0 -0
  102. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_dialectic_miner.py +0 -0
  103. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_dialectic_observation_resolve.py +0 -0
  104. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_dialectic_recompute.py +0 -0
  105. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_dialectic_tier.py +0 -0
  106. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_dialectic_validator.py +0 -0
  107. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_dialog_fts_external.py +0 -0
  108. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_egress_policy.py +0 -0
  109. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_embed_routing.py +0 -0
  110. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_embedding_generation.py +0 -0
  111. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_embedding_model_revision.py +0 -0
  112. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_embedding_storage_dedup.py +0 -0
  113. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_error_paths.py +0 -0
  114. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_eval_harness.py +0 -0
  115. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_evolve_apply_2.py +0 -0
  116. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_evolve_apply_3.py +0 -0
  117. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_extract_daemon.py +0 -0
  118. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_extract_dedup.py +0 -0
  119. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_forget.py +0 -0
  120. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_github_budget.py +0 -0
  121. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_github_safety.py +0 -0
  122. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_helpers.py +0 -0
  123. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_host_embed.py +0 -0
  124. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_host_env.py +0 -0
  125. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_host_main.py +0 -0
  126. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_host_wedged.py +0 -0
  127. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_hybrid_retrieval.py +0 -0
  128. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_identity.py +0 -0
  129. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_ingest_cursor.py +0 -0
  130. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_ingest_denylist.py +0 -0
  131. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_ingest_secret_redaction.py +0 -0
  132. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_ingest_status.py +0 -0
  133. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_ingest_transactions.py +0 -0
  134. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_lessons.py +0 -0
  135. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_memory_eval.py +0 -0
  136. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_memory_guard.py +0 -0
  137. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_memory_guard_host.py +0 -0
  138. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_missed_spawns.py +0 -0
  139. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_model_catalog.py +0 -0
  140. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_nudges.py +0 -0
  141. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_onnx_embeddings.py +0 -0
  142. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_permissions.py +0 -0
  143. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_process_health.py +0 -0
  144. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_retention.py +0 -0
  145. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_review_prompts.py +0 -0
  146. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_search_fts_punctuation.py +0 -0
  147. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_session_read_purity.py +0 -0
  148. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_setup.py +0 -0
  149. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_shadow_review.py +0 -0
  150. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_skill_hint.py +0 -0
  151. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_skill_passive_tier.py +0 -0
  152. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_skill_tier.py +0 -0
  153. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_skill_updater.py +0 -0
  154. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_skill_use_parser.py +0 -0
  155. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_spawn_config.py +0 -0
  156. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_spawn_hint.py +0 -0
  157. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_spawn_reap.py +0 -0
  158. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_spawn_slim.py +0 -0
  159. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_spawn_worktree_isolation.py +0 -0
  160. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_spawn_wrap.py +0 -0
  161. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_sync_capture.py +0 -0
  162. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_sync_identity.py +0 -0
  163. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_sync_migrate.py +0 -0
  164. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_sync_protocol.py +0 -0
  165. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_sync_reset_node.py +0 -0
  166. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_sync_transport.py +0 -0
  167. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_task_retention.py +0 -0
  168. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_task_spool.py +0 -0
  169. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_thin_session.py +0 -0
  170. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_threads.py +0 -0
  171. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_tools_smoke.py +0 -0
  172. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_validate_threads.py +0 -0
  173. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_vec_search.py +0 -0
  174. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/__init__.py +0 -0
  175. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/_setup.py +0 -0
  176. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/_spawn_wrap.py +0 -0
  177. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/adapters/__init__.py +0 -0
  178. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/adapters/_hook_helpers.py +0 -0
  179. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/adapters/claude_code.py +0 -0
  180. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/adapters/claude_desktop.py +0 -0
  181. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/adapters/copilot.py +0 -0
  182. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/adapters/vscode.py +0 -0
  183. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/assets/macos-agent-status/Info.plist +0 -0
  184. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/assets/macos-agent-status/README.md +0 -0
  185. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/assets/macos-agent-status/ThreadKeeperAgentStatus.swift +0 -0
  186. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/assets/macos-agent-status/build.sh +0 -0
  187. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/assets/macos-agent-status/install.sh +0 -0
  188. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/backup.py +0 -0
  189. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/brief.py +0 -0
  190. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/config_io.py +0 -0
  191. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/config_watcher.py +0 -0
  192. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/curator_snapshots.py +0 -0
  193. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/daemon_liveness.py +0 -0
  194. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/daemon_state.py +0 -0
  195. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/daemon_supervisor.py +0 -0
  196. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/dialectic_miner.py +0 -0
  197. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/dialectic_validator.py +0 -0
  198. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/egress.py +0 -0
  199. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/embeddings.py +0 -0
  200. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/eval/__init__.py +0 -0
  201. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/eval/__main__.py +0 -0
  202. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/eval/harness.py +0 -0
  203. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/extract_daemon.py +0 -0
  204. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/forget.py +0 -0
  205. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/github_budget.py +0 -0
  206. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/harvest.py +0 -0
  207. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/helpers.py +0 -0
  208. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/host_embed.py +0 -0
  209. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/identity.py +0 -0
  210. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/lessons.py +0 -0
  211. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/link_health.py +0 -0
  212. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/memory_guard.py +0 -0
  213. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/menubar_app.py +0 -0
  214. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/migrate_embeddings.py +0 -0
  215. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/model_catalog.py +0 -0
  216. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/nudges.py +0 -0
  217. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/permissions.py +0 -0
  218. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/process_health.py +0 -0
  219. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/retention.py +0 -0
  220. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/retrieval.py +0 -0
  221. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/review_prompts.py +0 -0
  222. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/search_proxy.py +0 -0
  223. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/skill_audit.py +0 -0
  224. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/skill_updater.py +0 -0
  225. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/spawn_config.py +0 -0
  226. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/sync/__init__.py +0 -0
  227. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/sync/capture.py +0 -0
  228. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/sync/daemon.py +0 -0
  229. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/sync/identity.py +0 -0
  230. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/sync/migrate.py +0 -0
  231. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/sync/protocol.py +0 -0
  232. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/sync/reset_node.py +0 -0
  233. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/sync/server.py +0 -0
  234. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/task_spool.py +0 -0
  235. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/__init__.py +0 -0
  236. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/agent_status.py +0 -0
  237. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/candidate_reviewer.py +0 -0
  238. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/concepts.py +0 -0
  239. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/config_watch.py +0 -0
  240. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/consolidate.py +0 -0
  241. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/core_memory.py +0 -0
  242. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/correlation.py +0 -0
  243. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/db_maintenance.py +0 -0
  244. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/dialectic_feed.py +0 -0
  245. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/dialog.py +0 -0
  246. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/distill.py +0 -0
  247. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/evolve_applier.py +0 -0
  248. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/evolve_research.py +0 -0
  249. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/extract.py +0 -0
  250. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/forget.py +0 -0
  251. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/graph.py +0 -0
  252. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/invariants.py +0 -0
  253. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/memory_guard.py +0 -0
  254. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/missed_spawns.py +0 -0
  255. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/peers.py +0 -0
  256. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/probes.py +0 -0
  257. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/prompts.py +0 -0
  258. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/session.py +0 -0
  259. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/shadow_review.py +0 -0
  260. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/style.py +0 -0
  261. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/sync.py +0 -0
  262. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/threads.py +0 -0
  263. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/validate.py +0 -0
  264. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/trash.py +0 -0
  265. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/wikilinks.py +0 -0
  266. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper.egg-info/dependency_links.txt +0 -0
  267. {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper.egg-info/entry_points.txt +0 -0
  268. {threadkeeper-0.17.7 → threadkeeper-0.18.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.17.7
3
+ Version: 0.18.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<2,>=1.10.0
25
+ Requires-Dist: mcp<3,>=1.10.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
@@ -108,6 +108,8 @@ Integrity API provenance from the expected GitHub Trusted Publisher. Dirty or
108
108
  diverged git checkouts are skipped rather than overwritten. Restarts are gated
109
109
  on install/setup success plus a subprocess import smoke check, so a broken or
110
110
  unverified update is recorded but the current server keeps running.
111
+ Interpreters without pip (uv-created or pipx venvs) install through
112
+ `uv pip install --python <interpreter>` when `uv` is available.
111
113
  Upstream PyPI publishing is intentionally gated: green merge-to-main builds are
112
114
  auto-tagged, but every upload pauses for a human approval on the protected
113
115
  `pypi` GitHub Environment (a maintainer-signed annotated `v*` tag remains the
@@ -193,17 +195,19 @@ thread-keeper-setup --dry-run
193
195
  | Claude Code | `~/.claude.json` `mcpServers` | `~/.claude/CLAUDE.md` | `~/.claude/settings.json` `hooks` | `~/.claude/projects/**/*.jsonl` |
194
196
  | Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` `mcpServers` (macOS); `%APPDATA%\Claude\…` (Win); `~/.config/Claude/…` (Linux) | none (GUI-only) | not supported by the app | none — chats live in Electron IndexedDB |
195
197
  | Codex (CLI + desktop) | `~/.codex/config.toml` `[mcp_servers]` (shared between CLI and `Codex.app`) | `~/.codex/AGENTS.md` | not supported | `~/.codex/sessions/**/rollout-*.jsonl` |
196
- | Antigravity CLI (`agy`) | `~/.gemini/config/mcp_config.json` `mcpServers` | `~/.gemini/config/AGENTS.md` | not wired yet | not yet parsed — sqlite/protobuf under `~/.gemini/antigravity-cli/conversations/*.db` |
198
+ | Antigravity CLI (`agy`) | `~/.gemini/config/mcp_config.json` `mcpServers` | `~/.gemini/config/AGENTS.md` | not wired yet | `~/.gemini/antigravity-cli/conversations/*.db` (sqlite/protobuf, read-only: user prompts and final answers) |
197
199
  | Copilot | `~/.copilot/mcp-config.json` `mcpServers` | `~/.copilot/copilot-instructions.md` | `~/.copilot/hooks.json` | `~/.copilot/session-store.db` (sqlite) |
198
200
  | VS Code | `~/Library/Application Support/Code/User/mcp.json` `servers` (macOS); `%APPDATA%\Code\User\mcp.json` (Win); `~/.config/Code/User/mcp.json` (Linux) | none (per-workspace only) | not supported | none — extensions own their history |
199
201
 
200
202
  Every CLI that produces parseable transcripts feeds the same
201
203
  `dialog_messages` table with a `source` tag, so `dialog_search()` finds
202
- matches regardless of where the conversation happened. Claude Desktop,
203
- Antigravity CLI, and the VS Code adapter are the exceptions — MCP registration
204
- only; their chats don't reach the table for now (Electron IndexedDB on the
205
- Claude Desktop side; sqlite/protobuf on the Antigravity side; per-extension
206
- stores on the VS Code side).
204
+ matches regardless of where the conversation happened. Claude Desktop and
205
+ the VS Code adapter are the exceptions — MCP registration only; their chats
206
+ don't reach the table for now (Electron IndexedDB on the Claude Desktop side;
207
+ per-extension stores on the VS Code side). Antigravity keeps one SQLite file
208
+ per conversation with protobuf step payloads; thread-keeper opens it read-only
209
+ (never creating WAL sidecars next to it) and keeps only user prompts and final
210
+ model answers, skipping tool calls and thinking.
207
211
 
208
212
  VS Code's user-level `mcp.json` is the central host that **every
209
213
  MCP-aware VS Code extension** consumes — GitHub Copilot Chat, the
@@ -213,6 +217,14 @@ Cline, … — so a single registration there reaches all of them at once.
213
217
  Adding a new CLI = one file under `threadkeeper/adapters/` implementing
214
218
  the `CLIAdapter` contract. See [CONTRIBUTING.md](CONTRIBUTING.md).
215
219
 
220
+ ### Python MCP SDK compatibility
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.
227
+
216
228
  ### MCP primitives (tools, resources, prompts, elicitation)
217
229
 
218
230
  MCP has three server primitives. thread-keeper uses all three, mapped to the
@@ -309,9 +321,10 @@ task row so dead children stop counting against the cap. Admission control
309
321
  refuses a new spawn that would exceed `THREADKEEPER_SPAWN_BUDGET_MB`
310
322
  (3 GB default). Slim children that need semantic search delegate to the parent
311
323
  via `search_via_parent` — no per-child copy of the embedding model. Admission
312
- uses a SQLite `BEGIN IMMEDIATE` reservation: `spawn()` re-checks the budget and
313
- inserts the child task row with its RSS estimate before `Popen`, so two
314
- concurrent spawns cannot both squeeze through the cap.
324
+ uses a short SQLite `BEGIN IMMEDIATE` reservation: `spawn()` re-checks the
325
+ budget and commits the child task row with its RSS estimate before `Popen`, so
326
+ two concurrent spawns cannot both squeeze through the cap, and no write lock is
327
+ held while the child launches.
315
328
 
316
329
  When `cwd` is inside a Git checkout, `spawn()` also requires the source
317
330
  checkout's tracked files to be clean, then starts the child from a unique
@@ -319,6 +332,15 @@ branch/worktree under `THREADKEEPER_TASK_LOG_DIR/worktrees/`. Parallel children
319
332
  therefore never share a mutable checkout or Git index. Non-Git directories keep
320
333
  their existing behavior; a dirty Git checkout is refused before a child starts.
321
334
 
335
+ Learning-loop children that name no `cwd` (Curator, shadow review, candidate
336
+ and dialectic review, probes, panels, the archivist) start in the owner-only
337
+ `<db dir>/workspace` directory instead of the spawning process's working
338
+ directory. The daemon host keeps the directory of whichever session started it,
339
+ so these children used to run inside an unrelated user project, and a dirty Git
340
+ checkout there refused every loop spawn. Foreground spawns keep the caller's
341
+ directory, and callers that pass `cwd` (the Evolve reviewer and applier) are
342
+ unchanged.
343
+
322
344
  The spawn wrapper also records each completed child's `duration_s`,
323
345
  `tokens_in`, `tokens_out`, `tokens_total`, and `cost_usd` when the underlying
324
346
  CLI emits a recognizable usage trailer. Optional daily ceilings
@@ -535,7 +557,7 @@ shows agents focused on their primary task rarely do).
535
557
  | 2 | shadow_review daemon | every 15 min (env knob) | recent `dialog_messages` window | SKILL.md, lessons.md |
536
558
  | 3 | extract daemon | every 10 min (env knob) | recent `dialog_messages` window | `extract_candidates` pending queue |
537
559
  | 4 | candidate-reviewer daemon | every 1 h (env knob) | pending candidates queue | SKILL.md (create/patch) / notes / verbatim / reject |
538
- | 5 | Curator daemon | every 7 days (env knob) | every existing lesson + recently-touched skill | `REPORT-<date>.md`; Evolve applier applies it after roadmap issues |
560
+ | 5 | Curator daemon | every 7 days (env knob) | every existing lesson + recently-touched skill | pass-scoped research handoffs and `REPORT-<date>.md`; Evolve applier applies reports after roadmap issues |
539
561
  | 6 | evolve_reviewer daemon | configurable (env knob; 0=off) | code/docs/issues; web research in a separate read-only phase (#79) | roadmap updates + GitHub issues |
540
562
  | 7 | evolve_applier daemon | configurable (env knob; 0=off) | open GitHub issues, Curator reports, legacy promoted evolve suggestions | PRs + applied markers |
541
563
  | 8 | dialectic_miner daemon | configurable (env knob; 0=off) | recent `dialog_messages` — user replies + preceding-assistant context | `dialectic_observations` buffer |
@@ -626,6 +648,15 @@ slugs (semantic, with a lexical fallback). Shadow and candidate reviewers use
626
648
  that preflight to patch/consolidate an incumbent or add a `[[slug]]` cross-link
627
649
  to a related, distinct lesson while its body is still editable.
628
650
 
651
+ When the dialog shows a rule being broken again although a lesson already
652
+ covers it, the reviewers call `lesson_violation(slug, evidence)` instead of
653
+ writing a duplicate. One conversation counts once per lesson per day; at
654
+ `THREADKEEPER_LESSON_VIOLATION_THRESHOLD` (3) violations inside
655
+ `THREADKEEPER_LESSON_VIOLATION_WINDOW_DAYS` (90) the lesson is
656
+ memory-insufficient: `mp_dashboard` lists it and the Curator inventory marks it
657
+ `[MEMORY-INSUFFICIENT]` and recommends escalating the rule to a PreToolUse-style
658
+ hook or other hard guard.
659
+
629
660
  #### 3. Extract daemon
630
661
 
631
662
  Every `THREADKEEPER_EXTRACT_INTERVAL_S` seconds (default off, 600 =
@@ -709,10 +740,12 @@ flags a dense lesson subtopic when at least
709
740
  `THREADKEEPER_CURATOR_PROMOTION_MIN_LESSONS` lessons (default 3) share a pair
710
741
  of meaningful title terms. A non-protected candidate must become one validated,
711
742
  checklist-style canonical skill before its source lessons are retired; protected
712
- clusters are left for human review. The child reads every
743
+ clusters are left for human review. A read-only research child reads every
713
744
  complete skill and relevant support file, performs current web research against
714
- official docs and comparable
715
- public skills, then writes numbered per-skill verdicts to
745
+ official docs and comparable public skills, then writes a bounded
746
+ `RESEARCH-<pass>-batch-NNN-of-MMM.json` handoff through a destination-scoped
747
+ tool. A separate web-free evaluator receives that handoff as fenced, untrusted
748
+ data and writes numbered per-skill verdicts to
716
749
  `~/.threadkeeper/curator/REPORT-<isodate>.md` for a one-batch pass or
717
750
  `REPORT-<isodate>-batch-NNN-of-MMM.md` for a multi-batch pass: KEEP / REPAIR /
718
751
  UPDATE / MERGE / SPLIT / DEPRECATE / DELETE / CROSS_LINK / HUMAN_REVIEW.
@@ -744,25 +777,59 @@ have increased its patch counter. Patches are maintenance activity, not proof
744
777
  that a foreground user or agent consulted the skill.
745
778
 
746
779
  Before spawning, the scheduler hashes lessons, concepts, skill bodies, support
747
- trees, validators, and mirror state. Repeated manual calls over identical bytes
748
- return `unchanged_inventory`; the scheduled three-day pass still runs because
749
- CLI behavior, official guidance, and external alternatives can change without
750
- local file changes. `curator_review_status()` shows the inventory hash plus the
751
- latest report, deterministic audit manifest, recovery snapshot, last endorsed
752
- `inventory_sha256`, and the current inventory hash. Spawned pass events record
753
- `entries`, `batches`, `batch_entries`, and `max_batch_chars`, making partial or
754
- large reviews visible in the normal `curator_pass` trail.
755
-
756
- Each report path is explicitly authorized in a parent-authored `curator_pass`
757
- event before its child is launched. `curator_report_write` only accepts that
758
- exact path from the spawned Curator carrying the matching pass ID, then records
759
- the persisted report's SHA-256 in `curator_report_provenance`. This makes the
760
- report directory an untrusted transport: a stray or forged `REPORT-*.md` file
761
- cannot acquire the provenance needed by the applier.
762
-
763
- Curator applies its own PATCH / PRUNE / CONSOLIDATE directly by default (it
764
- writes the REPORT first, then mutates — `lesson_remove` is in its toolset so it
765
- can actually prune and consolidate duplicate lessons). Set
780
+ trees, validators, and mirror state. It then persists a pass manifest with the
781
+ expected reports and the rendered text of every batch before launching any
782
+ child. A dispatch is not completion: every batch must exit successfully and
783
+ have a final, matching provenance record before the inventory is endorsed.
784
+ Repeated manual calls over identical bytes return `unchanged_inventory` only
785
+ after that endorsement; scheduled passes still run because CLI behavior,
786
+ official guidance, and external alternatives can change without local file
787
+ changes. Every required inventory source (lessons, skill telemetry, skill
788
+ files, and concepts) must read successfully before a pass is created; a
789
+ successfully empty source remains valid, while a failed one records
790
+ `inventory_error source=<source> error=<type>` without authorizing a report,
791
+ creating a recovery snapshot, or launching a child.
792
+
793
+ A pass keeps reviewing the batches it froze even when the live inventory
794
+ changes, and resuming it never re-reads the inventory. Failed and timed-out
795
+ batches are retried without redispatching completed work, up to three launched
796
+ attempts per batch; a spawn memory or spend-budget refusal leaves the batch
797
+ waiting without using an attempt (a spend-cap refusal also alerts). A pass
798
+ whose batch exhausts its attempts, or that outlives two Curator intervals
799
+ (at least six hours), is abandoned so the next due tick starts fresh.
800
+ `THREADKEEPER_CURATOR_MAX_CONCURRENT_BATCHES` (default `1`) bounds one pass's
801
+ live children while normal spawn admission still enforces the shared RSS
802
+ budget. While a pass is active, the daemon polls it every
803
+ `THREADKEEPER_CURATOR_BATCH_POLL_S` seconds (default `60`) instead of waiting
804
+ for the next full Curator interval. `curator_review_status()` and the Curator
805
+ row in `agent_status` expose expected, running, failed, complete, and
806
+ unapplied batch counts alongside the inventory hash, reports, audit manifest,
807
+ and recovery snapshot.
808
+
809
+ Each research handoff path is explicitly authorized for one pass, inventory
810
+ fingerprint, manifest digest, and batch before its web-enabled child is
811
+ launched. `curator_research_write` accepts only that destination from a spawned
812
+ `curator_researcher` child carrying the matching pass ID, and records the final
813
+ SHA-256 as provenance. Research and evaluation are two phases of each batch
814
+ inside the durable pass: the web-free evaluator for a batch starts only after
815
+ that batch's handoff validates. A researcher that fails or leaves a missing,
816
+ malformed, swapped, or mismatched handoff is retried (three attempts); after
817
+ that the batch gets a non-mutating evaluator that records `HUMAN_REVIEW` for
818
+ decisions that depend on current external facts. A destructive pass takes its
819
+ recovery snapshot right before its first mutating evaluator.
820
+ `THREADKEEPER_CURATOR_WEB_RESEARCH=0` skips the research phase entirely (and
821
+ its token cost); the evaluator then judges currency from local evidence only.
822
+ Each report path is then
823
+ authorized in the existing parent-authored `curator_pass` event;
824
+ `curator_report_write` records its SHA-256 in `curator_report_provenance`.
825
+ This makes the report directory an untrusted transport: a stray or forged
826
+ `REPORT-*.md` file cannot acquire the provenance needed by the applier.
827
+
828
+ The web-enabled researcher never receives lesson, skill, or concept mutation
829
+ tools. The evaluator never receives web tools. Curator applies its own PATCH /
830
+ PRUNE / CONSOLIDATE directly by default (the evaluator writes the REPORT first,
831
+ then mutates — `lesson_remove` is in its toolset so it can actually prune and
832
+ consolidate duplicate lessons). Set
766
833
  `THREADKEEPER_CURATOR_DESTRUCTIVE=0` for advisory REPORT-only. Pinned and
767
834
  untracked skills remain protected. Foreground-authored skills are protected by
768
835
  default; set `THREADKEEPER_CURATOR_MANAGE_FOREGROUND_SKILLS=1` to grant the
@@ -803,11 +870,13 @@ or `skill_manage(action='restore', name=...)`. Trash retention is bounded by
803
870
  `THREADKEEPER_CURATOR_TRASH_TTL_DAYS` (30 days by default) and swept on new
804
871
  trash writes. Advisory mode does not write snapshots. The existing Evolve
805
872
  applier is
806
- also the Curator apply worker: after the roadmap issue queue is empty, it looks
807
- for the latest complete Curator report (`CURATOR_PASS_COMPLETE`) whose path and
808
- current SHA-256 match an unapplied `curator_report_provenance` event, then
809
- spawns an `evolve_applier` child to apply only safe, still-current memory
810
- maintenance through `lesson_append` / `lesson_patch` / `lesson_remove` / `skill_manage` /
873
+ also the Curator apply worker: after the roadmap issue queue is empty, it
874
+ enumerates every complete, unapplied report in the oldest endorsed Curator pass
875
+ (`CURATOR_PASS_COMPLETE`) whose path and current SHA-256 match an unapplied
876
+ `curator_report_provenance` event, then spawns an `evolve_applier` child to
877
+ apply only safe, still-current memory
878
+ maintenance through `lesson_append` / `lesson_patch` / `lesson_remove` /
879
+ `skill_manage` /
811
880
  `concept_manage`. It never touches `[PROTECTED]`,
812
881
  foreground/user, pinned, or validated entries. Only after the child finishes
813
882
  does it call `evolve_mark_curator_report_applied(...)` with the verified hash;
@@ -889,8 +958,18 @@ shell/`bypassPermissions` to the same child:
889
958
 
890
959
  A full research → audit cycle therefore spans two due passes.
891
960
 
892
- Before a privileged audit can create more issues, the parent counts open,
893
- not-yet-applied roadmap work with a paginated GitHub REST read. At
961
+ Privileged reviewer and applier launches use private server-owned launchers.
962
+ The public `spawn()` tool refuses `permission_mode="bypassPermissions"` even if
963
+ a caller supplies Evolve-looking role or provenance metadata; only the explicit
964
+ `THREADKEEPER_ALLOW_BYPASS_PERMISSIONS_SPAWN=1` operator override opens that
965
+ mode for public calls.
966
+
967
+ Before a privileged audit can create more issues, the parent counts only open,
968
+ not-yet-applied issues carrying the deliberate `roadmap` label with a paginated
969
+ GitHub REST read. Issues without that label do not consume the reviewer cap.
970
+ Roadmap issues with an applier skip label or an untrusted author still count:
971
+ those gates stop autonomous pickup, not the reviewer's view of intended roadmap
972
+ work. At
894
973
  `THREADKEEPER_EVOLVE_REVIEW_BACKLOG_MAX` (default 25), it withholds that audit
895
974
  and records `backlog_saturated open=<n> cap=<max>` on the
896
975
  `evolve_review_pass` event; set the knob to `0` to opt out. The read-only
@@ -1014,19 +1093,24 @@ which bypasses the gate as explicit promotion. This removes the untrusted input
1014
1093
  at the boundary and complements the in-prompt data-fencing of #22/#76. The
1015
1094
  public claim comment also carries only an opaque per-host token (a 6-char hash
1016
1095
  of the hostname), never the raw hostname/PID/git-rev; the full host identity is
1017
- recorded in the local event log for multi-host triage.
1018
-
1019
- **Privilege + public-body guard (#22).** Stored evolve suggestions and external
1096
+ recorded in the local event log for multi-host triage. A visible claim marker is
1097
+ also not a bearer token: the claim lock accepts it only when the comment's
1098
+ GitHub `author_association` is in that trusted association set, or when its
1099
+ author login is listed in `THREADKEEPER_EVOLVE_CLAIM_AUTOMATION_ACTORS`. Missing
1100
+ author metadata and untrusted marker comments remain visible on GitHub but do
1101
+ not block roadmap work or affect claim-race resolution.
1102
+
1103
+ **Privilege + public-body guard.** Stored evolve suggestions and external
1020
1104
  GitHub issue bodies are wrapped in explicit data fences before a privileged
1021
- child sees them. The exposed `spawn()` tool refuses
1022
- `permission_mode="bypassPermissions"` unless the request comes from the evolve
1023
- daemon role/write-origin pairs (`evolve_reviewer`/`evolve`,
1024
- `evolve_applier`/`evolve_apply`) or the operator explicitly opts in with
1025
- `THREADKEEPER_ALLOW_BYPASS_PERMISSIONS_SPAWN=1`. Privileged evolve children also
1026
- get a PATH-prepended `gh` wrapper that scrubs `gh issue create`, `gh issue
1027
- comment`, and `gh pr create` bodies before the real GitHub CLI sees them:
1028
- home-directory paths and common token shapes are redacted, and a body is
1029
- refused if a known unsafe pattern remains.
1105
+ child sees them. The public `spawn()` tool refuses
1106
+ `permission_mode="bypassPermissions"` regardless of caller-supplied role or
1107
+ provenance metadata; privileged Evolve launches use private server-owned
1108
+ capabilities, and public calls need the explicit
1109
+ `THREADKEEPER_ALLOW_BYPASS_PERMISSIONS_SPAWN=1` operator override. Privileged
1110
+ Evolve children also get a PATH-prepended `gh` wrapper that scrubs `gh issue
1111
+ create`, `gh issue comment`, and `gh pr create` bodies before the real GitHub
1112
+ CLI sees them: home-directory paths and common token shapes are redacted, and a
1113
+ body is refused if a known unsafe pattern remains.
1030
1114
 
1031
1115
  Fallback/manual paths remain:
1032
1116
 
@@ -1090,7 +1174,9 @@ Three detection sources per tick:
1090
1174
 
1091
1175
  1. **Admission failures / terminal timeouts** — a `<loop>_pass` event whose
1092
1176
  summary is a spawn/budget failure (e.g. `token_budget_exceeded`,
1093
- `claude_cli_not_found`), plus `spawn_timeout_retry_failed`.
1177
+ `claude_cli_not_found`), plus `spawn_timeout_retry_failed`. A budget
1178
+ refusal names the local cap that refused the child — the spawn memory
1179
+ budget or the daily token/cost budget — not the CLI subscription.
1094
1180
  2. **Dead children** — a `tasks` row that ended with a non-zero, non-timeout
1095
1181
  return code. This is the important one: `spawn()` returns `ok task=…` at
1096
1182
  *launch*, so a `*_pass` summary is a false success when a child later dies
@@ -1140,7 +1226,11 @@ category off silences the banner without hiding the history. Each materializatio
1140
1226
  uses its own skill or lesson toggle; generic task reports are history-only.
1141
1227
  Foreground writes appear too, without waiting for a background child to finish.
1142
1228
  A pathless skill mark only silences its reminder and has no materialization
1143
- banner. Enabling a toggle never replays backlog.
1229
+ banner. Enabling a toggle never replays backlog. Child-log excerpts and spawn
1230
+ error reasons in both lists are bounded sanitized summaries: credential-shaped
1231
+ values and private home paths are redacted before they reach the MCP status
1232
+ response, menu, or notification; raw logs remain available only through
1233
+ `task_logs`.
1144
1234
 
1145
1235
  **Settings in the app.** The menu-bar app's **Settings ▸ Notifications** tab
1146
1236
  edits these same `THREADKEEPER_NOTIFY_*` keys visually — switches for the
@@ -1197,9 +1287,9 @@ claim earned trust. Thresholds:
1197
1287
 
1198
1288
  All multilingual regex and prompt fragments live in
1199
1289
  `threadkeeper/i18n.py` — the rest of the codebase stays English-only.
1200
- Currently ships ten locales: **English, Mandarin Chinese, Hindi,
1201
- Spanish, Portuguese, French, German, Arabic, Russian, Japanese**
1202
- (~82 % of the world's speakers).
1290
+ Currently ships twelve locales: **English, Mandarin Chinese, Hindi,
1291
+ Spanish, Portuguese (Brazilian and European), French, German, Arabic,
1292
+ Russian, Japanese, Korean, Bengali**.
1203
1293
 
1204
1294
  Adding a new language is a two-file PR — see [CONTRIBUTING.md](CONTRIBUTING.md).
1205
1295
 
@@ -1248,10 +1338,14 @@ The most-used env knobs (full list in `threadkeeper/config.py`):
1248
1338
  | `THREADKEEPER_CANDIDATE_REVIEW_INTERVAL_S` | 0 (off) | candidate-reviewer daemon tick (s), restart-throttled by the last `candidate_review_pass`; 3600 = 1h recommended |
1249
1339
  | `THREADKEEPER_CANDIDATE_REVIEW_MIN` | 3 | min pending candidates before reviewer engages |
1250
1340
  | `THREADKEEPER_CANDIDATE_REVIEW_FLUSH_AGE_S` | 259200 | age-flush: an undersized pending queue is still reviewed once its oldest candidate is this old (0 = threshold only) |
1341
+ | `THREADKEEPER_LESSON_VIOLATION_THRESHOLD` | 3 | violations of one lesson inside the window that make it memory-insufficient (recommend a hook) |
1342
+ | `THREADKEEPER_LESSON_VIOLATION_WINDOW_DAYS` | 90 | window for counting `lesson_violation` records |
1343
+ | `THREADKEEPER_CANDIDATE_REVIEW_BATCH_SIZE` | 40 | most pending candidates (oldest first) one reviewer child receives; the rest stay pending for the next pass |
1251
1344
  | `THREADKEEPER_LEARNING_LOOP_SKILL_CREATE_LIMIT` | 2 | max new skills one autonomous learning-loop child (`candidate_review`, `shadow_review`, or `background_review`) may create in its session; foreground creation is unaffected |
1252
1345
  | `THREADKEEPER_CURATOR_INTERVAL_S` | 259200 | deep curator audit every three days; set `0` to disable |
1253
1346
  | `THREADKEEPER_CURATOR_MANAGE_FOREGROUND_SKILLS` | 0 | allow snapshot-scoped Curator repair/merge/delete of foreground skills; pinned/untracked skills remain protected |
1254
1347
  | `THREADKEEPER_CURATOR_MIN_LESSONS` | 3 | min lessons before curator engages |
1348
+ | `THREADKEEPER_CURATOR_WEB_RESEARCH` | `1` (on) | run a read-only web research child per batch before the web-free evaluator; `0` skips research (the evaluator uses local evidence only) |
1255
1349
  | `THREADKEEPER_CURATOR_DESTRUCTIVE` | `1` (on) | curator child writes its REPORT then applies its own PATCH/PRUNE/CONSOLIDATE directly (incl. `lesson_remove` for prune/consolidate); set `0` for advisory REPORT-only. `[PROTECTED]` entries are refused server-side |
1256
1350
  | `THREADKEEPER_CURATOR_MAX_DESTRUCTIVE_PER_PASS` | 10 | shared server-side ceiling for one destructive Curator pass across `lesson_remove` and `skill_manage(action='delete')`; `0` disables autonomous deletes and foreground deletes bypass it |
1257
1351
  | `THREADKEEPER_CURATOR_SNAPSHOT_RETENTION` | 10 | number of destructive curator pre-mutation snapshots to retain under `<reports_dir>/snapshots`; current pass is always retained |
@@ -1259,7 +1353,7 @@ The most-used env knobs (full list in `threadkeeper/config.py`):
1259
1353
  | `THREADKEEPER_PROBE_INTERVAL_S` | 0 (off) | probe daemon tick (s); 1800 = 30 min recommended so finished probe answers are graded promptly |
1260
1354
  | `THREADKEEPER_PROBE_COOLDOWN_S` | 604800 | per-category probe cooldown; 86400 = 1d recommended for active reliability tracking |
1261
1355
  | `THREADKEEPER_SPAWN_BUDGET_MB` | 3072 | combined child RSS cap (MB); 0 disables |
1262
- | `THREADKEEPER_ALLOW_BYPASS_PERMISSIONS_SPAWN` | "" (off) | explicit override that lets ordinary `spawn()` calls request `permission_mode="bypassPermissions"`; default off means only evolve daemon role/write-origin pairs can use the dangerous mode |
1356
+ | `THREADKEEPER_ALLOW_BYPASS_PERMISSIONS_SPAWN` | "" (off) | explicit operator override that lets public `spawn()` calls request `permission_mode="bypassPermissions"`; otherwise only private server-owned Evolve launchers can use it |
1263
1357
  | `THREADKEEPER_SPAWN_TOKEN_BUDGET` | 0 | recorded 24h spawned-child token ceiling; 0 disables |
1264
1358
  | `THREADKEEPER_SPAWN_COST_BUDGET_USD` | 0 | recorded 24h spawned-child dollar ceiling; 0 disables |
1265
1359
  | `THREADKEEPER_SPAWN_MAX_RUNTIME_S` | 3600 | wall-clock lifetime cap (s) for a spawned child; over-cap live children are SIGTERM→SIGKILL'd and closed with `return_code` 124; 0 disables |
@@ -1296,7 +1390,7 @@ The most-used env knobs (full list in `threadkeeper/config.py`):
1296
1390
  | `THREADKEEPER_DIALECTIC_VALIDATE_FLUSH_AGE_S` | 259200 | age-flush: an undersized observation buffer is still validated once its oldest eligible row is this old (0 = threshold only) |
1297
1391
  | `THREADKEEPER_DIALECTIC_VALIDATE_BATCH_SIZE` | 50 | max observations sent to one validator child; prevents oversized prompts and drains large queues incrementally |
1298
1392
  | `THREADKEEPER_EVOLVE_REVIEW_INTERVAL_S` | 0 (off) | evolve-reviewer daemon tick (s); audits thread-keeper for safety/leaks/optimization/new ideas, updates roadmap/issues, and includes legacy evolve suggestions as input. Runs as two alternating phases — read-only web research, then a privileged web-free audit that consumes the fenced research digest (#79) — so a full cycle spans two ticks |
1299
- | `THREADKEEPER_EVOLVE_REVIEW_BACKLOG_MAX` | 25 | max open, not-yet-applied roadmap issues before the issue-creating audit is skipped and records `backlog_saturated`; `0` disables the cap |
1393
+ | `THREADKEEPER_EVOLVE_REVIEW_BACKLOG_MAX` | 25 | max open, not-yet-applied `roadmap`-label issues before the issue-creating audit is skipped and records `backlog_saturated`; skip-labelled and untrusted-author roadmap issues still count; `0` disables the cap |
1300
1394
  | `THREADKEEPER_EVOLVE_APPLY_INTERVAL_S` | 0 (off) | evolve-applier daemon tick (s); implements one open GitHub issue at a time, then falls back to Curator reports and promoted legacy evolve suggestions. Empty checks are throttled between intervals; actionable work and manual apply tools still dispatch |
1301
1395
  | `THREADKEEPER_EVOLVE_REPO_ROOT` | (auto) | absolute path to the thread-keeper git checkout the evolve reviewer/applier branch, test, and open PRs against. When empty, the repo is resolved automatically: the package's parent dir for an editable `install.sh`, else a managed checkout under the DB dir that is auto-cloned on first use. Set this to pin an explicit checkout |
1302
1396
  | `THREADKEEPER_EVOLVE_AUTO_CLONE` | true | auto-provision a managed checkout that runs remote `pip install -e` and tests; set `0`/`false` on shared or multi-user hosts unless that remote-code-execution boundary is explicitly accepted |
@@ -1307,6 +1401,7 @@ The most-used env knobs (full list in `threadkeeper/config.py`):
1307
1401
  | `THREADKEEPER_EVOLVE_REPO_PROVISION_LOCK_TIMEOUT_S` | 5 | maximum seconds to wait for another clone/venv provisioning operation before returning `ERR evolve_repo_provisioning_in_progress retry_later=1`; `0` is immediate |
1308
1402
  | `THREADKEEPER_EVOLVE_APPLY_SKIP_LABELS` | `blocked,needs-design,wontfix,question,discussion,help wanted` | comma-separated labels that exclude GitHub issues from autonomous Evolve applier pickup. Exact-number apply returns `skipped: label X`; set to `off` to clear |
1309
1403
  | `THREADKEEPER_EVOLVE_TRUSTED_AUTHOR_ASSOCIATIONS` | `OWNER,MEMBER,COLLABORATOR` | comma-separated GitHub author associations eligible for **autonomous** issue pickup on this public repo; issues from other authors are skipped unless promoted (trust label or exact-number invocation) |
1404
+ | `THREADKEEPER_EVOLVE_CLAIM_AUTOMATION_ACTORS` | (empty) | comma-separated GitHub logins allowed to own visible Evolve claim comments without a trusted repository association; all other marker comments are advisory text only |
1310
1405
  | `THREADKEEPER_EVOLVE_TRUST_LABELS` | (empty) | comma-separated labels that promote an untrusted-author issue into the autonomous queue; on a public repo only collaborators can apply labels, so a trust label is a maintainer endorsement |
1311
1406
  | `THREADKEEPER_ROADMAP_ISSUE_MAX_ATTEMPTS` | 3 | poison-issue dead-letter cap: after this many implementer spawns for a roadmap issue with no resulting PR, the issue gets a `blocked` label + one summary comment and is excluded from the auto-drain until a human intervenes. A manual `evolve_apply_roadmap_issue(issue_number=N)` bypasses the cap, but the default skip-label gate still refuses the `blocked` label until it is removed or reconfigured |
1312
1407
  | `THREADKEEPER_ROADMAP_ISSUE_BACKOFF_BASE_S` | 172800 (2d) | base failure-backoff window for a roadmap issue; doubles per attempt (`base * 2^(attempts-1)`, capped at 30d). Defers re-selection of a repeatedly-aborting issue beyond the fixed 24h claim TTL |
@@ -1491,8 +1586,10 @@ hardening, WAL/schema migration, and vec-table setup once per process.
1491
1586
  `PRAGMA query_only=ON`, so retrieval cannot accidentally migrate, heartbeat,
1492
1587
  or write. `run_write()` opens a fresh connection, acquires `BEGIN IMMEDIATE`,
1493
1588
  runs a DB-only callback, and closes it; only `SQLITE_BUSY`/`SQLITE_LOCKED` are
1494
- retried with bounded jitter. `get_db()` remains a compatibility API for older
1495
- low-level call sites.
1589
+ retried with bounded jitter, including a lock hit while the connection is being
1590
+ set up. `get_db()` remains a compatibility API for older low-level call sites;
1591
+ its connections are tracked, and the daemon host logs the call site of any
1592
+ that keeps a write transaction open for a minute or more.
1496
1593
 
1497
1594
  Schema migration uses SQLite `PRAGMA user_version`: a current database skips
1498
1595
  legacy `ALTER TABLE` work, while an old or fresh v0 database migrates once
@@ -1827,8 +1924,8 @@ pip install -e '.[semantic,dev]'
1827
1924
  python -m pytest
1828
1925
  ```
1829
1926
 
1830
- 869 tests passing on Python 3.11 / 3.12 / 3.13 (1 skipped). CI runs
1831
- the suite and a resolved-dependency `pip-audit` gate on every push and PR.
1927
+ The suite runs on Python 3.11 / 3.12 / 3.13. CI runs it, together with a
1928
+ resolved-dependency `pip-audit` gate, on every push and PR.
1832
1929
  CodeQL scans the Python source on those changes and weekly; see
1833
1930
  [SECURITY.md](SECURITY.md#continuous-security-scanning) for the exception
1834
1931
  process and reporting policy.
@@ -1840,7 +1937,7 @@ process and reporting policy.
1840
1937
  ```
1841
1938
  threadkeeper/
1842
1939
  ├── server.py # MCP entry: python -m threadkeeper.server
1843
- ├── _mcp.py # FastMCP singleton + read_tool()/write_tool() annotation wrappers
1940
+ ├── _mcp.py # MCPServer singleton + read_tool()/write_tool() annotation wrappers
1844
1941
  ├── tool_schemas.py # typed outputSchema models for the structured status tools
1845
1942
  ├── _setup.py # `thread-keeper-setup` installer
1846
1943
  ├── config.py # env-driven defaults
@@ -1851,7 +1948,7 @@ threadkeeper/
1851
1948
  ├── eval/ # offline learning-loop decision-quality harness (python -m threadkeeper.eval)
1852
1949
  ├── brief.py # render_brief / render_context
1853
1950
  ├── shadow_review.py # autonomous learning observer
1854
- ├── i18n.py # 10 locales of regex + prompt bundles
1951
+ ├── i18n.py # 12 locales of regex + prompt bundles
1855
1952
  ├── adapters/ # one file per supported CLI
1856
1953
  │ ├── claude_code.py
1857
1954
  │ ├── claude_desktop.py
@@ -1859,7 +1956,7 @@ threadkeeper/
1859
1956
  │ ├── antigravity.py
1860
1957
  │ ├── copilot.py
1861
1958
  │ └── vscode.py
1862
- └── tools/ # @read_tool()/@write_tool() entries — 120 of them
1959
+ └── tools/ # registered @read_tool()/@write_tool() entries
1863
1960
  ├── threads.py
1864
1961
  ├── peers.py
1865
1962
  ├── spawn.py
@@ -1871,14 +1968,14 @@ threadkeeper/
1871
1968
 
1872
1969
  **Tool annotation contract (#67).** Every tool registers through
1873
1970
  `@read_tool()` or `@write_tool(destructive=…, idempotent=…)` (in `_mcp.py`),
1874
- so `tools/list` carries MCP 2025-06-18 `ToolAnnotations` for all 113 tools:
1875
- `readOnlyHint=True` for pure reads (`brief`, `context`, `search`,
1971
+ so `tools/list` carries MCP 2025-06-18 `ToolAnnotations` for every
1972
+ registered tool: `readOnlyHint=True` for pure reads (`brief`, `context`, `search`,
1876
1973
  `dialog_search`, the status tools, …) and `readOnlyHint=False`
1877
1974
  for mutations. `lesson_list` / `lesson_get` are classified as non-destructive
1878
- writes because they bump lesson access counters. The ten delete/overwrite/kill
1879
- tools carry `destructiveHint=True` (`compost` is read-only — it only surfaces
1880
- idle threads). A confirmation/elicitation host reads this to decide which calls
1881
- warrant a prompt. The five status tools (`context`, `spawn_budget_status`,
1975
+ writes because they bump lesson access counters. Delete/overwrite/kill tools
1976
+ carry `destructiveHint=True` (`compost` is read-only — it only surfaces idle
1977
+ threads). A confirmation/elicitation host reads this to decide which calls
1978
+ warrant a prompt. The status tools (`context`, `spawn_budget_status`,
1882
1979
  `spawn_status`, `mp_health`, `agent_status`) additionally advertise an
1883
1980
  `outputSchema` and return `structuredContent` alongside the legacy text
1884
1981
  block. The contract is enforced by `tests/test_tool_annotations.py`.