threadkeeper 0.22.0__tar.gz → 0.23.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 (337) hide show
  1. {threadkeeper-0.22.0/threadkeeper.egg-info → threadkeeper-0.23.0}/PKG-INFO +354 -50
  2. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/README.md +353 -49
  3. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/pyproject.toml +3 -1
  4. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_agent_status.py +1 -0
  5. threadkeeper-0.23.0/tests/test_child_memory_access.py +252 -0
  6. threadkeeper-0.23.0/tests/test_consequential_state.py +216 -0
  7. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_curator.py +323 -4
  8. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_db_schema.py +8 -0
  9. threadkeeper-0.23.0/tests/test_evidence_resources.py +295 -0
  10. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_evolve_applier.py +448 -6
  11. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_host_env.py +38 -2
  12. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_host_wedged.py +3 -2
  13. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_hybrid_retrieval.py +2 -2
  14. threadkeeper-0.23.0/tests/test_lesson_freshness.py +254 -0
  15. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_lessons.py +8 -3
  16. threadkeeper-0.23.0/tests/test_mcp_conformance_gate.py +45 -0
  17. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_mcp_protocol_conformance.py +68 -0
  18. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_menubar_app.py +59 -2
  19. threadkeeper-0.23.0/tests/test_model_pricing.py +158 -0
  20. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_model_router.py +59 -1
  21. threadkeeper-0.23.0/tests/test_model_router_digest.py +330 -0
  22. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_notify_daemon.py +41 -0
  23. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_project_scope.py +4 -1
  24. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_review_prompts.py +4 -2
  25. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_search_fts_punctuation.py +4 -1
  26. threadkeeper-0.23.0/tests/test_semantic_bundle.py +217 -0
  27. threadkeeper-0.23.0/tests/test_skill_fixtures.py +280 -0
  28. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_skills.py +358 -0
  29. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_spawn_child_env.py +68 -0
  30. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_spawn_slim.py +33 -0
  31. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_spawn_worktree_isolation.py +26 -0
  32. threadkeeper-0.23.0/tests/test_task_gate.py +190 -0
  33. threadkeeper-0.23.0/tests/test_task_worktree_retention.py +353 -0
  34. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_tool_annotations.py +6 -4
  35. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_tool_profiles.py +23 -1
  36. threadkeeper-0.23.0/tests/test_tracing.py +470 -0
  37. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/_mcp.py +13 -3
  38. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/_setup.py +4 -6
  39. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/adapters/base.py +2 -0
  40. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/adapters/claude_code.py +3 -4
  41. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/adapters/codex.py +1 -0
  42. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/agent_status.py +4 -2
  43. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/assets/macos-agent-status/README.md +14 -0
  44. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/assets/macos-agent-status/ThreadKeeperAgentStatus.swift +116 -0
  45. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/authority.py +26 -1
  46. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/brief.py +2 -0
  47. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/candidate_reviewer.py +2 -1
  48. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/config.py +3 -0
  49. threadkeeper-0.23.0/threadkeeper/conformance_server.py +31 -0
  50. threadkeeper-0.23.0/threadkeeper/consequential_state.py +158 -0
  51. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/curator.py +238 -19
  52. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/curator_snapshots.py +13 -4
  53. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/db.py +130 -2
  54. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/dialectic_validator.py +1 -0
  55. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/embeddings.py +25 -9
  56. threadkeeper-0.23.0/threadkeeper/evidence.py +238 -0
  57. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/evolve_applier.py +590 -105
  58. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/evolve_daemon.py +3 -2
  59. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/host.py +13 -0
  60. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/ingest.py +1 -1
  61. threadkeeper-0.23.0/threadkeeper/lesson_freshness.py +138 -0
  62. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/lessons.py +21 -6
  63. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/mcp_skills.py +14 -0
  64. threadkeeper-0.23.0/threadkeeper/memory_access.py +275 -0
  65. threadkeeper-0.23.0/threadkeeper/model_pricing.py +203 -0
  66. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/model_router.py +78 -17
  67. threadkeeper-0.23.0/threadkeeper/model_router_digest.py +379 -0
  68. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/notify.py +37 -8
  69. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/probe_daemon.py +1 -0
  70. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/project_scope.py +19 -1
  71. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/protocol.py +24 -6
  72. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/retrieval.py +4 -2
  73. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/review_prompts.py +10 -2
  74. threadkeeper-0.23.0/threadkeeper/semantic_bundle.py +1127 -0
  75. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/server.py +1 -0
  76. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/shadow_review.py +2 -1
  77. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/skill_audit.py +327 -20
  78. threadkeeper-0.23.0/threadkeeper/skill_lifecycle.py +18 -0
  79. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/skill_watcher.py +17 -0
  80. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/spawn_budget.py +3 -0
  81. threadkeeper-0.23.0/threadkeeper/task_worktrees.py +283 -0
  82. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tool_profiles.py +9 -0
  83. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tool_schemas.py +31 -5
  84. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/consolidate.py +46 -5
  85. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/curator.py +63 -4
  86. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/dashboard.py +2 -1
  87. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/dialog.py +26 -12
  88. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/evolve_applier.py +21 -3
  89. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/lessons.py +61 -4
  90. threadkeeper-0.23.0/threadkeeper/tools/memory_proposals.py +80 -0
  91. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/panel.py +1 -0
  92. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/pickup.py +1 -0
  93. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/resources.py +2 -3
  94. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/skills.py +582 -93
  95. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/spawn.py +123 -13
  96. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/threads.py +13 -6
  97. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tracing.py +134 -6
  98. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/trash.py +30 -0
  99. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/workflow_artifacts.py +5 -1
  100. {threadkeeper-0.22.0 → threadkeeper-0.23.0/threadkeeper.egg-info}/PKG-INFO +354 -50
  101. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper.egg-info/SOURCES.txt +20 -0
  102. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper.egg-info/entry_points.txt +1 -0
  103. threadkeeper-0.22.0/tests/test_model_router_digest.py +0 -107
  104. threadkeeper-0.22.0/tests/test_tracing.py +0 -237
  105. threadkeeper-0.22.0/threadkeeper/model_router_digest.py +0 -234
  106. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/LICENSE +0 -0
  107. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/setup.cfg +0 -0
  108. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_action_outcomes.py +0 -0
  109. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_adapters.py +0 -0
  110. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_antigravity_ingest.py +0 -0
  111. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_auto_update.py +0 -0
  112. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_backup.py +0 -0
  113. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_brief_footprint.py +0 -0
  114. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_brief_hook_size.py +0 -0
  115. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_brief_sections.py +0 -0
  116. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_candidate_reviewer.py +0 -0
  117. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_ci_parallel.py +0 -0
  118. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_concepts.py +0 -0
  119. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_config_settings.py +0 -0
  120. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_config_watcher.py +0 -0
  121. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_core_memory.py +0 -0
  122. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_daemon_host_config.py +0 -0
  123. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_daemon_host_integration.py +0 -0
  124. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_daemon_liveness.py +0 -0
  125. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_daemon_state.py +0 -0
  126. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_dashboard.py +0 -0
  127. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_dashboard_app.py +0 -0
  128. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_db_compact.py +0 -0
  129. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_db_contention.py +0 -0
  130. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_db_leak_guard.py +0 -0
  131. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_db_migration_fts.py +0 -0
  132. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_db_runtime.py +0 -0
  133. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_delegated_search.py +0 -0
  134. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_dialectic.py +0 -0
  135. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_dialectic_drift_eval.py +0 -0
  136. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_dialectic_feed_tools.py +0 -0
  137. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_dialectic_miner.py +0 -0
  138. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_dialectic_observation_resolve.py +0 -0
  139. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_dialectic_recompute.py +0 -0
  140. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_dialectic_tier.py +0 -0
  141. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_dialectic_validator.py +0 -0
  142. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_dialog_fts_external.py +0 -0
  143. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_documentation_inventory.py +0 -0
  144. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_egress_policy.py +0 -0
  145. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_embed_routing.py +0 -0
  146. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_embedding_generation.py +0 -0
  147. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_embedding_model_revision.py +0 -0
  148. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_embedding_storage_dedup.py +0 -0
  149. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_error_paths.py +0 -0
  150. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_eval_harness.py +0 -0
  151. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_evolve_apply_2.py +0 -0
  152. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_evolve_apply_3.py +0 -0
  153. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_evolve_apply_43.py +0 -0
  154. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_evolve_apply_45.py +0 -0
  155. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_evolve_apply_48.py +0 -0
  156. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_evolve_daemon.py +0 -0
  157. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_extract_daemon.py +0 -0
  158. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_extract_dedup.py +0 -0
  159. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_forget.py +0 -0
  160. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_github_budget.py +0 -0
  161. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_github_safety.py +0 -0
  162. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_helpers.py +0 -0
  163. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_hook_child_guard.py +0 -0
  164. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_host_embed.py +0 -0
  165. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_host_main.py +0 -0
  166. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_i18n_multilang.py +0 -0
  167. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_identity.py +0 -0
  168. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_ingest_cursor.py +0 -0
  169. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_ingest_denylist.py +0 -0
  170. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_ingest_secret_redaction.py +0 -0
  171. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_ingest_status.py +0 -0
  172. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_ingest_transactions.py +0 -0
  173. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_lesson_index.py +0 -0
  174. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_lesson_violations.py +0 -0
  175. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_mcp_resources_prompts.py +0 -0
  176. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_mcp_sdk_compat.py +0 -0
  177. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_mcp_server_instructions.py +0 -0
  178. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_mcp_skills_extension.py +0 -0
  179. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_mcp_tasks.py +0 -0
  180. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_memory_authority_lifecycle.py +0 -0
  181. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_memory_compatibility.py +0 -0
  182. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_memory_eval.py +0 -0
  183. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_memory_guard.py +0 -0
  184. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_memory_guard_host.py +0 -0
  185. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_missed_spawns.py +0 -0
  186. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_model_catalog.py +0 -0
  187. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_model_router_tools.py +0 -0
  188. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_model_routes.py +0 -0
  189. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_nudges.py +0 -0
  190. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_onnx_embeddings.py +0 -0
  191. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_panel.py +0 -0
  192. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_permissions.py +0 -0
  193. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_pickup.py +0 -0
  194. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_probe_daemon.py +0 -0
  195. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_process_health.py +0 -0
  196. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_release_workflows.py +0 -0
  197. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_retention.py +0 -0
  198. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_session_read_purity.py +0 -0
  199. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_setup.py +0 -0
  200. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_shadow_review.py +0 -0
  201. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_single_flight_prompt_prefixes.py +0 -0
  202. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_skill_hint.py +0 -0
  203. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_skill_passive_tier.py +0 -0
  204. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_skill_tier.py +0 -0
  205. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_skill_updater.py +0 -0
  206. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_skill_use_parser.py +0 -0
  207. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_skill_watcher.py +0 -0
  208. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_spawn_background_cwd.py +0 -0
  209. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_spawn_budget.py +0 -0
  210. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_spawn_codex_stdin.py +0 -0
  211. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_spawn_config.py +0 -0
  212. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_spawn_hint.py +0 -0
  213. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_spawn_permission_gate.py +0 -0
  214. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_spawn_reap.py +0 -0
  215. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_spawn_result.py +0 -0
  216. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_spawn_watchdog.py +0 -0
  217. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_spawn_wrap.py +0 -0
  218. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_storage.py +0 -0
  219. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_sync_capture.py +0 -0
  220. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_sync_identity.py +0 -0
  221. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_sync_migrate.py +0 -0
  222. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_sync_protocol.py +0 -0
  223. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_sync_reset_node.py +0 -0
  224. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_sync_transport.py +0 -0
  225. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_task_retention.py +0 -0
  226. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_task_spool.py +0 -0
  227. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_thin_session.py +0 -0
  228. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_thread_janitor.py +0 -0
  229. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_threads.py +0 -0
  230. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_tools_smoke.py +0 -0
  231. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_validate_threads.py +0 -0
  232. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_vec_search.py +0 -0
  233. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_verify_ingest.py +0 -0
  234. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/tests/test_workflow_retention.py +0 -0
  235. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/__init__.py +0 -0
  236. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/_spawn_wrap.py +0 -0
  237. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/action_outcomes.py +0 -0
  238. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/adapters/__init__.py +0 -0
  239. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/adapters/_hook_helpers.py +0 -0
  240. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/adapters/antigravity.py +0 -0
  241. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/adapters/claude_desktop.py +0 -0
  242. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/adapters/copilot.py +0 -0
  243. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/adapters/vscode.py +0 -0
  244. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/agent_metadata.py +0 -0
  245. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/assets/macos-agent-status/Info.plist +0 -0
  246. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/assets/macos-agent-status/build.sh +0 -0
  247. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/assets/macos-agent-status/install.sh +0 -0
  248. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/auto_update.py +0 -0
  249. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/backup.py +0 -0
  250. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/config_io.py +0 -0
  251. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/config_watcher.py +0 -0
  252. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/daemon_liveness.py +0 -0
  253. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/daemon_state.py +0 -0
  254. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/daemon_supervisor.py +0 -0
  255. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/dashboard_app.py +0 -0
  256. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/dialectic_drift_eval.py +0 -0
  257. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/dialectic_miner.py +0 -0
  258. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/egress.py +0 -0
  259. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/elicitation.py +0 -0
  260. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/eval/__init__.py +0 -0
  261. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/eval/__main__.py +0 -0
  262. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/eval/harness.py +0 -0
  263. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/extract_daemon.py +0 -0
  264. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/forget.py +0 -0
  265. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/github_budget.py +0 -0
  266. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/github_safety.py +0 -0
  267. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/harvest.py +0 -0
  268. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/helpers.py +0 -0
  269. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/host_embed.py +0 -0
  270. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/i18n.py +0 -0
  271. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/identity.py +0 -0
  272. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/instructions.py +0 -0
  273. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/lesson_violations.py +0 -0
  274. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/link_health.py +0 -0
  275. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/mcp_tasks.py +0 -0
  276. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/memory_compat.py +0 -0
  277. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/memory_guard.py +0 -0
  278. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/menubar_app.py +0 -0
  279. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/migrate_embeddings.py +0 -0
  280. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/model_catalog.py +0 -0
  281. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/model_routes.py +0 -0
  282. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/nudges.py +0 -0
  283. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/permissions.py +0 -0
  284. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/process_health.py +0 -0
  285. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/read_context.py +0 -0
  286. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/retention.py +0 -0
  287. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/search_proxy.py +0 -0
  288. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/skill_updater.py +0 -0
  289. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/spawn_config.py +0 -0
  290. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/spawn_result.py +0 -0
  291. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/storage.py +0 -0
  292. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/sync/__init__.py +0 -0
  293. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/sync/capture.py +0 -0
  294. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/sync/daemon.py +0 -0
  295. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/sync/identity.py +0 -0
  296. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/sync/migrate.py +0 -0
  297. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/sync/protocol.py +0 -0
  298. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/sync/reset_node.py +0 -0
  299. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/sync/server.py +0 -0
  300. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/task_spool.py +0 -0
  301. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/thread_janitor.py +0 -0
  302. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/__init__.py +0 -0
  303. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/agent_status.py +0 -0
  304. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/authority.py +0 -0
  305. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/candidate_reviewer.py +0 -0
  306. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/capabilities.py +0 -0
  307. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/concepts.py +0 -0
  308. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/config_watch.py +0 -0
  309. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/core_memory.py +0 -0
  310. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/correlation.py +0 -0
  311. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/db_maintenance.py +0 -0
  312. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/dialectic.py +0 -0
  313. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/dialectic_feed.py +0 -0
  314. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/distill.py +0 -0
  315. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/evolve_research.py +0 -0
  316. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/extract.py +0 -0
  317. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/forget.py +0 -0
  318. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/graph.py +0 -0
  319. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/invariants.py +0 -0
  320. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/memory_guard.py +0 -0
  321. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/missed_spawns.py +0 -0
  322. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/model_router.py +0 -0
  323. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/peers.py +0 -0
  324. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/probes.py +0 -0
  325. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/process_health.py +0 -0
  326. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/project_scope.py +0 -0
  327. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/prompts.py +0 -0
  328. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/session.py +0 -0
  329. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/shadow_review.py +0 -0
  330. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/style.py +0 -0
  331. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/sync.py +0 -0
  332. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/tools/validate.py +0 -0
  333. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/verify_ingest.py +0 -0
  334. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper/wikilinks.py +0 -0
  335. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper.egg-info/dependency_links.txt +0 -0
  336. {threadkeeper-0.22.0 → threadkeeper-0.23.0}/threadkeeper.egg-info/requires.txt +0 -0
  337. {threadkeeper-0.22.0 → threadkeeper-0.23.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.22.0
3
+ Version: 0.23.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
@@ -101,7 +101,11 @@ make it more than a memory store:
101
101
  `~/.codex/skills/`, `~/.gemini/config/skills/` for Antigravity,
102
102
  existing `~/.agents/skills/`, extra roots from
103
103
  `THREADKEEPER_EXTRA_SKILLS_DIRS`, and `~/.threadkeeper/skills/`), with
104
- lessons.md as a fallback for CLIs without a native skills loader.
104
+ lessons.md as a fallback for CLIs without a native skills loader. A managed
105
+ skill write reports success only after every mirror has the expected file
106
+ set and hashes; `ERR mirror_partial_failure` keeps the canonical copy and
107
+ names only the affected consumer plus relative file. Retry safely with
108
+ `skill_manage(action="reconcile", name="<skill>")`.
105
109
 
106
110
  Foreground MCP servers also run a daily self-update check by default. Source
107
111
  checkouts fast-forward their tracked git branch and reinstall the editable
@@ -254,6 +258,22 @@ surface memory as attachable / `@`-mentionable context through a mechanical
254
258
  channel. The brief resource renders lean and agent-status uses a cached snapshot,
255
259
  so an automatic host pull is **side-effect-free**.
256
260
 
261
+ `search` and `dialog_search` also return structured hits and `resource_link`
262
+ blocks. Read a hit's `resource_uri` with `resources/read` to inspect its evidence
263
+ without a broader search. Notes can also provide a `thread_uri` for a bounded
264
+ thread excerpt. Dialog links contain the matching message and up to two messages
265
+ on each side from the same source, session, and scope. Windows have fixed
266
+ membership; later messages do not expand an issued link.
267
+
268
+ These opaque `memory://evidence/...` links are private to the requesting caller
269
+ and project, expire after five minutes, and may be evicted sooner or lost on
270
+ server restart. Search again for a fresh link. Evidence reads return at most
271
+ 32 KiB of JSON, mark shortened bodies with `truncated`, and recheck scope,
272
+ authority invalidation, erasure, and dialog retention on every read. Thread
273
+ excerpts include at most ten notes. Links are not listed in `resources/list`
274
+ and must not be cached. Hosts that ignore links and structured output keep the
275
+ same first text block and search behavior.
276
+
257
277
  **Prompts** turn the curation / audit / review flows into discoverable,
258
278
  parameterized commands; each just drives the existing tools.
259
279
 
@@ -400,6 +420,27 @@ sent to another model provider.
400
420
 
401
421
  ### Spawn — primary parallelism primitive
402
422
 
423
+ Public children have explicit durable-memory access. `spawn(...,
424
+ memory_access="read_only")` is the default; `memory_access="propose"` queues
425
+ supported memory-writing calls as pending candidates. A foreground reviewer
426
+ uses `memory_proposals()` and `memory_proposal_decide(id, accept=True)` to
427
+ apply one candidate. New generated artifacts keep **observed** authority.
428
+ Only private Curator and Evolve launchers can grant direct `write` access.
429
+ Curator researchers, probe runners, model routers, and general helpers remain
430
+ read-only; learning reviewers submit proposals. Coordination and authorized
431
+ research/report handoffs remain available.
432
+
433
+ These are three separate controls: tool profiles decide which tools a child
434
+ can discover; provenance labels describe where content came from; a persisted,
435
+ task-bound memory capability decides whether it can write accepted memory.
436
+ Changing `write_origin`, a role, or the advertised tool profile grants no memory
437
+ authority. An omitted or forged child grant fails closed. Ordinary child reads
438
+ are capped to the launch project's namespace plus global/shared memory, even
439
+ with `cross_project=True`; a child outside a resolved project sees only
440
+ global/shared memory. Existing memory keeps its current scope. This boundary protects server
441
+ tools and storage helpers; it does not sandbox arbitrary code running under
442
+ the same operating-system account with direct access to database files.
443
+
403
444
  `spawn(prompt, slim=True, role=..., visible=False, ...)` launches a child
404
445
  Claude session via a `claude -p` subprocess. By default `slim=True`: the
405
446
  child loads only the thread-keeper MCP, no embeddings, no third-party
@@ -671,6 +712,36 @@ optional `THREADKEEPER_EXTRA_SKILLS_DIRS`, plus the canonical
671
712
  CLI-agnostic fallback for clients without a native skills loader (Copilot and
672
713
  bare MCP clients).
673
714
 
715
+ **Hidden test fixtures.** Maintainers can keep seeds and probes without adding
716
+ them to the guidance that agents discover or to Curator's production health
717
+ totals. Create one with
718
+ `skill_manage(action="create", name="validator-example", description="Validation only", content="...", classification="fixture")`.
719
+ Fixtures live beside each skill root under
720
+ `<root parent>/.threadkeeper-fixtures/<root name>/<name>/`, outside recursive
721
+ skill discovery. The durable
722
+ classification is separate from the active/stale/archived aging state;
723
+ fixtures never age automatically.
724
+
725
+ Use `skill_list(include_fixtures=True)` to list them and
726
+ `skill_validate(name="validator-example", include_fixtures=True)` to run the
727
+ usual file, link, consumer, and mirror checks. Dedicated test runs can record
728
+ their own counters with `skill_record(..., include_fixtures=True)`; passive
729
+ usage and ordinary production totals exclude fixtures. Retire a fixture with
730
+ `skill_manage(action="delete", name="validator-example", force=True)` from a
731
+ foreground session. The usual protection and trash/restore rules still apply.
732
+ Semantic bundle export/import and hidden mirror copies preserve classification.
733
+
734
+ For a legacy fixture, first select the entry using known test provenance and
735
+ call `skill_fixture_migrate(name="...", evidence="why this is validation data")`.
736
+ This preview returns the candidate's current classification, origin, evidence,
737
+ and SHA-256 without changing it. After reviewing that evidence, call it again
738
+ with `dry_run=False` and `expected_sha256="<preview sha256>"` from a foreground
739
+ session. It moves the entry into hidden storage and removes ordinary mirror
740
+ copies. Retry partial mirror failures with `skill_manage(action="reconcile", name="...")`.
741
+ Names such as `test-*`, `seed-*`, or `probe-*` never trigger classification.
742
+ Existing and user-authored guidance stays production guidance until an explicit
743
+ maintainer migration; startup performs no fixture data migration.
744
+
674
745
  **Harvest boundary (issue #36).** The dialog-reading loops share
675
746
  `threadkeeper.harvest` as their session exclusion boundary. Raw transcripts are
676
747
  still persisted for diagnostics, but shadow-review, extract, dialectic mining,
@@ -697,6 +768,33 @@ them without touching foreground-authored ones, and a write-time screen
697
768
  refuses loop-origin lesson/skill bodies that contain imperative-override /
698
769
  remote-exec idioms. See [`SECURITY.md`](SECURITY.md).
699
770
 
771
+ **Host settings need consent and rollback (#390).** Background writers cannot
772
+ save unconditional advice to reset a host proxy, remove credentials, change
773
+ shell/global settings, control startup services, change home/system permissions,
774
+ or alter global packages. Each procedure must state a narrow confirmed
775
+ precondition, require the owner's consent, save the original state, and restore
776
+ it during cleanup, including on failure. Read-only checks and project-local
777
+ changes remain allowed. This is a conservative English text/command screen,
778
+ separate from injection detection, rather than a semantic safety guarantee.
779
+
780
+ Existing unsafe advice stays on disk with an **OWNER REVIEW REQUIRED** warning
781
+ in lesson retrieval, skill listings, and Curator inventories. Skill manifests
782
+ show the warning in their description; MCP resource delivery pauses with the
783
+ same warning. Curator never rewrites protected records to clear it, including
784
+ in its verifier phase. The skill watcher also flags direct file edits.
785
+
786
+ To clear a warning, the owner reviews the procedure in a foreground session,
787
+ then uses `lesson_patch` / `lesson_append` or `skill_manage` to remove the unsafe
788
+ advice or add all three guards. For example: “Only when inspection has confirmed
789
+ an observed test-owned proxy left by this test, ask the user for explicit consent
790
+ before changing it. Save the original proxy settings, reset the proxy for the
791
+ test, and restore the original settings in cleanup even if the test fails.” Keep
792
+ the condition and guards together in each procedure paragraph. Retrieval checks
793
+ the current content, so the warning clears after a safe repair; there is no
794
+ blanket approval switch. Historical `host_state_review` telemetry keeps only
795
+ fixed categories and a SHA-256 fingerprint, without host values, paths, titles,
796
+ or command text.
797
+
700
798
  #### 1. Auto-review on close_thread
701
799
 
702
800
  When a closed thread is rich (≥5 notes, ≥2 insight/move),
@@ -847,9 +945,20 @@ children start, a deterministic validator writes
847
945
  ThreadKeeper/Claude Code/Codex/Agent Skills compatibility, resource/link
848
946
  findings, mirror hashes, exact-body duplicate groups, and lexical candidates
849
947
  for semantic review. System and installed-plugin sources are resolved from
850
- their read-only caches rather than misreported as missing mirrors; telemetry
851
- rows with no real `SKILL.md` remain explicit orphans. The same inventory also
852
- flags a dense lesson subtopic when at least
948
+ their read-only caches rather than misreported as missing mirrors. A telemetry
949
+ row with no readable primary source becomes `unavailable`, is non-compatible,
950
+ and shows only `restore_source` until validation sees a source again; repeated
951
+ validation does not create duplicate repair work. A scoped Curator verifier can
952
+ archive or remove a confirmed registry-only orphan only after rechecking every
953
+ configured root, the primary archive, and external cache and saving its usage row plus
954
+ audit evidence to recovery trash. The same inventory also
955
+ checks Markdown links and unambiguous slash-containing inline-code resource
956
+ paths for missing local files. It ignores URLs, absolute or escaping paths,
957
+ shell fragments, frontmatter, headings, and fenced examples. It also reports
958
+ only substantial repeated prose: normalized paragraphs of at least 160
959
+ characters that occur at least three times in the same skill. The same findings
960
+ are returned by `skill_validate`, before a Curator repair is mirrored. The
961
+ inventory also flags a dense lesson subtopic when at least
853
962
  `THREADKEEPER_CURATOR_PROMOTION_MIN_LESSONS` lessons (default 3) share two
854
963
  concrete title terms and a non-generic body mechanism. Generic joins and
855
964
  domain-wide/high-document-frequency title words cannot form a candidate. The
@@ -928,10 +1037,15 @@ have increased its patch counter. Patches are maintenance activity, not proof
928
1037
  that a foreground user or agent consulted the skill.
929
1038
 
930
1039
  Before spawning, the scheduler hashes lessons, concepts, skill bodies, support
931
- trees, validators, and mirror state. It then persists a pass manifest with the
932
- expected reports and the rendered text of every batch before launching any
933
- child. A dispatch is not completion: every batch must exit successfully and
934
- have a final, matching provenance record before the inventory is endorsed.
1040
+ trees, validators, and mirror state. It persists the full pass audit manifest
1041
+ for parent coordination and provenance, then writes one smaller, hash-checked
1042
+ audit slice for every frozen batch. A worker reads only its slice: assigned
1043
+ skill records and their relevant consumer, link, mirror, source, duplicate, and
1044
+ candidate findings. A lesson-only batch receives an explicit no-skills slice
1045
+ instead of the full skill inventory. The scheduler stores the expected reports,
1046
+ slice hashes, and rendered text before launching any child. A dispatch is not
1047
+ completion: every batch must exit successfully and have a final, matching
1048
+ provenance record before the inventory is endorsed.
935
1049
  Repeated manual calls over identical bytes return `unchanged_inventory` only
936
1050
  after that endorsement; scheduled passes still run because CLI behavior,
937
1051
  official guidance, and external alternatives can change without local file
@@ -995,7 +1109,26 @@ verifier) additionally lets the
995
1109
  evaluator make in-place repairs to them. Lessons are
996
1110
  stamped with an explicit `origin=<THREADKEEPER_WRITE_ORIGIN>` marker when
997
1111
  appended; missing, legacy, or unknown lesson provenance is protected by
998
- default. `lesson_remove` and `skill_manage(action='delete')` refuse protected
1112
+ default. Protection preserves authored content; it does not certify that every
1113
+ factual claim is current. `lesson_verifications(slug)` returns the current
1114
+ lesson revision and its factual checks. After reading an official source,
1115
+ Curator can call `curator_verify_lesson(slug, lesson_revision, claim, status,
1116
+ source_url, source_revision)` with an exact claim excerpt, `verified`,
1117
+ `contradicted`, or `unsubstantiated`, an official HTTPS URL, and its revision
1118
+ identifier or ETag. When no official revision is available, omit
1119
+ `source_revision` and pass the checked page text as `source_text`; the server
1120
+ hashes it without retaining the text. Use a consistent revision identifier or
1121
+ page text for each URL. The server records check time separately from lesson
1122
+ content. Repeated checks update the same claim record. `lesson_get` and
1123
+ `lesson_list(query=...)` show source-linked warnings for contradicted or
1124
+ unsubstantiated claims. A changed lesson or an observed source revision change
1125
+ makes old checks require a new audit; a verified recheck clears the warning.
1126
+ Sources are checked by the caller: these tools do not fetch pages, certify
1127
+ domains, or detect remote changes between audits. Unavailable research means
1128
+ defer the check, not declare the claim unsubstantiated. Content edits still
1129
+ follow the existing approval and verifier rules.
1130
+
1131
+ `lesson_remove` and `skill_manage(action='delete')` refuse protected
999
1132
  foreground/unknown-origin entries unless `force=True` is called from a
1000
1133
  foreground writer or the caller is the Curator verifier phase; curator/spawned
1001
1134
  children cannot elevate themselves with `force`. Before a destructive child is spawned, thread-keeper writes
@@ -1073,10 +1206,12 @@ embedding is closest to the prompt reached 25% recall at 12% precision.
1073
1206
 
1074
1207
  Lesson access is tracked the same way skill access is: `lesson_list` increments
1075
1208
  `lesson_usage.view_count` for displayed rows and `lesson_get` increments
1076
- `lesson_usage.use_count` for the returned lesson. Only foreground reads count:
1077
- the curator and other loops read every lesson they audit, and counting those
1078
- reads would make every lesson look recently useful. Curator dry runs include a
1079
- ranked `STALE LESSONS (dry-run decay ranking)` section computed as
1209
+ `lesson_usage.use_count` for the returned lesson. Foreground and ordinary task
1210
+ reads count as consultations. Curator evaluator and verifier reads are neutral
1211
+ only while the caller owns a live task with a server-issued Curator launch
1212
+ record. This also applies to retries and every batch of a pass. An origin label
1213
+ or a caller-supplied pass ID cannot suppress normal usage. Curator dry runs
1214
+ include a ranked `STALE LESSONS (dry-run decay ranking)` section computed as
1080
1215
  `access_frequency × exp(-days_since_access / tau)`, filtered to unprotected
1081
1216
  lessons with no recent access and low pull-count. That decay list is advisory
1082
1217
  only; it never becomes an automatic `lesson_remove` path by itself, and pinned
@@ -1170,27 +1305,44 @@ with that name instead of minting overlapping roadmap PRs.
1170
1305
 
1171
1306
  The Evolve applier is the downstream implementer. `evolve_apply_roadmap_issue()`
1172
1307
  picks one open GitHub issue at a time (`roadmap` label first, then FIFO), but
1173
- the automatic pass first sweeps the open PRs authored by the account
1174
- thread-keeper's `gh` acts as — yours: applier, reviewer, and hand-made PRs
1175
- alike. A PR from any other account (a collaborator, a bot, a fork) is never
1176
- updated, repaired, or merged. If the PR sweep itself cannot read GitHub state
1177
- or the `gh` account, the pass fails closed instead of taking fresh work blind.
1178
- The sweep squash-merges the oldest non-draft PR GitHub reports `CLEAN` (up to
1179
- date, required checks green) through the protected merge API, pinned to the
1180
- head SHA it saw green, and then asks GitHub to update the oldest `BEHIND` one —
1181
- the "Update branch" button — so CI reruns against the new base. Open
1182
- work in progress as a draft: drafts are never merged. Strict branch
1308
+ the automatic pass first sweeps eligible PRs from the account thread-keeper's
1309
+ `gh` acts as. A PR must be in this repository and have a recognized automation
1310
+ branch (`roadmap/issue-…`, `evolve/apply-…`, or `docs/roadmap-audit-…`) or a
1311
+ standalone ownership marker in its body. Applier PRs use
1312
+ `<!-- thread-keeper:evolve-applier-pr -->`; reviewer roadmap PRs use
1313
+ `<!-- thread-keeper:evolve-reviewer-roadmap-pr -->`. Manual and fork PRs, and
1314
+ PRs with missing or conflicting ownership signals, stay read-only. If the PR
1315
+ sweep itself cannot read GitHub state or the `gh` account, the pass fails
1316
+ closed instead of taking fresh work blind.
1317
+
1318
+ The sweep squash-merges the oldest eligible non-draft PR GitHub reports
1319
+ `CLEAN` (up to date, required checks green) through the protected merge API,
1320
+ pinned to the head SHA it saw green, and then asks the oldest eligible `BEHIND`
1321
+ PR to update its branch so CI reruns against the new base. Strict branch
1183
1322
  protection lets only one PR merge per base commit, so PRs land one at a time,
1184
1323
  oldest first. Between full passes the daemon repeats only this cheap landing
1185
- sweep every 15 minutes, so a green PR lands minutes after its checks finish;
1186
- it skips while any reviewer/applier git writer runs. A conflicted applier PR
1187
- (`roadmap/…` or `evolve/…` head branch in this repository) is repaired before
1188
- any new issue/report/evolve work is started: the conflict-repair child checks
1189
- out the existing PR branch, merges the current base branch, resolves
1190
- conflicts, runs the full suite, and pushes back to the same branch, then
1191
- stops; the landing sweep merges it once it is green. A hand-made PR with
1192
- conflicts, and red or otherwise blocked PRs, wait for a human. Set
1193
- `THREADKEEPER_EVOLVE_AUTOLAND=0` to leave every merge to a human.
1324
+ sweep every 15 minutes, so a green PR lands minutes after its checks finish; it
1325
+ skips while any reviewer/applier git writer runs.
1326
+
1327
+ The applier keeps one applier PR (`roadmap/…` or `evolve/…` head branch in
1328
+ this repository) in flight at a time. While any is open it starts no new
1329
+ issue, Curator report, or evolve suggestion, and works on the oldest one until
1330
+ it lands. A conflicted PR gets a conflict-repair child that checks out the
1331
+ existing PR branch, merges the current base branch, resolves conflicts, runs
1332
+ the full suite, and pushes back to the same branch. A PR whose finished checks
1333
+ include a red one is first updated onto the newest base, which clears failures
1334
+ already fixed there. If it stays red while up to date, a fix child reads the
1335
+ failing logs, fixes the cause on the same branch (or re-runs a job that failed
1336
+ outside the code), runs the full suite, and pushes. A green applier draft is
1337
+ marked ready. Children never merge: the landing sweep lands the PR once it is
1338
+ green. When that PR needs a child, or the last open applier PR has just
1339
+ landed, the 15-minute sweep runs the full pass right away. After three repair
1340
+ or fix children one PR waits for a human and the next open applier PR moves
1341
+ ahead. When every open applier PR waits for a human, the pass records
1342
+ `ERR applier_prs_need_human=…`, which the notifier reports as a failure. A
1343
+ hand-made PR with conflicts or red checks waits for its author and does not
1344
+ hold back new work. Set `THREADKEEPER_EVOLVE_AUTOLAND=0` to leave every merge
1345
+ to a human.
1194
1346
  The roadmap issue child skips issues carrying denylisted human-gate labels,
1195
1347
  skips issues with an active Evolve claim comment, posts its own claim comment
1196
1348
  before spawning, and advances to the next issue when an issue-local dispatch
@@ -1342,9 +1494,9 @@ the prior cursor and retries on a bounded short cadence; repeated identical
1342
1494
  blockers are recorded once until the blocker changes. `evolve_apply_status()`
1343
1495
  shows the last attempt, last successful dispatch, and any pending retry time.
1344
1496
  Manual tools such as `evolve_apply_conflicted_pr()` and
1345
- `evolve_apply_roadmap_issue()` dispatch immediately. If no conflicted applier PR
1346
- or roadmap issue is startable, the pass falls back to Curator reports and then
1347
- legacy promoted `evolve_format(...)` suggestions.
1497
+ `evolve_apply_roadmap_issue()` dispatch immediately. If no applier PR is open
1498
+ and no roadmap issue is startable, the pass falls back to Curator reports and
1499
+ then legacy promoted `evolve_format(...)` suggestions.
1348
1500
 
1349
1501
  #### Honest take
1350
1502
 
@@ -1428,9 +1580,7 @@ after an update shows a one-time permission prompt.
1428
1580
  `agent_status` feeds the app two lists — `recent_results` (named skill/lesson
1429
1581
  write events plus completed-task history) and `recent_failures` (the two failure
1430
1582
  sources above) — each item tagged with a `notify` flag computed from the toggles
1431
- below. The app **lists**
1432
- every item in its menu but only **posts a banner** for flagged ones, so turning a
1433
- category off silences the banner without hiding the history. Each materialization
1583
+ below. The app only posts a banner for flagged items. Each materialization
1434
1584
  uses its own skill or lesson toggle; generic task reports are history-only.
1435
1585
  Foreground writes appear too, without waiting for a background child to finish.
1436
1586
  A pathless skill mark only silences its reminder and has no materialization
@@ -1440,6 +1590,26 @@ values and private home paths are redacted before they reach the MCP status
1440
1590
  response, menu, or notification; raw logs remain available only through
1441
1591
  `task_logs`.
1442
1592
 
1593
+ **Reading a hidden or truncated banner.** The menu-bar popover's **Notification
1594
+ history** keeps the last 200 notification attempts, with the full text supplied
1595
+ to macOS and **Copy details** in each item's context menu. The app also imports
1596
+ its own delivered notifications still retained by Notification Center, so an
1597
+ older banner can be recovered even if previews are hidden. This local history
1598
+ survives app restarts and category toggles; it does not replay notifications or
1599
+ change macOS privacy settings. For diagnostics, the installed app prints the
1600
+ same history as JSON:
1601
+
1602
+ ```bash
1603
+ ~/Applications/ThreadKeeperAgentStatus.app/Contents/MacOS/ThreadKeeperAgentStatus --notification-history
1604
+ ```
1605
+
1606
+ The daemon also stores each sanitized title/body as a node-local `notification`
1607
+ event before dispatch, including its source event or task, regardless of the
1608
+ selected channels. `notify_pass` remains the aggregate cursor/counter record.
1609
+ These records describe notification attempts, not proof that macOS displayed a
1610
+ banner. Older dismissed notifications without a saved payload cannot be
1611
+ reconstructed from counters alone.
1612
+
1443
1613
  **Settings in the app.** The menu-bar app's **Settings ▸ Notifications** tab
1444
1614
  edits these same `THREADKEEPER_NOTIFY_*` keys visually — switches for the
1445
1615
  on/off categories, a picker for the interval, channel, and cooldown — and writes
@@ -1537,6 +1707,7 @@ the host after changing it:
1537
1707
  | `coordination` | `core` plus child spawning, task status, pickup, and delegated search |
1538
1708
  | `memory-admin` | `core` plus durable memory, concepts, lessons, graph links, and explicit erasure |
1539
1709
  | `learning-loops` | `core` plus extraction, validation, skills, Curator, probes, and dialectic workflows |
1710
+ | `model-router` | only the pass-authorized `model_route_write` tool |
1540
1711
  | `full` | every registered tool; the compatibility default when no profile is configured |
1541
1712
 
1542
1713
  New `thread-keeper-setup` registrations use `core`. Existing server entries
@@ -1547,7 +1718,9 @@ server rather than exposing an unintended surface.
1547
1718
 
1548
1719
  Spawned children default to `core`; archival, Curator, candidate-review,
1549
1720
  shadow-review, probe-runner, and dialectic-validator roles use
1550
- `learning-loops`, while Evolve roles request `full`. The server receives the
1721
+ `learning-loops`, model-router children use `model-router`, and Evolve roles
1722
+ request `full`. A router refuses to spawn if its parent cannot expose the
1723
+ route-write tool. The server receives the
1551
1724
  parent’s resolved allowlist and intersects it with the child profile, so a
1552
1725
  child cannot regain a tool its parent did not expose.
1553
1726
  `THREADKEEPER_CHILD_TOOL_PROFILE` explicitly selects a different child profile,
@@ -1560,7 +1733,7 @@ use it as the fallback on hosts that lack native deferred tool discovery.
1560
1733
 
1561
1734
  | Knob | Default | Purpose |
1562
1735
  |---|---|---|
1563
- | `THREADKEEPER_TOOL_PROFILE` | `full` | MCP registry profile: `core`, `coordination`, `memory-admin`, `learning-loops`, or `full`; read at server start |
1736
+ | `THREADKEEPER_TOOL_PROFILE` | `full` | MCP registry profile: `core`, `coordination`, `memory-admin`, `learning-loops`, `model-router`, or `full`; read at server start |
1564
1737
  | `THREADKEEPER_TOOL_INCLUDE` | "" | exact comma-separated tool names to add to the selected profile; unknown names fail closed |
1565
1738
  | `THREADKEEPER_TOOL_EXCLUDE` | "" | exact comma-separated tool names to remove from the selected profile; unknown or overlapping names fail closed |
1566
1739
  | `THREADKEEPER_CHILD_TOOL_PROFILE` | role default | spawned-child profile override; always intersected with the parent’s resolved tool surface |
@@ -1668,7 +1841,7 @@ use it as the fallback on hosts that lack native deferred tool discovery.
1668
1841
  | `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 |
1669
1842
  | `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 |
1670
1843
  | `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 |
1671
- | `THREADKEEPER_EVOLVE_AUTOLAND` | true | land open non-draft PRs authored by the account `gh` acts as: squash-merge one once GitHub reports it up to date with required checks green, and update one that fell behind the base. Other accounts' PRs are never touched. `0`/`false` leaves every merge to a human |
1844
+ | `THREADKEEPER_EVOLVE_AUTOLAND` | true | land open non-draft PRs authored by the account `gh` acts as: squash-merge one once GitHub reports it up to date with required checks green, and update one that fell behind the base. Applier PRs also get a green draft marked ready, and red checks retried on the newest base before a fix child starts. Other accounts' PRs are never touched. `0`/`false` leaves every merge to a human |
1672
1845
  | `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 |
1673
1846
  | `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 |
1674
1847
  | `THREADKEEPER_EVOLVE_REPO_URL` | upstream repo | HTTPS `github.com` source for the managed clone; restart-only, and other hosts/schemes are refused |
@@ -1684,7 +1857,7 @@ use it as the fallback on hosts that lack native deferred tool discovery.
1684
1857
  | `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 |
1685
1858
  | `THREADKEEPER_DIALECTIC_MAX_NEW_CLAIMS` | 3 | max new dialectic claims the validator may create per pass |
1686
1859
  | `THREADKEEPER_DIALECTIC_OBS_MAX_REQUEUES` | 3 | requeues (validator child exited without resolving) before an observation is terminally skipped as poison; 0 = uncapped |
1687
- | `THREADKEEPER_DAEMON_HOST` | `1` (on) | One headless host (`python -m threadkeeper.host`) owns the background loops + the warm embedding model + the embed socket, and per-session servers go thin (no daemons, no ONNX). `0` reverts to the legacy mode where every foreground MCP server runs its own daemon threads. See [Embeddings](#embeddings) below |
1860
+ | `THREADKEEPER_DAEMON_HOST` | `1` (on) | One headless host (`python -m threadkeeper.host`) owns the background loops + the warm embedding model + the embed socket, and per-session servers go thin (no daemons, no ONNX). `0` reverts to the legacy mode where every foreground MCP server runs its own daemon threads. No host is spawned for a `THREADKEEPER_DB` under the system temp directory or `/tmp` (test and eval runs). See [Embeddings](#embeddings) below |
1688
1861
  | `THREADKEEPER_ROLE` | `server` | process role: `server` (default; per-session MCP server) or `host`. Set to `host` only by `python -m threadkeeper.host` — do not set this by hand |
1689
1862
  | `THREADKEEPER_HOST_SOCK` | (auto) | embed-only unix socket the thin servers dial and the host binds; empty resolves to `<db dir>/host.sock` |
1690
1863
  | `THREADKEEPER_HOST_HEARTBEAT_TTL_S` | 120 | host liveness window (s): how stale the host's presence heartbeat may get before `memory_guard`/a thin server treats it as dead and spawns a replacement |
@@ -1759,16 +1932,50 @@ model and effort:
1759
1932
  THREADKEEPER_SPAWN__LOOP__SHADOW_OBSERVER=auto
1760
1933
  ```
1761
1934
 
1762
- The router reviews the last 30 days of that role's spawns (success rate,
1763
- failures such as provider capacity errors, run time, tokens, recent outcomes)
1764
- plus the local model catalog, and records one decision per `auto` role with a
1765
- reason. Until it has decided, `auto` keeps its old meaning and follows the
1935
+ The router compares models and effort for each role using the results of its
1936
+ previous tasks: task effectiveness, cost, and efficiency. Its last-30-day
1937
+ digest includes task instructions and final answers from saved transcripts,
1938
+ with structured CLI results or log summaries as a fallback. Samples cover
1939
+ different model/effort combinations and include failed executions. Time,
1940
+ tokens, recorded USD costs and missing-cost coverage accompany the results;
1941
+ subscription token use is a quota proxy, not an invented dollar price.
1942
+
1943
+ Before each decision pass, the parent fetches official OpenAI API and Codex
1944
+ credit rates, Anthropic API rates and Claude alias rules, GitHub Copilot rates,
1945
+ and Antigravity quota/overage rules for available CLIs. Rate cards are cached
1946
+ for 24 hours in `model_pricing.json` beside the database, with source URLs and
1947
+ fetch times. Network or document-layout failures retain the last good card
1948
+ as **stale**; absent rates remain unknown. Published API USD rates, Codex
1949
+ credits, GitHub AI credits and subscription quotas keep their own billing
1950
+ scope. The router uses observed input/output tokens, duration and results to
1951
+ compare effort; effort has no fixed invented price multiplier. Estimates
1952
+ state missing cache/reasoning or alias details and never replace recorded spend.
1953
+ Completed router executions also supply dated CLI/model/effort access evidence,
1954
+ so an older startup rejection is not treated as unresolved after a later success.
1955
+ Access evidence does not establish quality on other roles' tasks.
1956
+ Recent failed router attempts also carry their parsed error text, including
1957
+ temporary quota limits, separately from evidence of task quality.
1958
+
1959
+ Every pass reassesses alternatives, even when the current model runs without
1960
+ errors. Exit code zero does not prove task success, and missing results leave
1961
+ quality unknown. Each decision explains the task-specific tradeoff and its
1962
+ uncertainty. Until it has decided, `auto` keeps its old meaning and follows the
1766
1963
  active CLI. While a role is routed, its `SPAWN__MODEL__<ROLE>` and
1767
1964
  `SPAWN__EFFORT__<ROLE>` pins are ignored and `spawn_status()` warns about them.
1768
1965
 
1769
1966
  The router runs on `claude` / `opus` / `max` (override with the usual
1770
1967
  `SPAWN__LOOP|MODEL|EFFORT__MODEL_ROUTER` keys). If that run fails, the same pass
1771
1968
  reruns on the fallback below; a pass that fails twice retries after 6 hours.
1969
+ Due-pass checks run hourly; unfinished passes reconcile every 30 seconds so
1970
+ a finished or failed child does not wait an hour for its next state transition.
1971
+ It decides from the prepared evidence and prices, rather than starting new
1972
+ research. Claude router children expose only native `ToolSearch` plus the MCP
1973
+ route writer. Captured headless Claude launches stream intermediate events
1974
+ to the task log so an active analysis is distinguishable from a stalled run.
1975
+ If the CLI rejects a model shown in the shared model cache, check
1976
+ `codex --version` and update the CLI before changing model pins. The desktop
1977
+ app and the CLI on `PATH` can use different releases. Verify access with a
1978
+ short call through the CLI that thread-keeper actually launches.
1772
1979
  `model_router_status()` shows current decisions and `model_router_run()` runs a
1773
1980
  pass now. Set `THREADKEEPER_MODEL_ROUTER_INTERVAL_S=0` to disable the router.
1774
1981
 
@@ -1792,6 +1999,17 @@ Three tools keep the memory tidy. `consolidate()` and `forget()` default to
1792
1999
  `THREADKEEPER_TASK_RETENTION_DAYS` defaults to `30` and
1793
2000
  `THREADKEEPER_TASK_RETENTION_COUNT` defaults to `1000`; a row is kept if it
1794
2001
  is protected by either bound. Set either knob to `0` to disable that bound.
2002
+ With both bounds disabled, finished task rows and their checkouts are kept.
2003
+ Expired tasks also release their owned Git worktrees and temporary branches
2004
+ when the checkout is clean and its commits remain reachable from another
2005
+ local branch or a remote-tracking branch. Other temporary task branches do
2006
+ not count as a safe copy. Dry run reports checkout counts, bytes, and reasons
2007
+ for keeping work. Cleanup preserves live tasks, changed or untracked files
2008
+ (including ignored files), unpublished work, locked checkouts, changed
2009
+ ownership, and branches checked out elsewhere. It never forces Git removal.
2010
+ Commits saved only in the task's branch or checkout history are also kept,
2011
+ including commits left behind by a reset or branch switch.
2012
+ Ownership records survive task-history pruning, so a later cleanup can retry.
1795
2013
  - **`forget(selector, selector_type="auto", dry_run=True)`** — targeted
1796
2014
  privacy erasure for one session/cid/thread/dialog UUID. Dry-run reports the
1797
2015
  rows that would be removed from `dialog_messages`, FTS/vector sidecars,
@@ -1835,6 +2053,22 @@ private child environment (and its slim MCP config), never in a prompt or CLI
1835
2053
  argument. A watchdog continuation uses the prior agent span as its parent, so
1836
2054
  retries remain in the same trace.
1837
2055
 
2056
+ For MCP `2026-07-28`, a host can add W3C `traceparent`, `tracestate`, and
2057
+ `baggage` values to the request `_meta`. A valid `traceparent` becomes the
2058
+ parent of the MCP operation, so the host and child work appear in one trace.
2059
+ Older MCP clients keep the existing local behavior. A missing or malformed
2060
+ host traceparent starts a local trace and does not fail the tool call.
2061
+
2062
+ ThreadKeeper accepts only the exact lowercase W3C `00` traceparent form. It
2063
+ keeps valid tracestate only when it has at most 32 entries and 512 characters,
2064
+ with W3C key and value characters. Baggage is more restrictive: it is capped
2065
+ at 256 characters and only forwards `deployment.environment`,
2066
+ `service.namespace`, and `threadkeeper.workflow_class` values made of up to 64
2067
+ safe identifier characters. All other baggage is dropped. These three values
2068
+ stay in private task tracing fields and child environment variables; they never appear
2069
+ in prompts, command arguments, memory, tool results, or exported span
2070
+ attributes.
2071
+
1838
2072
  The exporter has a fixed allowlist: operation/tool identifier, latency and
1839
2073
  queue wait, provider/model identifiers, input/output/total tokens, USD cost,
1840
2074
  retry count, and terminal outcome. Operation names are fixed; tool/provider
@@ -1961,6 +2195,56 @@ first and copy `db.sqlite`, `db.sqlite-wal`, and `db.sqlite-shm` together. To
1961
2195
  wipe memory, also stop thread-keeper first, then remove the main DB and both
1962
2196
  sidecars.
1963
2197
 
2198
+ For a selective, human-inspectable migration or audit, use
2199
+ `tk-semantic-bundle`. It writes a versioned `manifest.json` and canonical
2200
+ `records.jsonl`; every record has a SHA-256 content hash, source namespace and
2201
+ ID, provenance/authority metadata, temporal fields, and deletion or
2202
+ invalidation state. It is not a disaster-recovery snapshot and does not
2203
+ replace active-active sync:
2204
+
2205
+ | Need | Use |
2206
+ | --- | --- |
2207
+ | Recover an exact local store after loss | `tk-backup` |
2208
+ | Keep live, compatible instances converged | instance sync |
2209
+ | Move or inspect selected semantic memory | `tk-semantic-bundle` |
2210
+
2211
+ ```bash
2212
+ # Default: semantic threads, notes, concepts, distillations, lessons, skills,
2213
+ # and graph links. Raw dialog, task logs, and personal memory stay out.
2214
+ tk-semantic-bundle export ./memory.bundle --namespace laptop-2026
2215
+
2216
+ # Opt in to each sensitive class independently when it is needed.
2217
+ tk-semantic-bundle export ./complete.bundle --namespace laptop-2026 \
2218
+ --include-personal-memory --include-dialog --include-task-logs
2219
+
2220
+ # Import is a dry run unless --apply is explicit.
2221
+ tk-semantic-bundle import ./memory.bundle
2222
+ tk-semantic-bundle import ./memory.bundle --apply
2223
+ ```
2224
+
2225
+ An import first validates the manifest schema and every hash, then reports
2226
+ additions, conflicts, invalid records, and its namespace choice without
2227
+ writing. Applied imports retain source IDs in the local
2228
+ `semantic_bundle_import_records` namespace ledger; an identical re-import is
2229
+ idempotent, while changed source content reports a deterministic conflict.
2230
+ The format has a documented crosswalk to the evolving
2231
+ [W3C AI Agent Memory Interoperability work](https://www.w3.org/groups/cg/ai-agent-memory-interop/)
2232
+ and [IETF AIMEM bundle draft](https://www.ietf.org/archive/id/draft-vu-aimem-bundle-00.html),
2233
+ but does not claim conformance to either draft. The crosswalk is deliberately
2234
+ conceptual, not a wire-format mapping:
2235
+
2236
+ | ThreadKeeper bundle v1 | External direction | Deliberate difference |
2237
+ | --- | --- | --- |
2238
+ | versioned manifest + canonical JSONL | AIMEM envelope and versioning | Directory/JSONL rather than the draft's one JSON object |
2239
+ | `source.namespace` + source-ID ledger | AIMEM producer namespace and replay identity | Local namespace strings are not AIMEM URNs |
2240
+ | record and stream SHA-256 hashes | AIMEM checksum/content-hash concepts | No signature or authenticity claim |
2241
+ | typed records and graph links | AIMEM chunks and edges | ThreadKeeper types and relations remain local |
2242
+ | egress class, authority, valid time, tombstone | W3C work on memory metadata, sharing, and revocation | Local policy metadata; not an external profile |
2243
+
2244
+ The IETF document is an Internet-Draft and the W3C group is still developing
2245
+ its protocol, so compatibility must be reviewed again before publishing an
2246
+ external-profile claim.
2247
+
1964
2248
  ### Opt-in encrypted storage (experimental)
1965
2249
 
1966
2250
  The default `sqlite` profile is unchanged. For sensitive memory at rest, the
@@ -2104,6 +2388,18 @@ and visible `.command` files with no-follow owner-only opens. Git-backed spawns
2104
2388
  also create their isolated worktrees there. `consolidate()` garbage-collects
2105
2389
  task spool files once their task row is no longer retained.
2106
2390
 
2391
+ Task worktrees use the same retention bounds. Before removing history,
2392
+ `consolidate()` saves cleanup eligibility in a separate ownership record.
2393
+ It uses `git worktree remove` and deletes only the recorded temporary branch,
2394
+ after checking that its tip is retained elsewhere. Failed or interrupted cleanup
2395
+ keeps that record; rerun the dry run after saving work on another branch,
2396
+ publishing commits, or resolving the reported block. Apply cleanup only after
2397
+ reviewing that report. A changed branch or checkout must be reviewed manually.
2398
+ Older checkouts without ownership records are reported as `ownership_unknown`
2399
+ and kept; their task rows also stay available for manual review. Verify their
2400
+ Git status and saved commits before removing them
2401
+ manually with Git; directory names alone never establish ownership.
2402
+
2107
2403
  ---
2108
2404
 
2109
2405
  ## Embeddings
@@ -2464,9 +2760,17 @@ write is `dialectic_supersede`.
2464
2760
  era. Legacy hosts retain the existing tools, resources, and prompts. Modern
2465
2761
  calls derive identity and capabilities from every request, static discovery
2466
2762
  catalogs advertise public cache freshness, and replaying a completed write with
2467
- the same caller-bound JSON-RPC id returns the original result instead of
2468
- committing a second mutation. Sealed continuation state expires after five
2469
- minutes; an interrupted write with no terminal result is rejected on replay.
2763
+ the same caller-bound JSON-RPC id within one stdio connection returns the
2764
+ original result instead of committing a second mutation. Independent CLI
2765
+ connections have separate replay namespaces even when their client metadata
2766
+ and numeric IDs match; numeric and string IDs are distinct. Sealed continuation
2767
+ state expires after five minutes; an interrupted write with no terminal result
2768
+ is rejected on replay within that connection. Durable workflow retries use
2769
+ their application handles, such as a router pass ID.
2770
+ The release gate also runs the pinned official MCP conformance requirement sets
2771
+ for both versions against a loopback-only Streamable HTTP view of this same
2772
+ registry. Its version-specific expected-failure baselines are checked for both
2773
+ new failures and stale entries; stdio keeps the ThreadKeeper-specific coverage.
2470
2774
 
2471
2775
  Detailed map in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
2472
2776
  Open work in [docs/ROADMAP.md](docs/ROADMAP.md) and the