threadkeeper 0.20.0__tar.gz → 0.21.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 (312) hide show
  1. {threadkeeper-0.20.0/threadkeeper.egg-info → threadkeeper-0.21.0}/PKG-INFO +39 -3
  2. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/README.md +38 -2
  3. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/pyproject.toml +1 -1
  4. threadkeeper-0.21.0/tests/test_brief_hook_size.py +69 -0
  5. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_brief_sections.py +43 -0
  6. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_curator.py +164 -90
  7. threadkeeper-0.21.0/tests/test_dialectic_drift_eval.py +97 -0
  8. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_evolve_applier.py +13 -1
  9. threadkeeper-0.21.0/tests/test_evolve_apply_48.py +92 -0
  10. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_forget.py +66 -0
  11. threadkeeper-0.21.0/tests/test_lesson_index.py +76 -0
  12. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_memory_eval.py +33 -0
  13. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_spawn_slim.py +104 -0
  14. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_spawn_watchdog.py +59 -10
  15. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/_setup.py +10 -0
  16. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/authority.py +4 -2
  17. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/brief.py +53 -1
  18. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/curator.py +295 -149
  19. threadkeeper-0.21.0/threadkeeper/dialectic_drift_eval.py +321 -0
  20. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/dialectic_validator.py +5 -0
  21. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/forget.py +91 -1
  22. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/instructions.py +5 -2
  23. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/lessons.py +49 -0
  24. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/spawn_budget.py +38 -45
  25. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/curator.py +51 -18
  26. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/spawn.py +62 -2
  27. {threadkeeper-0.20.0 → threadkeeper-0.21.0/threadkeeper.egg-info}/PKG-INFO +39 -3
  28. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper.egg-info/SOURCES.txt +5 -0
  29. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/LICENSE +0 -0
  30. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/setup.cfg +0 -0
  31. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_adapters.py +0 -0
  32. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_agent_status.py +0 -0
  33. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_antigravity_ingest.py +0 -0
  34. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_auto_update.py +0 -0
  35. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_backup.py +0 -0
  36. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_brief_footprint.py +0 -0
  37. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_candidate_reviewer.py +0 -0
  38. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_ci_parallel.py +0 -0
  39. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_concepts.py +0 -0
  40. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_config_settings.py +0 -0
  41. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_config_watcher.py +0 -0
  42. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_core_memory.py +0 -0
  43. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_daemon_host_config.py +0 -0
  44. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_daemon_host_integration.py +0 -0
  45. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_daemon_liveness.py +0 -0
  46. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_daemon_state.py +0 -0
  47. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_dashboard.py +0 -0
  48. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_dashboard_app.py +0 -0
  49. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_db_compact.py +0 -0
  50. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_db_contention.py +0 -0
  51. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_db_leak_guard.py +0 -0
  52. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_db_migration_fts.py +0 -0
  53. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_db_runtime.py +0 -0
  54. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_db_schema.py +0 -0
  55. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_delegated_search.py +0 -0
  56. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_dialectic.py +0 -0
  57. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_dialectic_feed_tools.py +0 -0
  58. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_dialectic_miner.py +0 -0
  59. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_dialectic_observation_resolve.py +0 -0
  60. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_dialectic_recompute.py +0 -0
  61. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_dialectic_tier.py +0 -0
  62. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_dialectic_validator.py +0 -0
  63. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_dialog_fts_external.py +0 -0
  64. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_documentation_inventory.py +0 -0
  65. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_egress_policy.py +0 -0
  66. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_embed_routing.py +0 -0
  67. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_embedding_generation.py +0 -0
  68. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_embedding_model_revision.py +0 -0
  69. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_embedding_storage_dedup.py +0 -0
  70. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_error_paths.py +0 -0
  71. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_eval_harness.py +0 -0
  72. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_evolve_apply_2.py +0 -0
  73. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_evolve_apply_3.py +0 -0
  74. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_evolve_apply_43.py +0 -0
  75. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_evolve_apply_45.py +0 -0
  76. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_evolve_daemon.py +0 -0
  77. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_extract_daemon.py +0 -0
  78. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_extract_dedup.py +0 -0
  79. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_github_budget.py +0 -0
  80. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_github_safety.py +0 -0
  81. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_helpers.py +0 -0
  82. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_hook_child_guard.py +0 -0
  83. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_host_embed.py +0 -0
  84. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_host_env.py +0 -0
  85. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_host_main.py +0 -0
  86. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_host_wedged.py +0 -0
  87. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_hybrid_retrieval.py +0 -0
  88. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_i18n_multilang.py +0 -0
  89. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_identity.py +0 -0
  90. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_ingest_cursor.py +0 -0
  91. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_ingest_denylist.py +0 -0
  92. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_ingest_secret_redaction.py +0 -0
  93. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_ingest_status.py +0 -0
  94. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_ingest_transactions.py +0 -0
  95. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_lesson_violations.py +0 -0
  96. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_lessons.py +0 -0
  97. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_mcp_protocol_conformance.py +0 -0
  98. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_mcp_resources_prompts.py +0 -0
  99. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_mcp_sdk_compat.py +0 -0
  100. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_mcp_server_instructions.py +0 -0
  101. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_mcp_skills_extension.py +0 -0
  102. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_mcp_tasks.py +0 -0
  103. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_memory_authority_lifecycle.py +0 -0
  104. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_memory_compatibility.py +0 -0
  105. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_memory_guard.py +0 -0
  106. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_memory_guard_host.py +0 -0
  107. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_menubar_app.py +0 -0
  108. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_missed_spawns.py +0 -0
  109. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_model_catalog.py +0 -0
  110. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_model_router.py +0 -0
  111. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_model_router_digest.py +0 -0
  112. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_model_router_tools.py +0 -0
  113. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_model_routes.py +0 -0
  114. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_notify_daemon.py +0 -0
  115. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_nudges.py +0 -0
  116. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_onnx_embeddings.py +0 -0
  117. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_panel.py +0 -0
  118. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_permissions.py +0 -0
  119. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_pickup.py +0 -0
  120. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_probe_daemon.py +0 -0
  121. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_process_health.py +0 -0
  122. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_project_scope.py +0 -0
  123. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_release_workflows.py +0 -0
  124. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_retention.py +0 -0
  125. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_review_prompts.py +0 -0
  126. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_search_fts_punctuation.py +0 -0
  127. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_session_read_purity.py +0 -0
  128. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_setup.py +0 -0
  129. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_shadow_review.py +0 -0
  130. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_single_flight_prompt_prefixes.py +0 -0
  131. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_skill_hint.py +0 -0
  132. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_skill_passive_tier.py +0 -0
  133. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_skill_tier.py +0 -0
  134. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_skill_updater.py +0 -0
  135. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_skill_use_parser.py +0 -0
  136. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_skill_watcher.py +0 -0
  137. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_skills.py +0 -0
  138. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_spawn_background_cwd.py +0 -0
  139. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_spawn_budget.py +0 -0
  140. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_spawn_child_env.py +0 -0
  141. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_spawn_codex_stdin.py +0 -0
  142. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_spawn_config.py +0 -0
  143. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_spawn_hint.py +0 -0
  144. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_spawn_permission_gate.py +0 -0
  145. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_spawn_reap.py +0 -0
  146. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_spawn_result.py +0 -0
  147. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_spawn_worktree_isolation.py +0 -0
  148. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_spawn_wrap.py +0 -0
  149. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_storage.py +0 -0
  150. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_sync_capture.py +0 -0
  151. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_sync_identity.py +0 -0
  152. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_sync_migrate.py +0 -0
  153. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_sync_protocol.py +0 -0
  154. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_sync_reset_node.py +0 -0
  155. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_sync_transport.py +0 -0
  156. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_task_retention.py +0 -0
  157. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_task_spool.py +0 -0
  158. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_thin_session.py +0 -0
  159. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_thread_janitor.py +0 -0
  160. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_threads.py +0 -0
  161. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_tool_annotations.py +0 -0
  162. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_tool_profiles.py +0 -0
  163. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_tools_smoke.py +0 -0
  164. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_tracing.py +0 -0
  165. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_validate_threads.py +0 -0
  166. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_vec_search.py +0 -0
  167. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_verify_ingest.py +0 -0
  168. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/tests/test_workflow_retention.py +0 -0
  169. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/__init__.py +0 -0
  170. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/_mcp.py +0 -0
  171. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/_spawn_wrap.py +0 -0
  172. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/adapters/__init__.py +0 -0
  173. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/adapters/_hook_helpers.py +0 -0
  174. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/adapters/antigravity.py +0 -0
  175. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/adapters/base.py +0 -0
  176. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/adapters/claude_code.py +0 -0
  177. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/adapters/claude_desktop.py +0 -0
  178. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/adapters/codex.py +0 -0
  179. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/adapters/copilot.py +0 -0
  180. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/adapters/vscode.py +0 -0
  181. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/agent_metadata.py +0 -0
  182. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/agent_status.py +0 -0
  183. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/assets/macos-agent-status/Info.plist +0 -0
  184. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/assets/macos-agent-status/README.md +0 -0
  185. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/assets/macos-agent-status/ThreadKeeperAgentStatus.swift +0 -0
  186. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/assets/macos-agent-status/build.sh +0 -0
  187. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/assets/macos-agent-status/install.sh +0 -0
  188. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/auto_update.py +0 -0
  189. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/backup.py +0 -0
  190. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/candidate_reviewer.py +0 -0
  191. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/config.py +0 -0
  192. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/config_io.py +0 -0
  193. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/config_watcher.py +0 -0
  194. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/curator_snapshots.py +0 -0
  195. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/daemon_liveness.py +0 -0
  196. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/daemon_state.py +0 -0
  197. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/daemon_supervisor.py +0 -0
  198. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/dashboard_app.py +0 -0
  199. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/db.py +0 -0
  200. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/dialectic_miner.py +0 -0
  201. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/egress.py +0 -0
  202. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/elicitation.py +0 -0
  203. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/embeddings.py +0 -0
  204. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/eval/__init__.py +0 -0
  205. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/eval/__main__.py +0 -0
  206. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/eval/harness.py +0 -0
  207. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/evolve_applier.py +0 -0
  208. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/evolve_daemon.py +0 -0
  209. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/extract_daemon.py +0 -0
  210. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/github_budget.py +0 -0
  211. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/github_safety.py +0 -0
  212. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/harvest.py +0 -0
  213. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/helpers.py +0 -0
  214. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/host.py +0 -0
  215. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/host_embed.py +0 -0
  216. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/i18n.py +0 -0
  217. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/identity.py +0 -0
  218. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/ingest.py +0 -0
  219. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/lesson_violations.py +0 -0
  220. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/link_health.py +0 -0
  221. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/mcp_skills.py +0 -0
  222. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/mcp_tasks.py +0 -0
  223. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/memory_compat.py +0 -0
  224. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/memory_guard.py +0 -0
  225. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/menubar_app.py +0 -0
  226. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/migrate_embeddings.py +0 -0
  227. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/model_catalog.py +0 -0
  228. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/model_router.py +0 -0
  229. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/model_router_digest.py +0 -0
  230. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/model_routes.py +0 -0
  231. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/notify.py +0 -0
  232. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/nudges.py +0 -0
  233. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/permissions.py +0 -0
  234. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/probe_daemon.py +0 -0
  235. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/process_health.py +0 -0
  236. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/project_scope.py +0 -0
  237. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/protocol.py +0 -0
  238. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/read_context.py +0 -0
  239. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/retention.py +0 -0
  240. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/retrieval.py +0 -0
  241. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/review_prompts.py +0 -0
  242. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/search_proxy.py +0 -0
  243. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/server.py +0 -0
  244. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/shadow_review.py +0 -0
  245. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/skill_audit.py +0 -0
  246. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/skill_updater.py +0 -0
  247. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/skill_watcher.py +0 -0
  248. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/spawn_config.py +0 -0
  249. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/spawn_result.py +0 -0
  250. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/storage.py +0 -0
  251. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/sync/__init__.py +0 -0
  252. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/sync/capture.py +0 -0
  253. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/sync/daemon.py +0 -0
  254. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/sync/identity.py +0 -0
  255. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/sync/migrate.py +0 -0
  256. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/sync/protocol.py +0 -0
  257. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/sync/reset_node.py +0 -0
  258. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/sync/server.py +0 -0
  259. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/task_spool.py +0 -0
  260. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/thread_janitor.py +0 -0
  261. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tool_profiles.py +0 -0
  262. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tool_schemas.py +0 -0
  263. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/__init__.py +0 -0
  264. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/agent_status.py +0 -0
  265. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/authority.py +0 -0
  266. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/candidate_reviewer.py +0 -0
  267. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/capabilities.py +0 -0
  268. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/concepts.py +0 -0
  269. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/config_watch.py +0 -0
  270. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/consolidate.py +0 -0
  271. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/core_memory.py +0 -0
  272. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/correlation.py +0 -0
  273. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/dashboard.py +0 -0
  274. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/db_maintenance.py +0 -0
  275. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/dialectic.py +0 -0
  276. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/dialectic_feed.py +0 -0
  277. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/dialog.py +0 -0
  278. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/distill.py +0 -0
  279. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/evolve_applier.py +0 -0
  280. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/evolve_research.py +0 -0
  281. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/extract.py +0 -0
  282. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/forget.py +0 -0
  283. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/graph.py +0 -0
  284. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/invariants.py +0 -0
  285. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/lessons.py +0 -0
  286. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/memory_guard.py +0 -0
  287. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/missed_spawns.py +0 -0
  288. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/model_router.py +0 -0
  289. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/panel.py +0 -0
  290. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/peers.py +0 -0
  291. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/pickup.py +0 -0
  292. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/probes.py +0 -0
  293. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/process_health.py +0 -0
  294. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/project_scope.py +0 -0
  295. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/prompts.py +0 -0
  296. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/resources.py +0 -0
  297. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/session.py +0 -0
  298. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/shadow_review.py +0 -0
  299. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/skills.py +0 -0
  300. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/style.py +0 -0
  301. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/sync.py +0 -0
  302. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/threads.py +0 -0
  303. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tools/validate.py +0 -0
  304. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/tracing.py +0 -0
  305. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/trash.py +0 -0
  306. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/verify_ingest.py +0 -0
  307. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/wikilinks.py +0 -0
  308. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper/workflow_artifacts.py +0 -0
  309. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper.egg-info/dependency_links.txt +0 -0
  310. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper.egg-info/entry_points.txt +0 -0
  311. {threadkeeper-0.20.0 → threadkeeper-0.21.0}/threadkeeper.egg-info/requires.txt +0 -0
  312. {threadkeeper-0.20.0 → threadkeeper-0.21.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.20.0
3
+ Version: 0.21.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
@@ -972,8 +972,12 @@ recovery snapshot right before its first mutating evaluator.
972
972
  its token cost); the evaluator then judges currency from local evidence only.
973
973
  A batch that holds no skills never gets a researcher: the fixed research topics
974
974
  only concern skills, so its evaluator starts at once.
975
- Each report path is then
976
- authorized in the existing parent-authored `curator_pass` event;
975
+ Curator pass and snapshot values are explicit, per-child launch data rather
976
+ than temporary server-process environment. Ordinary foreground and daemon
977
+ spawns strip stale inherited values. The private launcher binds each task,
978
+ child identity, pass, batch, phase, role, origin, and exact
979
+ report/research destination before launch; a watchdog retry restores only that
980
+ recorded data.
977
981
  `curator_report_write` records its SHA-256 in `curator_report_provenance`.
978
982
  This makes the report directory an untrusted transport: a stray or forged
979
983
  `REPORT-*.md` file cannot acquire the provenance needed by the applier.
@@ -1055,6 +1059,18 @@ within an 8-second budget per call; a call that runs out falls back to the
1055
1059
  lexical ranking and the next call resumes. Each line shows the lesson's summary. Without a query a session
1056
1060
  would only ever see the newest few lessons, whatever the task.
1057
1061
 
1062
+ **Lesson index.** Agents rarely search lessons on their own, so every lesson
1063
+ write also refreshes `lesson-index.md` next to `lessons.md`
1064
+ (`~/.threadkeeper/lesson-index.md` by default), a sorted list of all lesson
1065
+ titles (about 35 000 characters for 550 lessons). The managed
1066
+ instructions import it (`@~/.threadkeeper/lesson-index.md`), so a CLI that
1067
+ supports imports, such as Claude Code, loads every title with its instructions;
1068
+ other CLIs are told to read the file at session start. Each title is the rule,
1069
+ and the agent opens the ones that apply with `lesson_get`. On 160 real user
1070
+ prompts an agent that saw only this index opened 65-70% of the lessons judged
1071
+ relevant, and 51-59% of its picks were relevant. Injecting the lessons whose
1072
+ embedding is closest to the prompt reached 25% recall at 12% precision.
1073
+
1058
1074
  Lesson access is tracked the same way skill access is: `lesson_list` increments
1059
1075
  `lesson_usage.view_count` for displayed rows and `lesson_get` increments
1060
1076
  `lesson_usage.use_count` for the returned lesson. Only foreground reads count:
@@ -1448,6 +1464,13 @@ claim and its evidence but closes the old valid-time interval at the new claim's
1448
1464
  slice; `dialectic_review(as_of=...)` and
1449
1465
  `dialectic_synthesis(include_history=True)` expose past validity intervals.
1450
1466
 
1467
+ **Durable-preference drift pilot.** Run
1468
+ `python -m threadkeeper.dialectic_drift_eval --json` to replay the fixed,
1469
+ anonymized durable-change / task-exception / ambiguity / poisoning corpus. It
1470
+ reports supersession precision and recall, false durable-flip and clarification
1471
+ rates, poisoning containment, cited memory IDs, and counterfactuals. The pilot
1472
+ is read-only and does not change the live supersession policy.
1473
+
1451
1474
  **Source-based evidence discount.** Each evidence row's effective weight
1452
1475
  is `base_weight × discount(WRITE_ORIGIN)`. Foreground (direct user / human
1453
1476
  signal) = 1.0. shadow_review / background_review / candidate_review /
@@ -2251,6 +2274,12 @@ It reports these headline groups over a fixed ground-truth set:
2251
2274
  - **retrieval latency** — mean / p50 / p95 / max wall-clock milliseconds. With
2252
2275
  `--semantic`, the backend is reported as `hybrid`, because dense candidates
2253
2276
  augment rather than replace FTS.
2277
+ - **forgetting lifecycle** — the bundled mutation fixture writes a fact,
2278
+ corrects it twice, supersedes its dialectic claim, consolidates duplicate
2279
+ verbatim evidence, forgets the old source, and reopens the store in a new
2280
+ process. It reports a separate score for thread notes, verbatim memory,
2281
+ dialectic claims, and lessons or skills.
2282
+
2254
2283
  - **outcome classification** — retrieval failure, reasoning failure,
2255
2284
  stale-memory use, removed-memory use, and privacy-policy violation.
2256
2285
 
@@ -2286,6 +2315,13 @@ retrieved context, or replayed answer is written to stdout, artifacts, or the
2286
2315
  repository. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the corpus
2287
2316
  contract and migration-gate interpretation.
2288
2317
 
2318
+ The lifecycle score gives current evidence `+1` and subtracts `0.5` for every
2319
+ obsolete or invalidated fact that the same retrieval path returns. A current
2320
+ fact alongside stale evidence therefore does not receive full credit. The
2321
+ post-forgetting and post-restart stages must score `1.0` in both deterministic
2322
+ FTS and hybrid variants; the JSON report names the lifecycle stage and memory
2323
+ class for any release-gate failure.
2324
+
2289
2325
  ## Evaluating learning-loop decision quality
2290
2326
 
2291
2327
  `verify_ingest` answers *"did we capture the data?"*. The decision-quality
@@ -925,8 +925,12 @@ recovery snapshot right before its first mutating evaluator.
925
925
  its token cost); the evaluator then judges currency from local evidence only.
926
926
  A batch that holds no skills never gets a researcher: the fixed research topics
927
927
  only concern skills, so its evaluator starts at once.
928
- Each report path is then
929
- authorized in the existing parent-authored `curator_pass` event;
928
+ Curator pass and snapshot values are explicit, per-child launch data rather
929
+ than temporary server-process environment. Ordinary foreground and daemon
930
+ spawns strip stale inherited values. The private launcher binds each task,
931
+ child identity, pass, batch, phase, role, origin, and exact
932
+ report/research destination before launch; a watchdog retry restores only that
933
+ recorded data.
930
934
  `curator_report_write` records its SHA-256 in `curator_report_provenance`.
931
935
  This makes the report directory an untrusted transport: a stray or forged
932
936
  `REPORT-*.md` file cannot acquire the provenance needed by the applier.
@@ -1008,6 +1012,18 @@ within an 8-second budget per call; a call that runs out falls back to the
1008
1012
  lexical ranking and the next call resumes. Each line shows the lesson's summary. Without a query a session
1009
1013
  would only ever see the newest few lessons, whatever the task.
1010
1014
 
1015
+ **Lesson index.** Agents rarely search lessons on their own, so every lesson
1016
+ write also refreshes `lesson-index.md` next to `lessons.md`
1017
+ (`~/.threadkeeper/lesson-index.md` by default), a sorted list of all lesson
1018
+ titles (about 35 000 characters for 550 lessons). The managed
1019
+ instructions import it (`@~/.threadkeeper/lesson-index.md`), so a CLI that
1020
+ supports imports, such as Claude Code, loads every title with its instructions;
1021
+ other CLIs are told to read the file at session start. Each title is the rule,
1022
+ and the agent opens the ones that apply with `lesson_get`. On 160 real user
1023
+ prompts an agent that saw only this index opened 65-70% of the lessons judged
1024
+ relevant, and 51-59% of its picks were relevant. Injecting the lessons whose
1025
+ embedding is closest to the prompt reached 25% recall at 12% precision.
1026
+
1011
1027
  Lesson access is tracked the same way skill access is: `lesson_list` increments
1012
1028
  `lesson_usage.view_count` for displayed rows and `lesson_get` increments
1013
1029
  `lesson_usage.use_count` for the returned lesson. Only foreground reads count:
@@ -1401,6 +1417,13 @@ claim and its evidence but closes the old valid-time interval at the new claim's
1401
1417
  slice; `dialectic_review(as_of=...)` and
1402
1418
  `dialectic_synthesis(include_history=True)` expose past validity intervals.
1403
1419
 
1420
+ **Durable-preference drift pilot.** Run
1421
+ `python -m threadkeeper.dialectic_drift_eval --json` to replay the fixed,
1422
+ anonymized durable-change / task-exception / ambiguity / poisoning corpus. It
1423
+ reports supersession precision and recall, false durable-flip and clarification
1424
+ rates, poisoning containment, cited memory IDs, and counterfactuals. The pilot
1425
+ is read-only and does not change the live supersession policy.
1426
+
1404
1427
  **Source-based evidence discount.** Each evidence row's effective weight
1405
1428
  is `base_weight × discount(WRITE_ORIGIN)`. Foreground (direct user / human
1406
1429
  signal) = 1.0. shadow_review / background_review / candidate_review /
@@ -2204,6 +2227,12 @@ It reports these headline groups over a fixed ground-truth set:
2204
2227
  - **retrieval latency** — mean / p50 / p95 / max wall-clock milliseconds. With
2205
2228
  `--semantic`, the backend is reported as `hybrid`, because dense candidates
2206
2229
  augment rather than replace FTS.
2230
+ - **forgetting lifecycle** — the bundled mutation fixture writes a fact,
2231
+ corrects it twice, supersedes its dialectic claim, consolidates duplicate
2232
+ verbatim evidence, forgets the old source, and reopens the store in a new
2233
+ process. It reports a separate score for thread notes, verbatim memory,
2234
+ dialectic claims, and lessons or skills.
2235
+
2207
2236
  - **outcome classification** — retrieval failure, reasoning failure,
2208
2237
  stale-memory use, removed-memory use, and privacy-policy violation.
2209
2238
 
@@ -2239,6 +2268,13 @@ retrieved context, or replayed answer is written to stdout, artifacts, or the
2239
2268
  repository. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the corpus
2240
2269
  contract and migration-gate interpretation.
2241
2270
 
2271
+ The lifecycle score gives current evidence `+1` and subtracts `0.5` for every
2272
+ obsolete or invalidated fact that the same retrieval path returns. A current
2273
+ fact alongside stale evidence therefore does not receive full credit. The
2274
+ post-forgetting and post-restart stages must score `1.0` in both deterministic
2275
+ FTS and hybrid variants; the JSON report names the lifecycle stage and memory
2276
+ class for any release-gate failure.
2277
+
2242
2278
  ## Evaluating learning-loop decision quality
2243
2279
 
2244
2280
  `verify_ingest` answers *"did we capture the data?"*. The decision-quality
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "threadkeeper"
7
- version = "0.20.0"
7
+ version = "0.21.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" }]
@@ -0,0 +1,69 @@
1
+ """The SessionStart brief stays under the CLI's hook output limit."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import os
6
+ import subprocess
7
+ from pathlib import Path
8
+
9
+ HOOK = Path(__file__).resolve().parents[1] / "scripts" / "hooks" / "tk-brief.sh"
10
+
11
+
12
+ def _run_hook(tmp_path: Path, context_lines: int) -> str:
13
+ fake_python = tmp_path / "python"
14
+ body = "\\n".join(f"rule {i}: " + "x" * 60 for i in range(context_lines))
15
+ fake_python.write_text(
16
+ "#!/bin/sh\ncat >/dev/null\n"
17
+ f"printf 'STATUS\\tthread-keeper: ok\\nCONTEXT\\t{body}\\n'\n",
18
+ encoding="utf-8",
19
+ )
20
+ fake_python.chmod(0o700)
21
+ env = {
22
+ "PATH": os.environ.get("PATH", "/usr/bin:/bin"),
23
+ "HOME": str(tmp_path),
24
+ "THREADKEEPER_PYTHON": str(fake_python),
25
+ }
26
+ proc = subprocess.run(
27
+ ["bash", str(HOOK)], input="{}", capture_output=True, text=True, env=env, timeout=30,
28
+ )
29
+ assert proc.returncode == 0, proc.stderr
30
+ return json.loads(proc.stdout)["hookSpecificOutput"]["additionalContext"]
31
+
32
+
33
+ def test_oversized_brief_is_cut_at_a_line_break_under_the_hook_limit(tmp_path):
34
+ context = _run_hook(tmp_path, context_lines=400)
35
+
36
+ assert len(context) <= 9000
37
+ assert context.endswith("call brief() for the rest]")
38
+ assert "\nrule 0: " in context
39
+ kept = context.splitlines()[-2]
40
+ assert kept.startswith("rule ") and kept.endswith("x" * 60)
41
+
42
+
43
+ def test_brief_under_the_limit_is_passed_through_whole(tmp_path):
44
+ context = _run_hook(tmp_path, context_lines=20)
45
+
46
+ assert "rule 19: " in context
47
+ assert "trimmed" not in context
48
+
49
+
50
+ def test_brief_without_line_breaks_is_cut_at_the_limit(tmp_path):
51
+ fake_python = tmp_path / "python"
52
+ fake_python.write_text(
53
+ "#!/bin/sh\ncat >/dev/null\n"
54
+ "printf 'STATUS\\tok\\nCONTEXT\\t%s\\n' \"$(head -c 20000 /dev/zero | tr '\\0' x)\"\n",
55
+ encoding="utf-8",
56
+ )
57
+ fake_python.chmod(0o700)
58
+ env = {
59
+ "PATH": os.environ.get("PATH", "/usr/bin:/bin"),
60
+ "HOME": str(tmp_path),
61
+ "THREADKEEPER_PYTHON": str(fake_python),
62
+ }
63
+ proc = subprocess.run(
64
+ ["bash", str(HOOK)], input="{}", capture_output=True, text=True, env=env, timeout=30,
65
+ )
66
+
67
+ context = json.loads(proc.stdout)["hookSpecificOutput"]["additionalContext"]
68
+ assert 8000 < len(context) <= 9000
69
+ assert context.endswith("call brief() for the rest]")
@@ -146,6 +146,49 @@ def test_consulted_skills_silent_without_events(mp_with_cid):
146
146
  assert "consulted_skills" not in txt
147
147
 
148
148
 
149
+ def test_consulted_lessons_surface_recall_telemetry_and_keep_brief_sections(
150
+ mp_with_cid,
151
+ ):
152
+ """A recalled lesson keeps its read telemetry visible without dropping
153
+ the existing thread, evolve, or user-facing brief sections."""
154
+ pkg = mp_with_cid(_FAKE_CID)
155
+ lesson_append = _tool(pkg, "lesson_append")
156
+ lesson_list = _tool(pkg, "lesson_list")
157
+ lesson_get = _tool(pkg, "lesson_get")
158
+ open_thread = _tool(pkg, "open_thread")
159
+
160
+ assert lesson_append(
161
+ title="Recall telemetry", body="Use this lesson for brief coverage.",
162
+ source="shadow",
163
+ ).startswith("ok")
164
+ lesson_list(k=1)
165
+ assert "Use this lesson for brief coverage." in lesson_get(
166
+ slug="recall-telemetry"
167
+ )
168
+ open_thread(question="keep the open section")
169
+
170
+ conn = pkg["db"].get_db()
171
+ conn.execute(
172
+ "INSERT INTO evolve (suggestion, rationale, applied, status, created_at) "
173
+ "VALUES (?, ?, 0, 'promoted', ?)",
174
+ ("seeded promoted suggestion", "brief coverage", int(time.time())),
175
+ )
176
+ conn.commit()
177
+
178
+ txt = _brief_text(pkg)
179
+
180
+ assert "consulted_lessons" in txt
181
+ assert "recall-telemetry" in txt
182
+ assert "viewed×1" in txt
183
+ assert "used×1" in txt
184
+ assert "last_recalled=" in txt
185
+ assert "open" in txt
186
+ assert "keep the open section" in txt
187
+ assert "evolve_pending" in txt
188
+ assert "★ \"seeded promoted suggestion\"" in txt
189
+ assert "⚠️ user-facing: paraphrase plain." in txt
190
+
191
+
149
192
  def test_skill_nudge_silent_after_materialization(mp_with_cid, monkeypatch):
150
193
  monkeypatch.setenv("THREADKEEPER_SKILL_NUDGE_INTERVAL", "3")
151
194
  pkg = mp_with_cid(_FAKE_CID)