threadkeeper 0.19.0__tar.gz → 0.20.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.
- {threadkeeper-0.19.0/threadkeeper.egg-info → threadkeeper-0.20.0}/PKG-INFO +463 -89
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/README.md +458 -87
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/pyproject.toml +13 -5
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_agent_status.py +36 -17
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_config_settings.py +3 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_curator.py +674 -22
- threadkeeper-0.20.0/tests/test_dashboard_app.py +143 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_db_schema.py +85 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_egress_policy.py +10 -3
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_evolve_applier.py +632 -13
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_evolve_daemon.py +163 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_host_env.py +38 -1
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_identity.py +2 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_ingest_denylist.py +1 -1
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_lessons.py +596 -22
- threadkeeper-0.20.0/tests/test_mcp_protocol_conformance.py +235 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_mcp_resources_prompts.py +160 -0
- threadkeeper-0.20.0/tests/test_mcp_server_instructions.py +139 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_mcp_skills_extension.py +5 -1
- threadkeeper-0.20.0/tests/test_mcp_tasks.py +158 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_memory_eval.py +84 -4
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_menubar_app.py +1 -1
- threadkeeper-0.20.0/tests/test_model_router.py +244 -0
- threadkeeper-0.20.0/tests/test_model_router_digest.py +107 -0
- threadkeeper-0.20.0/tests/test_model_router_tools.py +194 -0
- threadkeeper-0.20.0/tests/test_model_routes.py +183 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_nudges.py +4 -7
- threadkeeper-0.20.0/tests/test_project_scope.py +113 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_release_workflows.py +18 -4
- threadkeeper-0.20.0/tests/test_session_read_purity.py +104 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_setup.py +1 -1
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_shadow_review.py +6 -1
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_skill_hint.py +4 -5
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_skill_tier.py +137 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_skill_updater.py +69 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_skill_use_parser.py +33 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_skills.py +160 -37
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_spawn_config.py +193 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_spawn_hint.py +9 -16
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_spawn_watchdog.py +32 -0
- threadkeeper-0.20.0/tests/test_storage.py +256 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_tool_annotations.py +56 -3
- threadkeeper-0.20.0/tests/test_tool_profiles.py +170 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_tools_smoke.py +1 -0
- threadkeeper-0.20.0/tests/test_tracing.py +237 -0
- threadkeeper-0.20.0/tests/test_workflow_retention.py +255 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/_mcp.py +115 -3
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/_setup.py +7 -55
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/_spawn_wrap.py +23 -4
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/adapters/base.py +6 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/adapters/codex.py +1 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/agent_metadata.py +15 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/agent_status.py +136 -32
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/assets/macos-agent-status/README.md +5 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/assets/macos-agent-status/ThreadKeeperAgentStatus.swift +49 -13
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/backup.py +37 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/brief.py +50 -41
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/candidate_reviewer.py +9 -6
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/config.py +73 -9
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/curator.py +1017 -326
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/curator_snapshots.py +137 -3
- threadkeeper-0.20.0/threadkeeper/dashboard_app.py +411 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/db.py +387 -38
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/dialectic_miner.py +10 -5
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/dialectic_validator.py +4 -2
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/elicitation.py +3 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/embeddings.py +12 -4
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/evolve_applier.py +593 -82
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/evolve_daemon.py +60 -27
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/forget.py +13 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/github_budget.py +9 -1
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/host.py +42 -17
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/identity.py +13 -3
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/ingest.py +42 -5
- threadkeeper-0.20.0/threadkeeper/instructions.py +80 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/lessons.py +130 -6
- threadkeeper-0.20.0/threadkeeper/mcp_tasks.py +327 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/migrate_embeddings.py +3 -2
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/model_catalog.py +9 -0
- threadkeeper-0.20.0/threadkeeper/model_router.py +373 -0
- threadkeeper-0.20.0/threadkeeper/model_router_digest.py +234 -0
- threadkeeper-0.20.0/threadkeeper/model_routes.py +318 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/notify.py +1 -0
- threadkeeper-0.20.0/threadkeeper/project_scope.py +204 -0
- threadkeeper-0.20.0/threadkeeper/protocol.py +186 -0
- threadkeeper-0.20.0/threadkeeper/read_context.py +24 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/retention.py +5 -1
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/retrieval.py +34 -8
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/server.py +11 -1
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/shadow_review.py +4 -1
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/skill_audit.py +4 -1
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/skill_updater.py +41 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/spawn_budget.py +32 -23
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/spawn_config.py +166 -45
- threadkeeper-0.20.0/threadkeeper/storage.py +740 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/sync/identity.py +6 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/sync/migrate.py +13 -13
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/sync/protocol.py +3 -1
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/sync/reset_node.py +5 -2
- threadkeeper-0.20.0/threadkeeper/tool_profiles.py +364 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tool_schemas.py +1 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/agent_status.py +15 -2
- threadkeeper-0.20.0/threadkeeper/tools/capabilities.py +15 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/concepts.py +25 -10
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/core_memory.py +43 -16
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/curator.py +41 -10
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/dashboard.py +7 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/db_maintenance.py +19 -1
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/dialectic.py +39 -13
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/dialog.py +24 -7
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/distill.py +26 -10
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/evolve_applier.py +53 -21
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/extract.py +44 -25
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/lessons.py +577 -256
- threadkeeper-0.20.0/threadkeeper/tools/model_router.py +152 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/pickup.py +4 -3
- threadkeeper-0.20.0/threadkeeper/tools/project_scope.py +33 -0
- threadkeeper-0.20.0/threadkeeper/tools/resources.py +460 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/session.py +8 -3
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/shadow_review.py +28 -11
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/skills.py +139 -42
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/spawn.py +83 -9
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/style.py +14 -2
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/sync.py +2 -2
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/threads.py +60 -22
- threadkeeper-0.20.0/threadkeeper/tracing.py +572 -0
- threadkeeper-0.20.0/threadkeeper/workflow_artifacts.py +575 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0/threadkeeper.egg-info}/PKG-INFO +463 -89
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper.egg-info/SOURCES.txt +29 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper.egg-info/entry_points.txt +1 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper.egg-info/requires.txt +5 -1
- threadkeeper-0.19.0/tests/test_session_read_purity.py +0 -51
- threadkeeper-0.19.0/threadkeeper/tools/resources.py +0 -100
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/LICENSE +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/setup.cfg +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_adapters.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_antigravity_ingest.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_auto_update.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_backup.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_brief_footprint.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_brief_sections.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_candidate_reviewer.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_ci_parallel.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_concepts.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_config_watcher.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_core_memory.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_daemon_host_config.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_daemon_host_integration.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_daemon_liveness.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_daemon_state.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_dashboard.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_db_compact.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_db_contention.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_db_leak_guard.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_db_migration_fts.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_db_runtime.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_delegated_search.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_dialectic.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_dialectic_feed_tools.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_dialectic_miner.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_dialectic_observation_resolve.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_dialectic_recompute.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_dialectic_tier.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_dialectic_validator.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_dialog_fts_external.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_documentation_inventory.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_embed_routing.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_embedding_generation.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_embedding_model_revision.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_embedding_storage_dedup.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_error_paths.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_eval_harness.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_evolve_apply_2.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_evolve_apply_3.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_evolve_apply_43.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_evolve_apply_45.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_extract_daemon.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_extract_dedup.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_forget.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_github_budget.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_github_safety.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_helpers.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_hook_child_guard.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_host_embed.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_host_main.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_host_wedged.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_hybrid_retrieval.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_i18n_multilang.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_ingest_cursor.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_ingest_secret_redaction.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_ingest_status.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_ingest_transactions.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_lesson_violations.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_mcp_sdk_compat.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_memory_authority_lifecycle.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_memory_compatibility.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_memory_guard.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_memory_guard_host.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_missed_spawns.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_model_catalog.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_notify_daemon.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_onnx_embeddings.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_panel.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_permissions.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_pickup.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_probe_daemon.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_process_health.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_retention.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_review_prompts.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_search_fts_punctuation.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_single_flight_prompt_prefixes.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_skill_passive_tier.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_skill_watcher.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_spawn_background_cwd.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_spawn_budget.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_spawn_child_env.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_spawn_codex_stdin.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_spawn_permission_gate.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_spawn_reap.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_spawn_result.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_spawn_slim.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_spawn_worktree_isolation.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_spawn_wrap.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_sync_capture.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_sync_identity.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_sync_migrate.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_sync_protocol.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_sync_reset_node.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_sync_transport.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_task_retention.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_task_spool.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_thin_session.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_thread_janitor.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_threads.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_validate_threads.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_vec_search.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/tests/test_verify_ingest.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/__init__.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/adapters/__init__.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/adapters/_hook_helpers.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/adapters/antigravity.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/adapters/claude_code.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/adapters/claude_desktop.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/adapters/copilot.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/adapters/vscode.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/assets/macos-agent-status/Info.plist +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/assets/macos-agent-status/build.sh +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/assets/macos-agent-status/install.sh +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/authority.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/auto_update.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/config_io.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/config_watcher.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/daemon_liveness.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/daemon_state.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/daemon_supervisor.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/egress.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/eval/__init__.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/eval/__main__.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/eval/harness.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/extract_daemon.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/github_safety.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/harvest.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/helpers.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/host_embed.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/i18n.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/lesson_violations.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/link_health.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/mcp_skills.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/memory_compat.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/memory_guard.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/menubar_app.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/nudges.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/permissions.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/probe_daemon.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/process_health.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/review_prompts.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/search_proxy.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/skill_watcher.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/spawn_result.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/sync/__init__.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/sync/capture.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/sync/daemon.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/sync/server.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/task_spool.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/thread_janitor.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/__init__.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/authority.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/candidate_reviewer.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/config_watch.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/consolidate.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/correlation.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/dialectic_feed.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/evolve_research.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/forget.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/graph.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/invariants.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/memory_guard.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/missed_spawns.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/panel.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/peers.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/probes.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/process_health.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/prompts.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/tools/validate.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/trash.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/verify_ingest.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper/wikilinks.py +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.0}/threadkeeper.egg-info/dependency_links.txt +0 -0
- {threadkeeper-0.19.0 → threadkeeper-0.20.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.
|
|
3
|
+
Version: 0.20.0
|
|
4
4
|
Summary: Multi-agent shared brain across Claude Code/Desktop, Codex, Antigravity CLI, Copilot, and VS Code. Cross-session memory, self-improving skill loops, inter-agent signaling — one local MCP server.
|
|
5
5
|
Author: thread-keeper contributors
|
|
6
6
|
License: MIT
|
|
@@ -22,7 +22,7 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
|
22
22
|
Requires-Python: >=3.11
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
License-File: LICENSE
|
|
25
|
-
Requires-Dist: mcp<3,>=2.
|
|
25
|
+
Requires-Dist: mcp<3,>=2.3.0
|
|
26
26
|
Requires-Dist: pydantic>=2.13.4
|
|
27
27
|
Requires-Dist: pydantic-settings>=2.14.2
|
|
28
28
|
Requires-Dist: pyyaml>=6.0.3
|
|
@@ -35,6 +35,9 @@ Provides-Extra: semantic-st
|
|
|
35
35
|
Requires-Dist: sentence-transformers>=2.2.0; extra == "semantic-st"
|
|
36
36
|
Requires-Dist: numpy>=1.24.0; extra == "semantic-st"
|
|
37
37
|
Requires-Dist: sqlite-vec>=0.1.9; extra == "semantic-st"
|
|
38
|
+
Provides-Extra: encrypted
|
|
39
|
+
Requires-Dist: keyring>=25.0; extra == "encrypted"
|
|
40
|
+
Requires-Dist: sqlcipher3-wheels>=0.5.7; extra == "encrypted"
|
|
38
41
|
Provides-Extra: dev
|
|
39
42
|
Requires-Dist: pytest>=9.1.1; extra == "dev"
|
|
40
43
|
Requires-Dist: pytest-cov>=7.1.0; extra == "dev"
|
|
@@ -150,6 +153,12 @@ managed instructions block and call `brief()` / `context()` before answering, or
|
|
|
150
153
|
`memory://brief` resource the host attaches automatically (see
|
|
151
154
|
[MCP primitives](#mcp-primitives-tools-resources-prompts)).
|
|
152
155
|
|
|
156
|
+
The MCP initialization response also carries concise, static lifecycle guidance:
|
|
157
|
+
load the brief/context, open/note/close substantive threads, prefer read-only
|
|
158
|
+
resources for host-attached context, and use dry-run plus confirmation before
|
|
159
|
+
destructive maintenance. Hooks and the managed files remain reinforcement for
|
|
160
|
+
clients that do not surface server instructions.
|
|
161
|
+
|
|
153
162
|
### Alternative installs
|
|
154
163
|
|
|
155
164
|
If you don't have `pipx` and don't want to install it:
|
|
@@ -219,12 +228,12 @@ the `CLIAdapter` contract. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
|
219
228
|
|
|
220
229
|
### Python MCP SDK surface
|
|
221
230
|
|
|
222
|
-
thread-keeper requires MCP Python SDK 2.
|
|
223
|
-
stable Skills
|
|
231
|
+
thread-keeper requires MCP Python SDK 2.3 or later (`mcp>=2.3.0,<3`). Its
|
|
232
|
+
stable Skills and durable Tasks extensions use the MCP 2026-07-28 extension surface, while tools,
|
|
224
233
|
resources, prompts, annotations, structured content, elicitation, and stdio
|
|
225
234
|
continue to use the same `MCPServer` instance.
|
|
226
235
|
|
|
227
|
-
### MCP primitives (tools, resources, prompts, elicitation)
|
|
236
|
+
### MCP primitives (tools, resources, prompts, Apps, elicitation)
|
|
228
237
|
|
|
229
238
|
MCP has three server primitives. thread-keeper uses all three, mapped to the
|
|
230
239
|
read/act split, plus MCP elicitation for host-native confirmations:
|
|
@@ -232,8 +241,9 @@ read/act split, plus MCP elicitation for host-native confirmations:
|
|
|
232
241
|
| Primitive | Control | What thread-keeper exposes | When to use |
|
|
233
242
|
|---|---|---|---|
|
|
234
243
|
| **Tools** | model-controlled (may act) | the full surface — `brief`, `note`, `spawn`, `search`, `curator_review`, `wikilink_health`, … | the agent decides to call them |
|
|
235
|
-
| **Resources** | application-controlled, read-only | `memory://brief`, `memory://context`, `memory://dashboard`, `memory://agent-status` | the **host** attaches/pulls them automatically |
|
|
244
|
+
| **Resources** | application-controlled, read-only | `memory://brief`, `memory://context`, `memory://dashboard`, `memory://agent-status` | the **host** attaches/pulls them automatically; capable hosts may subscribe |
|
|
236
245
|
| **Prompts** | user-controlled templates | `review_recent_threads`, `run_library_curation`, `audit_threadkeeper` | the user runs them (Claude Code: `/mcp__thread-keeper__<name>`) |
|
|
246
|
+
| **MCP App** | host-rendered, display-only | `open_dashboard` → `ui://thread-keeper/dashboard` | a host that advertises MCP Apps opens the sandboxed dashboard |
|
|
237
247
|
|
|
238
248
|
**Resources** back the genuinely read-only memory views with the same render
|
|
239
249
|
functions as the matching tools, so the content is identical — `memory://brief`
|
|
@@ -247,6 +257,24 @@ so an automatic host pull is **side-effect-free**.
|
|
|
247
257
|
**Prompts** turn the curation / audit / review flows into discoverable,
|
|
248
258
|
parameterized commands; each just drives the existing tools.
|
|
249
259
|
|
|
260
|
+
**Portable dashboard App.** `open_dashboard` is a read-only progressive
|
|
261
|
+
enhancement over the existing status data. In an MCP Apps-capable host it opens
|
|
262
|
+
`ui://thread-keeper/dashboard` in a sandboxed iframe and shows autonomous
|
|
263
|
+
loops, running children, budget state, task history, recent failures, and
|
|
264
|
+
recent materializations. It never sends prompts, transcripts, task-log text,
|
|
265
|
+
credentials, child identifiers, or local paths to the iframe. Loading and the
|
|
266
|
+
App's Refresh button only open the existing database in SQLite read-only mode;
|
|
267
|
+
they never bootstrap a session, reconcile tasks, start daemons, refresh process
|
|
268
|
+
RSS, or write telemetry.
|
|
269
|
+
|
|
270
|
+
This is portable across macOS, Windows, and Linux because it uses the existing
|
|
271
|
+
stdio server registration in the table above — no native app installation is
|
|
272
|
+
needed. If the host does not advertise the `io.modelcontextprotocol/ui`
|
|
273
|
+
extension with `text/html;profile=mcp-app`, `open_dashboard` returns a short
|
|
274
|
+
text fallback; use `agent_status(json_output=True)` or `mp_dashboard()` as the
|
|
275
|
+
tested fallback. The macOS menu-bar app remains an optional native frontend
|
|
276
|
+
over the same status data, not a requirement for this dashboard.
|
|
277
|
+
|
|
250
278
|
**Elicitation** is a client feature, not a server primitive. When a host
|
|
251
279
|
advertises form-mode elicitation, high-stakes mutations can pause for a
|
|
252
280
|
structured user choice instead of relying on an ignorable text nudge. The first
|
|
@@ -256,7 +284,8 @@ the previous immediate tool behavior.
|
|
|
256
284
|
|
|
257
285
|
Everything here is **additive and capability-gated**: a host that advertises the
|
|
258
286
|
`resources` / `prompts` capabilities sees those primitives; one that advertises
|
|
259
|
-
|
|
287
|
+
MCP Apps gets the dashboard iframe; and one that advertises `elicitation.form`
|
|
288
|
+
gets structured confirmations for covered high-stakes writes.
|
|
260
289
|
Hosts without a capability fall back to the SessionStart hook plus the `brief()`
|
|
261
290
|
/ `context()` tools and the existing write behavior — same content, no
|
|
262
291
|
regression. Static URIs only for now (resource *templates* with `{param}` are
|
|
@@ -285,6 +314,23 @@ activate it, approve its tools, record use telemetry, or approve supporting
|
|
|
285
314
|
files. Activation and any user approval remain responsibilities of the host's
|
|
286
315
|
skill-loading path.
|
|
287
316
|
|
|
317
|
+
### MCP Tasks for headless spawn
|
|
318
|
+
|
|
319
|
+
Clients using MCP 2026-07-28 that advertise the
|
|
320
|
+
`io.modelcontextprotocol/tasks` extension can call `spawn(..., visible=False)`
|
|
321
|
+
as a durable asynchronous operation. The response is an opaque public task
|
|
322
|
+
handle; poll it with `tasks/get` and request cooperative cancellation with
|
|
323
|
+
`tasks/cancel`. Handles are bound to the authenticated caller (or the local OS
|
|
324
|
+
user for stdio), survive a host reconnect, and never expose the short internal
|
|
325
|
+
spawn ID. The server suggests a five-second polling interval.
|
|
326
|
+
|
|
327
|
+
Only headless `spawn` is task-eligible: visible Terminal.app work has no stable
|
|
328
|
+
process-group handle. `tournament`, `tasks`, `task_logs`, and the legacy custom
|
|
329
|
+
spawn lifecycle stay unchanged. Clients that do not advertise the extension
|
|
330
|
+
receive the existing synchronous `spawn` response, including on older protocol
|
|
331
|
+
versions. `spawn` does not pause for approval, so it deliberately does not
|
|
332
|
+
implement `tasks/update` or task enumeration.
|
|
333
|
+
|
|
288
334
|
### Memory egress (cross-provider privacy)
|
|
289
335
|
|
|
290
336
|
thread-keeper is "one user model … shared across CLIs," and that sharing is by
|
|
@@ -322,6 +368,32 @@ override wins over `.env`):
|
|
|
322
368
|
THREADKEEPER_MEMORY_EGRESS=same-vendor
|
|
323
369
|
```
|
|
324
370
|
|
|
371
|
+
### Project memory boundaries
|
|
372
|
+
|
|
373
|
+
Memory is also isolated by repository. thread-keeper derives a path-free
|
|
374
|
+
`prj_…` namespace from the repository remote (or, when no remote exists, its
|
|
375
|
+
Git history), so ordinary moves and Git worktrees retain one identity without
|
|
376
|
+
storing a local path. New project memory is scoped to that namespace; portable
|
|
377
|
+
memory must be explicitly written as `global` or `shared`.
|
|
378
|
+
|
|
379
|
+
`brief()`, `search()`, `dialog_search()`, resources, and learning-loop inputs
|
|
380
|
+
read the current project plus explicit global/shared rows. Set
|
|
381
|
+
`cross_project=True` on `search` or `dialog_search` only for deliberate
|
|
382
|
+
cross-project recall. Those results show `scope=`/`project=` provenance and
|
|
383
|
+
write a `cross_project_recall` audit event. The `memory_scope_status()` tool
|
|
384
|
+
shows the current namespace and the migration accounting.
|
|
385
|
+
|
|
386
|
+
Existing databases keep their memory recallable: legacy rows without reliable
|
|
387
|
+
repository provenance become `global`, as all memory effectively was before
|
|
388
|
+
namespaces, while tasks whose working directory still resolves are scoped to
|
|
389
|
+
their project; new memory is scoped from then on. The migration report records
|
|
390
|
+
the counts. A database that was already at schema v5 before namespaces existed
|
|
391
|
+
gets the namespace columns and this one-time backfill on its next start. Rows
|
|
392
|
+
explicitly marked `unresolved` stay excluded from automatic recall. This
|
|
393
|
+
boundary controls *which local memory is recalled*; it is separate from
|
|
394
|
+
`THREADKEEPER_MEMORY_EGRESS`, which controls whether personal memory may be
|
|
395
|
+
sent to another model provider.
|
|
396
|
+
|
|
325
397
|
---
|
|
326
398
|
|
|
327
399
|
## Core systems
|
|
@@ -666,9 +738,23 @@ can correct one unique substring without reserializing a lesson. Shadow-origin
|
|
|
666
738
|
though an existing same-slug long lesson may be corrected without increasing
|
|
667
739
|
its body size; near-duplicate slugs are blocked, and semantic body matches are
|
|
668
740
|
routed to the incumbent lesson or surfaced for curation instead of minting a
|
|
669
|
-
sibling lesson.
|
|
670
|
-
|
|
671
|
-
|
|
741
|
+
sibling lesson. The same slug and semantic gates apply to every writer,
|
|
742
|
+
foreground sessions included: a foreground near-duplicate is refused with the
|
|
743
|
+
neighbour's slug, and the author either extends it with `lesson_patch` or, after
|
|
744
|
+
reading it, passes `new_despite=<slug>` (comma-separated when the gates name
|
|
745
|
+
several neighbours) to keep a deliberately separate rule. Loop writers are
|
|
746
|
+
recognized by their write origin, so a loop child cannot opt out by passing a
|
|
747
|
+
foreground-looking `source`. A same-slug rewrite is never folded into a
|
|
748
|
+
different lesson, and the Curator verifier writes the umbrella of a verified
|
|
749
|
+
consolidation without the gates.
|
|
750
|
+
A new lesson that debunks a near-duplicate is refused too: correct the existing
|
|
751
|
+
lesson instead of adding a conflicting one. Refusals are recorded as
|
|
752
|
+
`lesson_refused` events so the gate's hit rate stays measurable. Replacing a
|
|
753
|
+
lesson by its exact title keeps the creator's `origin` (and, for a
|
|
754
|
+
human-authored lesson, its `source` too), and each new Curator
|
|
755
|
+
pass first backfills legacy or mislabeled origin markers (loop sources such as
|
|
756
|
+
`shadow` and `curator` can never count as human-authored), keeping the
|
|
757
|
+
original file as `lessons.md.pre-origin-backfill`.
|
|
672
758
|
Before a genuinely new fallback lesson is written,
|
|
673
759
|
`lesson_neighbors(title, body, summary, k=3)` shows the nearest existing lesson
|
|
674
760
|
slugs (semantic, with a lexical fallback). Shadow and candidate reviewers use
|
|
@@ -764,22 +850,60 @@ for semantic review. System and installed-plugin sources are resolved from
|
|
|
764
850
|
their read-only caches rather than misreported as missing mirrors; telemetry
|
|
765
851
|
rows with no real `SKILL.md` remain explicit orphans. The same inventory also
|
|
766
852
|
flags a dense lesson subtopic when at least
|
|
767
|
-
`THREADKEEPER_CURATOR_PROMOTION_MIN_LESSONS` lessons (default 3) share
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
853
|
+
`THREADKEEPER_CURATOR_PROMOTION_MIN_LESSONS` lessons (default 3) share two
|
|
854
|
+
concrete title terms and a non-generic body mechanism. Generic joins and
|
|
855
|
+
domain-wide/high-document-frequency title words cannot form a candidate. The
|
|
856
|
+
inventory and `curator_review_status()` report emitted/rejected counts and
|
|
857
|
+
rejection reasons, so the rule can be tuned from production data. Every
|
|
858
|
+
candidate, protected members included, becomes a PROMOTE proposal: the
|
|
859
|
+
verifier creates or patches one validated, checklist-style canonical skill
|
|
860
|
+
before it retires the source lessons. The evaluator reads every complete skill and relevant support
|
|
861
|
+
file, but has no web tools. External research is off by default. With explicit
|
|
862
|
+
`THREADKEEPER_CURATOR_WEB_RESEARCH=1`, a separate Claude-only researcher gets
|
|
863
|
+
only fixed public compatibility queries, `WebSearch`, `WebFetch`, and one
|
|
864
|
+
parent-authorized handoff writer; it cannot read local memory or the
|
|
865
|
+
repository. The evaluator receives that handoff as fenced, untrusted data and
|
|
866
|
+
writes numbered per-skill verdicts to
|
|
776
867
|
`~/.threadkeeper/curator/REPORT-<isodate>.md` for a one-batch pass or
|
|
777
868
|
`REPORT-<isodate>-batch-NNN-of-MMM.md` for a multi-batch pass: KEEP / REPAIR /
|
|
778
|
-
UPDATE / MERGE / SPLIT / DEPRECATE / DELETE / CROSS_LINK /
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
869
|
+
UPDATE / MERGE / SPLIT / DEPRECATE / DELETE / CROSS_LINK / DEFER. DEFER means
|
|
870
|
+
the evidence is insufficient for now and a later pass re-examines the entry;
|
|
871
|
+
no verdict waits for a person. Similar names and cosine scores are only
|
|
872
|
+
candidates; merge/delete decisions compare intent, workflow, inputs, outcomes,
|
|
873
|
+
and unique details. Pinned entries are marked `[PINNED]` (keep only), and
|
|
874
|
+
foreground-authored or legacy entries `[PROTECTED]`; the mutating tools enforce
|
|
875
|
+
both boundaries server-side.
|
|
876
|
+
|
|
877
|
+
Destructive decisions are settled by a second agent, not by a person. The
|
|
878
|
+
evaluator applies only in-place edits that remove nothing (lesson and skill
|
|
879
|
+
patches, repairs, cross-links, concept curation) to entries that are neither
|
|
880
|
+
`[PROTECTED]` nor `[PINNED]`. Every removal, consolidation, merge, split and
|
|
881
|
+
promotion, and every change to a `[PROTECTED]` entry, goes into a
|
|
882
|
+
`VERIFY_PROPOSALS` block at the end of its report. When that block holds at
|
|
883
|
+
least one numbered proposal, the batch gets a third child: an independent
|
|
884
|
+
verifier that re-reads every lesson and skill a proposal touches, tries to
|
|
885
|
+
refute it (a merge that would lose a distinct rule, a removal whose "covered
|
|
886
|
+
by" claim is only partly true, a promotion that is not one procedure), and
|
|
887
|
+
applies only the confirmed or corrected proposals inside the pass snapshot. It
|
|
888
|
+
patches missing residue into a covering skill before removing a lesson,
|
|
889
|
+
records `keep_both` merge verdicts for the pairs it refutes, and writes a
|
|
890
|
+
provenanced `VERIFY-<pass>[-batch-NNN-of-MMM].md` report ending with
|
|
891
|
+
`CURATOR_VERIFY_COMPLETE`. A pass is endorsed only after every batch's
|
|
892
|
+
verification is complete or has used its three attempts. Server-side,
|
|
893
|
+
`lesson_remove` and `skill_manage(action='delete')` refuse the evaluator
|
|
894
|
+
outright, and `lesson_patch` / same-slug `lesson_append` refuse it on protected
|
|
895
|
+
lessons; only the verifier phase, inside a pass carrying both pass-id and
|
|
896
|
+
snapshot-dir context, may change protected entries, and no curator phase may
|
|
897
|
+
touch a pinned one. The verifier is also scoped: the parent records every name
|
|
898
|
+
its batch's proposals mention under the pass snapshot
|
|
899
|
+
(`verify_scope/batch-NNN.json`), and the server refuses a verifier change to
|
|
900
|
+
any lesson or skill outside that list. The batch inventory and the proposals
|
|
901
|
+
reach it as fenced, untrusted data. The Evolve report applier never removes
|
|
902
|
+
anything: it may patch, and the server refuses its `lesson_remove` and skill
|
|
903
|
+
deletes. The parent records the mode each evaluator actually ran in, so a batch
|
|
904
|
+
whose research failed (and was evaluated without mutation tools) never reaches
|
|
905
|
+
the verifier. Report destinations are authorized by exact pass and name, so a
|
|
906
|
+
long pass cannot age them out. The pass is
|
|
783
907
|
single-flight across processes — a non-blocking `fcntl.flock` pidfile
|
|
784
908
|
(`<db dir>/curator.lock`) plus a running-children check serialize it, so
|
|
785
909
|
multiple MCP server instances can't run overlapping (now destructive) passes
|
|
@@ -841,11 +965,13 @@ SHA-256 as provenance. Research and evaluation are two phases of each batch
|
|
|
841
965
|
inside the durable pass: the web-free evaluator for a batch starts only after
|
|
842
966
|
that batch's handoff validates. A researcher that fails or leaves a missing,
|
|
843
967
|
malformed, swapped, or mismatched handoff is retried (three attempts); after
|
|
844
|
-
that the batch gets a non-mutating evaluator that
|
|
845
|
-
|
|
968
|
+
that the batch gets a non-mutating evaluator that marks `DEFER` for decisions
|
|
969
|
+
that depend on current external facts, so a later pass re-examines them. A destructive pass takes its
|
|
846
970
|
recovery snapshot right before its first mutating evaluator.
|
|
847
971
|
`THREADKEEPER_CURATOR_WEB_RESEARCH=0` skips the research phase entirely (and
|
|
848
972
|
its token cost); the evaluator then judges currency from local evidence only.
|
|
973
|
+
A batch that holds no skills never gets a researcher: the fixed research topics
|
|
974
|
+
only concern skills, so its evaluator starts at once.
|
|
849
975
|
Each report path is then
|
|
850
976
|
authorized in the existing parent-authored `curator_pass` event;
|
|
851
977
|
`curator_report_write` records its SHA-256 in `curator_report_provenance`.
|
|
@@ -853,34 +979,39 @@ This makes the report directory an untrusted transport: a stray or forged
|
|
|
853
979
|
`REPORT-*.md` file cannot acquire the provenance needed by the applier.
|
|
854
980
|
|
|
855
981
|
The web-enabled researcher never receives lesson, skill, or concept mutation
|
|
856
|
-
tools. The evaluator never receives web tools.
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
982
|
+
tools. The evaluator never receives web tools. By default the evaluator applies
|
|
983
|
+
its own in-place edits directly after writing the REPORT, and the verifier
|
|
984
|
+
applies the confirmed destructive proposals; neither the evaluator's toolset
|
|
985
|
+
nor the server lets the evaluator remove anything. Set
|
|
860
986
|
`THREADKEEPER_CURATOR_DESTRUCTIVE=0` for advisory REPORT-only. Pinned and
|
|
861
|
-
untracked skills remain protected. Foreground-authored skills are
|
|
862
|
-
default
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
987
|
+
untracked skills remain protected. Foreground-authored skills are `[PROTECTED]`
|
|
988
|
+
by default, so only the verifier changes them;
|
|
989
|
+
`THREADKEEPER_CURATOR_MANAGE_FOREGROUND_SKILLS=1` (which does not limit the
|
|
990
|
+
verifier) additionally lets the
|
|
991
|
+
evaluator make in-place repairs to them. Lessons are
|
|
866
992
|
stamped with an explicit `origin=<THREADKEEPER_WRITE_ORIGIN>` marker when
|
|
867
993
|
appended; missing, legacy, or unknown lesson provenance is protected by
|
|
868
994
|
default. `lesson_remove` and `skill_manage(action='delete')` refuse protected
|
|
869
995
|
foreground/unknown-origin entries unless `force=True` is called from a
|
|
870
|
-
foreground writer
|
|
871
|
-
`force`. Before a destructive child is spawned, thread-keeper writes
|
|
996
|
+
foreground writer or the caller is the Curator verifier phase; curator/spawned
|
|
997
|
+
children cannot elevate themselves with `force`. Before a destructive child is spawned, thread-keeper writes
|
|
872
998
|
a recoverable snapshot under
|
|
873
999
|
`<reports_dir>/snapshots/<pass-id>/` (default
|
|
874
1000
|
`~/.threadkeeper/curator/snapshots/<pass-id>/`). The snapshot contains
|
|
875
1001
|
`lessons.md`, copied in-scope skill dirs, a `manifest.json`, and per-action
|
|
876
1002
|
tombstones for curator prunes/deletes. Retention is bounded by
|
|
877
|
-
`THREADKEEPER_CURATOR_SNAPSHOT_RETENTION` (default 10
|
|
1003
|
+
`THREADKEEPER_CURATOR_SNAPSHOT_RETENTION` (default 10) for legacy/direct
|
|
1004
|
+
snapshots. Daemon-owned snapshots instead follow the coordinated workflow
|
|
1005
|
+
retention policy, so their pass, report, manifest, and recovery archive cannot
|
|
1006
|
+
age out on different schedules; active work and an endorsed report that has not
|
|
1007
|
+
been applied always keep recovery material.
|
|
878
1008
|
Use `curator_restore(pass_id, lesson_slug="...")` or
|
|
879
1009
|
`curator_restore(pass_id, skill_name="...")` to restore an item from a snapshot.
|
|
880
1010
|
As a prevention layer before recovery is needed, a destructive Curator pass
|
|
881
1011
|
has one server-side shared admission budget for `lesson_remove` and
|
|
882
1012
|
`skill_manage(action='delete')`, including across bounded child batches.
|
|
883
|
-
`THREADKEEPER_CURATOR_MAX_DESTRUCTIVE_PER_PASS` defaults to
|
|
1013
|
+
`THREADKEEPER_CURATOR_MAX_DESTRUCTIVE_PER_PASS` defaults to 30 (only verified
|
|
1014
|
+
proposals delete anything); set it to 0 to
|
|
884
1015
|
disable those autonomous deletes. The pass ID makes the count durable and
|
|
885
1016
|
cross-process, while foreground/human deletes are unaffected. `mp_dashboard`
|
|
886
1017
|
shows admitted and refused operations with `status=HIT` when the Curator reaches
|
|
@@ -914,9 +1045,21 @@ The shared lesson file has its own write serialization: `lesson_append`,
|
|
|
914
1045
|
`lessons.md.lock` around file creation/read/mutate/write, so foreground calls
|
|
915
1046
|
and learning-loop children cannot last-writer-win over each other's sections.
|
|
916
1047
|
|
|
1048
|
+
`lesson_list(k=20, query="")` lists the newest lessons, or with `query` (the
|
|
1049
|
+
managed instructions pass the opening request) the most relevant ones with a
|
|
1050
|
+
relevance score: semantic when embeddings are available, otherwise the share
|
|
1051
|
+
of query words (any script) a lesson contains. Lesson vectors are stored in the
|
|
1052
|
+
`lesson_vectors` table keyed by body hash and embedding fingerprint, so each
|
|
1053
|
+
body is encoded once per install. Missing vectors are encoded in chunks of 32
|
|
1054
|
+
within an 8-second budget per call; a call that runs out falls back to the
|
|
1055
|
+
lexical ranking and the next call resumes. Each line shows the lesson's summary. Without a query a session
|
|
1056
|
+
would only ever see the newest few lessons, whatever the task.
|
|
1057
|
+
|
|
917
1058
|
Lesson access is tracked the same way skill access is: `lesson_list` increments
|
|
918
1059
|
`lesson_usage.view_count` for displayed rows and `lesson_get` increments
|
|
919
|
-
`lesson_usage.use_count` for the returned lesson.
|
|
1060
|
+
`lesson_usage.use_count` for the returned lesson. Only foreground reads count:
|
|
1061
|
+
the curator and other loops read every lesson they audit, and counting those
|
|
1062
|
+
reads would make every lesson look recently useful. Curator dry runs include a
|
|
920
1063
|
ranked `STALE LESSONS (dry-run decay ranking)` section computed as
|
|
921
1064
|
`access_frequency × exp(-days_since_access / tau)`, filtered to unprotected
|
|
922
1065
|
lessons with no recent access and low pull-count. That decay list is advisory
|
|
@@ -1011,15 +1154,27 @@ with that name instead of minting overlapping roadmap PRs.
|
|
|
1011
1154
|
|
|
1012
1155
|
The Evolve applier is the downstream implementer. `evolve_apply_roadmap_issue()`
|
|
1013
1156
|
picks one open GitHub issue at a time (`roadmap` label first, then FIFO), but
|
|
1014
|
-
the automatic pass first
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1157
|
+
the automatic pass first sweeps the open PRs authored by the account
|
|
1158
|
+
thread-keeper's `gh` acts as — yours: applier, reviewer, and hand-made PRs
|
|
1159
|
+
alike. A PR from any other account (a collaborator, a bot, a fork) is never
|
|
1160
|
+
updated, repaired, or merged. If the PR sweep itself cannot read GitHub state
|
|
1161
|
+
or the `gh` account, the pass fails closed instead of taking fresh work blind.
|
|
1162
|
+
The sweep squash-merges the oldest non-draft PR GitHub reports `CLEAN` (up to
|
|
1163
|
+
date, required checks green) through the protected merge API, pinned to the
|
|
1164
|
+
head SHA it saw green, and then asks GitHub to update the oldest `BEHIND` one —
|
|
1165
|
+
the "Update branch" button — so CI reruns against the new base. Open
|
|
1166
|
+
work in progress as a draft: drafts are never merged. Strict branch
|
|
1167
|
+
protection lets only one PR merge per base commit, so PRs land one at a time,
|
|
1168
|
+
oldest first. Between full passes the daemon repeats only this cheap landing
|
|
1169
|
+
sweep every 15 minutes, so a green PR lands minutes after its checks finish;
|
|
1170
|
+
it skips while any reviewer/applier git writer runs. A conflicted applier PR
|
|
1171
|
+
(`roadmap/…` or `evolve/…` head branch in this repository) is repaired before
|
|
1172
|
+
any new issue/report/evolve work is started: the conflict-repair child checks
|
|
1173
|
+
out the existing PR branch, merges the current base branch, resolves
|
|
1174
|
+
conflicts, runs the full suite, and pushes back to the same branch, then
|
|
1175
|
+
stops; the landing sweep merges it once it is green. A hand-made PR with
|
|
1176
|
+
conflicts, and red or otherwise blocked PRs, wait for a human. Set
|
|
1177
|
+
`THREADKEEPER_EVOLVE_AUTOLAND=0` to leave every merge to a human.
|
|
1023
1178
|
The roadmap issue child skips issues carrying denylisted human-gate labels,
|
|
1024
1179
|
skips issues with an active Evolve claim comment, posts its own claim comment
|
|
1025
1180
|
before spawning, and advances to the next issue when an issue-local dispatch
|
|
@@ -1054,9 +1209,13 @@ task’s tree. This prevents unrelated unfinished tests from blocking every PR
|
|
|
1054
1209
|
repair. Ignored files (including `.venv`) and explicit operator checkouts stay
|
|
1055
1210
|
in place; live writers prevent recovery, and backup failures block dispatch.
|
|
1056
1211
|
Each managed-checkout child fetches the
|
|
1057
|
-
configured branch
|
|
1058
|
-
|
|
1059
|
-
|
|
1212
|
+
configured branch, prepares or resumes its deterministic local/remote feature
|
|
1213
|
+
branch from the freshly fetched `origin/<THREADKEEPER_EVOLVE_REPO_BRANCH>` tip,
|
|
1214
|
+
rebases onto it, and re-syncs the managed venv to that branch's declared
|
|
1215
|
+
dependencies. `THREADKEEPER_EVOLVE_REPO_COMMIT` bounds only what the parent
|
|
1216
|
+
provisions and parks the checkout at; it is never a branch base, because a base
|
|
1217
|
+
that never moves cut every applier PR from one stale commit and each PR was
|
|
1218
|
+
born conflicted with `main`. Retries
|
|
1060
1219
|
therefore validate prior branch work instead of discovering a branch-name
|
|
1061
1220
|
collision after changing the base checkout. A shared git-writer running-task
|
|
1062
1221
|
check prevents the privileged reviewer audit and code/PR applier from
|
|
@@ -1066,8 +1225,9 @@ If a killed child leaves an unresolved merge or plain tracked WIP in the default
|
|
|
1066
1225
|
auto-managed checkout, the next code-producing pass archives the diff before
|
|
1067
1226
|
recovering it. Merge recovery remains limited to `roadmap/…`/`evolve/…`
|
|
1068
1227
|
branches whose exact PR is confirmed open or merged. For an open PR, the parent
|
|
1069
|
-
archives the interrupted merge
|
|
1070
|
-
|
|
1228
|
+
archives the interrupted merge (including edits the child made after staging
|
|
1229
|
+
a resolution), resets it, refreshes the disposable checkout, and lets the
|
|
1230
|
+
normal conflict-repair sweep retry that same PR. A merged PR's
|
|
1071
1231
|
leftover merge is discarded as stale. Plain abandoned WIP is recoverable on
|
|
1072
1232
|
those applier branches when PR state is readable, and also on the configured
|
|
1073
1233
|
base branch: the disposable base can contain orphaned edits when an older child
|
|
@@ -1160,6 +1320,11 @@ daily deterministic `docs/roadmap-audit-YYYY-MM-DD` branch so repeated audit
|
|
|
1160
1320
|
passes update or skip the existing roadmap PR rather than opening a second one.
|
|
1161
1321
|
Automatic apply passes respect the configured interval so multiple foreground
|
|
1162
1322
|
MCP server startups do not repeatedly spawn workers for the same open issue.
|
|
1323
|
+
That interval is consumed only by a real child launch or a completed empty
|
|
1324
|
+
check. A busy applier, checkout/GitHub/claim/race error, or refused spawn keeps
|
|
1325
|
+
the prior cursor and retries on a bounded short cadence; repeated identical
|
|
1326
|
+
blockers are recorded once until the blocker changes. `evolve_apply_status()`
|
|
1327
|
+
shows the last attempt, last successful dispatch, and any pending retry time.
|
|
1163
1328
|
Manual tools such as `evolve_apply_conflicted_pr()` and
|
|
1164
1329
|
`evolve_apply_roadmap_issue()` dispatch immediately. If no conflicted applier PR
|
|
1165
1330
|
or roadmap issue is startable, the pass falls back to Curator reports and then
|
|
@@ -1337,8 +1502,45 @@ Adding a new language is a two-file PR — see [CONTRIBUTING.md](CONTRIBUTING.md
|
|
|
1337
1502
|
|
|
1338
1503
|
The most-used env knobs (full list in `threadkeeper/config.py`):
|
|
1339
1504
|
|
|
1505
|
+
### MCP tool profiles
|
|
1506
|
+
|
|
1507
|
+
The server can advertise a role-sized MCP tool surface instead of its full
|
|
1508
|
+
catalog. Set `THREADKEEPER_TOOL_PROFILE` before server startup, then restart
|
|
1509
|
+
the host after changing it:
|
|
1510
|
+
|
|
1511
|
+
| Profile | Intended surface |
|
|
1512
|
+
| --- | --- |
|
|
1513
|
+
| `core` | daily session memory: `brief`, `context`, threads, notes, search, and peer coordination |
|
|
1514
|
+
| `coordination` | `core` plus child spawning, task status, pickup, and delegated search |
|
|
1515
|
+
| `memory-admin` | `core` plus durable memory, concepts, lessons, graph links, and explicit erasure |
|
|
1516
|
+
| `learning-loops` | `core` plus extraction, validation, skills, Curator, probes, and dialectic workflows |
|
|
1517
|
+
| `full` | every registered tool; the compatibility default when no profile is configured |
|
|
1518
|
+
|
|
1519
|
+
New `thread-keeper-setup` registrations use `core`. Existing server entries
|
|
1520
|
+
without the setting retain `full` until you opt in. Add exact, comma-separated
|
|
1521
|
+
tool names with `THREADKEEPER_TOOL_INCLUDE`, or remove names with
|
|
1522
|
+
`THREADKEEPER_TOOL_EXCLUDE`; unknown names and contradictory overrides stop the
|
|
1523
|
+
server rather than exposing an unintended surface.
|
|
1524
|
+
|
|
1525
|
+
Spawned children default to `core`; archival, Curator, candidate-review,
|
|
1526
|
+
shadow-review, probe-runner, and dialectic-validator roles use
|
|
1527
|
+
`learning-loops`, while Evolve roles request `full`. The server receives the
|
|
1528
|
+
parent’s resolved allowlist and intersects it with the child profile, so a
|
|
1529
|
+
child cannot regain a tool its parent did not expose.
|
|
1530
|
+
`THREADKEEPER_CHILD_TOOL_PROFILE` explicitly selects a different child profile,
|
|
1531
|
+
still under that parent constraint.
|
|
1532
|
+
|
|
1533
|
+
Every non-full profile includes read-only `capability_search(query)`. It returns
|
|
1534
|
+
at most six matching capability *families* with a summary and the profile that
|
|
1535
|
+
would expose them. It does not list hidden concrete tools or enable any action;
|
|
1536
|
+
use it as the fallback on hosts that lack native deferred tool discovery.
|
|
1537
|
+
|
|
1340
1538
|
| Knob | Default | Purpose |
|
|
1341
1539
|
|---|---|---|
|
|
1540
|
+
| `THREADKEEPER_TOOL_PROFILE` | `full` | MCP registry profile: `core`, `coordination`, `memory-admin`, `learning-loops`, or `full`; read at server start |
|
|
1541
|
+
| `THREADKEEPER_TOOL_INCLUDE` | "" | exact comma-separated tool names to add to the selected profile; unknown names fail closed |
|
|
1542
|
+
| `THREADKEEPER_TOOL_EXCLUDE` | "" | exact comma-separated tool names to remove from the selected profile; unknown or overlapping names fail closed |
|
|
1543
|
+
| `THREADKEEPER_CHILD_TOOL_PROFILE` | role default | spawned-child profile override; always intersected with the parent’s resolved tool surface |
|
|
1342
1544
|
| `THREADKEEPER_DB` | `~/.threadkeeper/db.sqlite` | SQLite file |
|
|
1343
1545
|
| `THREADKEEPER_TASK_LOG_DIR` | `~/.threadkeeper/tasks` | owner-only task spool for spawn logs, stdin prompts, command scripts, and small runtime logs |
|
|
1344
1546
|
| `THREADKEEPER_RETENTION_INTERVAL_S` | 0 (off) | SQLite retention/compaction daemon tick; 0 disables the daemon |
|
|
@@ -1349,7 +1551,13 @@ The most-used env knobs (full list in `threadkeeper/config.py`):
|
|
|
1349
1551
|
| `THREADKEEPER_PROBE_RESULT_RETENTION_DAYS` | 0 | prune old `probe_results` and refresh reliability aggregates; 0 keeps forever |
|
|
1350
1552
|
| `THREADKEEPER_RETENTION_WAL_CHECKPOINT` | false | run `PRAGMA wal_checkpoint(TRUNCATE)` during retention passes |
|
|
1351
1553
|
| `THREADKEEPER_RETENTION_VACUUM_AFTER_ROWS` | 0 | run `VACUUM` after a pass deletes at least this many rows; 0 disables VACUUM |
|
|
1554
|
+
| `THREADKEEPER_WORKFLOW_RETENTION_DAYS` | 30 | age protection for terminal Curator passes and consumed Evolve research handoffs; `0` disables the age bound |
|
|
1555
|
+
| `THREADKEEPER_WORKFLOW_RETENTION_COUNT` | 100 | newest terminal Curator passes and research handoffs protected by count; `0` disables the count bound |
|
|
1352
1556
|
| `THREADKEEPER_MEMORY_EGRESS` | `all` | cross-provider scope for personal-class memory (verbatim quotes + dialectic user-model) in `brief()`. `all` = current behavior, egress to whichever vendor backs the consuming CLI. `same-vendor` = personal renders only for Claude/Anthropic, omitted for OpenAI/Google/Microsoft CLIs. `work-only` = personal never rendered, any vendor. See [Memory egress](#memory-egress-cross-provider-privacy) |
|
|
1557
|
+
| `THREADKEEPER_OTEL_ENABLED` | false | enable privacy-safe OTLP tracing only when an endpoint is also configured |
|
|
1558
|
+
| `THREADKEEPER_OTEL_ENDPOINT` | "" | OTLP/HTTP collector trace endpoint, normally `https://collector.example/v1/traces` |
|
|
1559
|
+
| `THREADKEEPER_OTEL_EXPORT_TIMEOUT_S` | 2 | collector request timeout; export happens outside the local workflow |
|
|
1560
|
+
| `THREADKEEPER_OTEL_EXPORT_QUEUE_SIZE` | 256 | bounded per-process span queue (1–2048); oldest queued spans are dropped under pressure |
|
|
1353
1561
|
| `THREADKEEPER_AUTO_REVIEW` | "" (off) | auto-review on `close_thread` |
|
|
1354
1562
|
| `THREADKEEPER_AUTO_UPDATE_INTERVAL_S` | 86400 | MCP self-update check interval; 0 disables |
|
|
1355
1563
|
| `THREADKEEPER_AUTO_UPDATE_RESTART` | "1" | exit MCP process after an update passes setup/import smoke checks so the host restarts on new code |
|
|
@@ -1381,11 +1589,11 @@ The most-used env knobs (full list in `threadkeeper/config.py`):
|
|
|
1381
1589
|
| `THREADKEEPER_CANDIDATE_REVIEW_BATCH_SIZE` | 40 | most pending candidates (oldest first) one reviewer child receives; the rest stay pending for the next pass |
|
|
1382
1590
|
| `THREADKEEPER_LEARNING_LOOP_SKILL_CREATE_LIMIT` | 2 | max new skills one autonomous learning-loop child (`candidate_review`, `shadow_review`, or `background_review`) may create in its session; foreground creation is unaffected |
|
|
1383
1591
|
| `THREADKEEPER_CURATOR_INTERVAL_S` | 259200 | deep curator audit every three days; set `0` to disable |
|
|
1384
|
-
| `THREADKEEPER_CURATOR_MANAGE_FOREGROUND_SKILLS` | 0 |
|
|
1592
|
+
| `THREADKEEPER_CURATOR_MANAGE_FOREGROUND_SKILLS` | 0 | let the Curator evaluator make in-place repairs to foreground skills; the verifier changes foreground skills its proposals name regardless, and pinned skills stay protected |
|
|
1385
1593
|
| `THREADKEEPER_CURATOR_MIN_LESSONS` | 3 | min lessons before curator engages |
|
|
1386
|
-
| `THREADKEEPER_CURATOR_WEB_RESEARCH` | `
|
|
1594
|
+
| `THREADKEEPER_CURATOR_WEB_RESEARCH` | `0` (off) | explicit consent for the isolated Curator web researcher; it receives only fixed public compatibility queries, and decisions that need facts the handoff lacks are marked `DEFER` for a later pass |
|
|
1387
1595
|
| `THREADKEEPER_CURATOR_DESTRUCTIVE` | `1` (on) | curator child writes its REPORT then applies its own PATCH/PRUNE/CONSOLIDATE directly (incl. `lesson_remove` for prune/consolidate); set `0` for advisory REPORT-only. `[PROTECTED]` entries are refused server-side |
|
|
1388
|
-
| `THREADKEEPER_CURATOR_MAX_DESTRUCTIVE_PER_PASS` |
|
|
1596
|
+
| `THREADKEEPER_CURATOR_MAX_DESTRUCTIVE_PER_PASS` | 30 | shared server-side ceiling for one destructive Curator pass across `lesson_remove` and `skill_manage(action='delete')`; `0` disables autonomous deletes and foreground deletes bypass it |
|
|
1389
1597
|
| `THREADKEEPER_CURATOR_SNAPSHOT_RETENTION` | 10 | number of destructive curator pre-mutation snapshots to retain under `<reports_dir>/snapshots`; current pass is always retained |
|
|
1390
1598
|
| `THREADKEEPER_CURATOR_TRASH_TTL_DAYS` | 30 | days to retain recovery artifacts under `<db dir>/curator/trash` for `lesson_remove` and `skill_manage(action='delete')`; expired artifacts are swept on new trash writes |
|
|
1391
1599
|
| `THREADKEEPER_PROBE_INTERVAL_S` | 0 (off) | probe daemon tick (s); 1800 = 30 min recommended so finished probe answers are graded promptly |
|
|
@@ -1427,12 +1635,17 @@ The most-used env knobs (full list in `threadkeeper/config.py`):
|
|
|
1427
1635
|
| `THREADKEEPER_SKILL_NUDGE_INTERVAL` | 10 | events between `skill_hint` nudges |
|
|
1428
1636
|
| `THREADKEEPER_DIALECTIC_MINE_INTERVAL_S` | 0 (off) | dialectic_miner daemon tick (s); 0 disables mechanical observation capture |
|
|
1429
1637
|
| `THREADKEEPER_DIALECTIC_VALIDATE_INTERVAL_S` | 0 (off) | dialectic_validator daemon tick (s); 0 disables LLM-driven claim synthesis |
|
|
1638
|
+
| `THREADKEEPER_MODEL_ROUTER_INTERVAL_S` | 604800 (weekly) | model-router pass interval (s); 0 disables it and `auto` follows the active CLI |
|
|
1639
|
+
| `THREADKEEPER_MODEL_ROUTER_FALLBACK_CLI` | codex | CLI for the router's fallback run |
|
|
1640
|
+
| `THREADKEEPER_MODEL_ROUTER_FALLBACK_MODEL` | gpt-6.1-sol | model for the router's fallback run |
|
|
1641
|
+
| `THREADKEEPER_MODEL_ROUTER_FALLBACK_EFFORT` | xhigh | effort for the router's fallback run |
|
|
1430
1642
|
| `THREADKEEPER_DIALECTIC_VALIDATE_MIN` | 5 | min buffered observations before validator engages |
|
|
1431
1643
|
| `THREADKEEPER_DIALECTIC_VALIDATE_FLUSH_AGE_S` | 259200 | age-flush: an undersized observation buffer is still validated once its oldest eligible row is this old (0 = threshold only) |
|
|
1432
1644
|
| `THREADKEEPER_DIALECTIC_VALIDATE_BATCH_SIZE` | 50 | max observations sent to one validator child; prevents oversized prompts and drains large queues incrementally |
|
|
1433
1645
|
| `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 |
|
|
1434
1646
|
| `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 |
|
|
1435
1647
|
| `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 |
|
|
1648
|
+
| `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 |
|
|
1436
1649
|
| `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 |
|
|
1437
1650
|
| `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 |
|
|
1438
1651
|
| `THREADKEEPER_EVOLVE_REPO_URL` | upstream repo | HTTPS `github.com` source for the managed clone; restart-only, and other hosts/schemes are refused |
|
|
@@ -1492,7 +1705,7 @@ THREADKEEPER_SPAWN__DEFAULT=claude
|
|
|
1492
1705
|
# supported CLI keys: claude, codex, antigravity (agy executable), copilot
|
|
1493
1706
|
THREADKEEPER_SPAWN__LOOP__SHADOW_OBSERVER=claude # heaviest reasoning → keep on Claude
|
|
1494
1707
|
THREADKEEPER_SPAWN__LOOP__CURATOR=codex # weekly audit → Codex is fine
|
|
1495
|
-
THREADKEEPER_SPAWN__LOOP__CANDIDATE_REVIEWER=auto # "auto" =
|
|
1708
|
+
THREADKEEPER_SPAWN__LOOP__CANDIDATE_REVIEWER=auto # "auto" = model router decides (active CLI until it has)
|
|
1496
1709
|
# model pin per CLI or per role: THREADKEEPER_SPAWN__MODEL__<KEY>=<model>
|
|
1497
1710
|
THREADKEEPER_SPAWN__MODEL__CLAUDE=opus
|
|
1498
1711
|
THREADKEEPER_SPAWN__MODEL__CODEX=gpt-5.5
|
|
@@ -1505,7 +1718,8 @@ THREADKEEPER_SPAWN__EFFORT__CURATOR=xhigh
|
|
|
1505
1718
|
```
|
|
1506
1719
|
|
|
1507
1720
|
Resolution per role: `SPAWN__LOOP__<role>` → `SPAWN__DEFAULT` → active CLI →
|
|
1508
|
-
`claude`;
|
|
1721
|
+
`claude`; unset defers to the active CLI, and `"auto"` does too until the model
|
|
1722
|
+
router (below) has decided for that role. Real environment
|
|
1509
1723
|
variables override the `.env`. Force host detection with
|
|
1510
1724
|
`THREADKEEPER_ACTIVE_CLI=claude` (or `codex`, `antigravity`/`agy`,
|
|
1511
1725
|
`copilot`). `agy` is normalized to `antigravity`. See `.env.example`
|
|
@@ -1513,6 +1727,28 @@ for the full knob list. `spawn_status()` includes warnings when a configured
|
|
|
1513
1727
|
spawn CLI is unsupported or a model key does not match a supported CLI/startup
|
|
1514
1728
|
role, while keeping the same fallback resolution.
|
|
1515
1729
|
|
|
1730
|
+
### Model router (`auto` roles)
|
|
1731
|
+
|
|
1732
|
+
Set a role's CLI to `auto` to let the weekly **model router** choose its CLI,
|
|
1733
|
+
model and effort:
|
|
1734
|
+
|
|
1735
|
+
```bash
|
|
1736
|
+
THREADKEEPER_SPAWN__LOOP__SHADOW_OBSERVER=auto
|
|
1737
|
+
```
|
|
1738
|
+
|
|
1739
|
+
The router reviews the last 30 days of that role's spawns (success rate,
|
|
1740
|
+
failures such as provider capacity errors, run time, tokens, recent outcomes)
|
|
1741
|
+
plus the local model catalog, and records one decision per `auto` role with a
|
|
1742
|
+
reason. Until it has decided, `auto` keeps its old meaning and follows the
|
|
1743
|
+
active CLI. While a role is routed, its `SPAWN__MODEL__<ROLE>` and
|
|
1744
|
+
`SPAWN__EFFORT__<ROLE>` pins are ignored and `spawn_status()` warns about them.
|
|
1745
|
+
|
|
1746
|
+
The router runs on `claude` / `opus` / `max` (override with the usual
|
|
1747
|
+
`SPAWN__LOOP|MODEL|EFFORT__MODEL_ROUTER` keys). If that run fails, the same pass
|
|
1748
|
+
reruns on the fallback below; a pass that fails twice retries after 6 hours.
|
|
1749
|
+
`model_router_status()` shows current decisions and `model_router_run()` runs a
|
|
1750
|
+
pass now. Set `THREADKEEPER_MODEL_ROUTER_INTERVAL_S=0` to disable the router.
|
|
1751
|
+
|
|
1516
1752
|
Adapters without headless support (Claude Desktop, VS Code) can't be
|
|
1517
1753
|
spawn targets — `spawn_status()` reports them as "no adapter" and any
|
|
1518
1754
|
override pointing at them falls back to the next priority level.
|
|
@@ -1562,6 +1798,38 @@ Three tools keep the memory tidy. `consolidate()` and `forget()` default to
|
|
|
1562
1798
|
|
|
1563
1799
|
## Telemetry
|
|
1564
1800
|
|
|
1801
|
+
### Privacy-safe OTLP traces
|
|
1802
|
+
|
|
1803
|
+
OTLP export is **off by default**. To opt in, set both
|
|
1804
|
+
`THREADKEEPER_OTEL_ENABLED=1` and `THREADKEEPER_OTEL_ENDPOINT` to an
|
|
1805
|
+
OTLP/HTTP `/v1/traces` endpoint. ThreadKeeper sends OTLP JSON asynchronously;
|
|
1806
|
+
collector failure, timeout, or a full queue never delays a local MCP call,
|
|
1807
|
+
spawn, watchdog action, or SQLite write.
|
|
1808
|
+
|
|
1809
|
+
One spawned workflow is linked as `MCP spawn → workflow → agent → child MCP
|
|
1810
|
+
operation`. The W3C trace context is saved with the task and passed only in the
|
|
1811
|
+
private child environment (and its slim MCP config), never in a prompt or CLI
|
|
1812
|
+
argument. A watchdog continuation uses the prior agent span as its parent, so
|
|
1813
|
+
retries remain in the same trace.
|
|
1814
|
+
|
|
1815
|
+
The exporter has a fixed allowlist: operation/tool identifier, latency and
|
|
1816
|
+
queue wait, provider/model identifiers, input/output/total tokens, USD cost,
|
|
1817
|
+
retry count, and terminal outcome. Operation names are fixed; tool/provider
|
|
1818
|
+
identifiers are limited to 64 safe characters, model identifiers to 96, and a
|
|
1819
|
+
process exports at most 64 distinct model values (later values become
|
|
1820
|
+
`unknown`).
|
|
1821
|
+
Everything else is excluded by construction: prompts, MCP arguments/results,
|
|
1822
|
+
memory bodies, user quotes, credentials, paths, task logs, and SQLite event
|
|
1823
|
+
summaries are never span attributes.
|
|
1824
|
+
|
|
1825
|
+
The local SQLite event ledger remains the source of truth. It retains only the
|
|
1826
|
+
opaque trace linkage beside a task row, so its existing task-retention policy
|
|
1827
|
+
also removes that linkage. Configure collector retention at the collector; no
|
|
1828
|
+
trace payload is persisted locally. If traces do not arrive, first verify the
|
|
1829
|
+
endpoint includes `/v1/traces`; then inspect collector connectivity. A full
|
|
1830
|
+
queue intentionally drops its oldest pending spans rather than growing memory
|
|
1831
|
+
or blocking the workflow.
|
|
1832
|
+
|
|
1565
1833
|
- **`mp_dashboard(window_days=7)`** — one-call rollup of the whole
|
|
1566
1834
|
system, read-only. Three sections: **stores** (threads by state,
|
|
1567
1835
|
notes/dialog/distill/concepts counts, skills + claims by tier,
|
|
@@ -1634,7 +1902,9 @@ that keeps a write transaction open for a minute or more.
|
|
|
1634
1902
|
|
|
1635
1903
|
Schema migration uses SQLite `PRAGMA user_version`: a current database skips
|
|
1636
1904
|
legacy `ALTER TABLE` work, while an old or fresh v0 database migrates once
|
|
1637
|
-
under a writer transaction and records the current version.
|
|
1905
|
+
under a writer transaction and records the current version. Every startup
|
|
1906
|
+
also replays the idempotent baseline `CREATE ... IF NOT EXISTS` schema, so a
|
|
1907
|
+
table added after the last version bump still reaches existing databases. Duplicate-column
|
|
1638
1908
|
migrations are the only expected no-op; other DDL errors are logged and raised.
|
|
1639
1909
|
|
|
1640
1910
|
On POSIX systems, startup and `get_db()` harden the default local store
|
|
@@ -1642,9 +1912,10 @@ best-effort: `~/.threadkeeper` is `0700`, while `db.sqlite`, SQLite
|
|
|
1642
1912
|
`-wal`/`-shm` sidecars, `~/.threadkeeper/.env`, curator `REPORT-*.md`
|
|
1643
1913
|
files, and headless spawn logs are owner-only (`0600`).
|
|
1644
1914
|
|
|
1645
|
-
Use `tk-backup` for disaster recovery.
|
|
1646
|
-
|
|
1647
|
-
|
|
1915
|
+
Use `tk-backup` for disaster recovery. In the default SQLite profile it uses
|
|
1916
|
+
`VACUUM INTO`; the encrypted profile uses SQLCipher's online backup API into a
|
|
1917
|
+
new encrypted destination. Both paths include committed frames still living in
|
|
1918
|
+
the live `-wal` sidecar without quiescing background writers:
|
|
1648
1919
|
|
|
1649
1920
|
```bash
|
|
1650
1921
|
tk-backup create ~/threadkeeper-backup.sqlite
|
|
@@ -1667,6 +1938,73 @@ first and copy `db.sqlite`, `db.sqlite-wal`, and `db.sqlite-shm` together. To
|
|
|
1667
1938
|
wipe memory, also stop thread-keeper first, then remove the main DB and both
|
|
1668
1939
|
sidecars.
|
|
1669
1940
|
|
|
1941
|
+
### Opt-in encrypted storage (experimental)
|
|
1942
|
+
|
|
1943
|
+
The default `sqlite` profile is unchanged. For sensitive memory at rest, the
|
|
1944
|
+
optional `sqlcipher` profile encrypts the main database, WAL/rollback-journal
|
|
1945
|
+
pages, and profile-aware snapshots with SQLCipher. SQLCipher temporary
|
|
1946
|
+
structures are kept in memory with cipher memory security enabled. Install it
|
|
1947
|
+
explicitly and initialize a new store before starting any MCP server:
|
|
1948
|
+
|
|
1949
|
+
```bash
|
|
1950
|
+
pip install 'threadkeeper[encrypted]'
|
|
1951
|
+
tk-storage init --db ~/.threadkeeper/secure.sqlite
|
|
1952
|
+
export THREADKEEPER_DB=~/.threadkeeper/secure.sqlite
|
|
1953
|
+
export THREADKEEPER_STORAGE_PROFILE=sqlcipher
|
|
1954
|
+
```
|
|
1955
|
+
|
|
1956
|
+
The profile name may live in normal configuration; the encryption key never
|
|
1957
|
+
does. Thread-keeper generates a random key and retrieves it from the operating
|
|
1958
|
+
system secret store through `keyring`. It never accepts a key in an environment
|
|
1959
|
+
variable, config file, command argument, event, log, or telemetry. If the
|
|
1960
|
+
secret store, SQLCipher backend, or key is unavailable, opening the encrypted
|
|
1961
|
+
profile fails closed. A wrong key cannot initialize or replace an existing
|
|
1962
|
+
database.
|
|
1963
|
+
|
|
1964
|
+
Use the normal backup command after enabling the profile; it recognizes the
|
|
1965
|
+
profile and creates an encrypted destination with its own OS-secret-store key:
|
|
1966
|
+
|
|
1967
|
+
```bash
|
|
1968
|
+
tk-backup create ~/threadkeeper-secure-backup.sqlite
|
|
1969
|
+
tk-backup restore ~/threadkeeper-secure-backup.sqlite --yes
|
|
1970
|
+
tk-storage integrity-check --db ~/.threadkeeper/secure.sqlite
|
|
1971
|
+
tk-storage rekey --db ~/.threadkeeper/secure.sqlite
|
|
1972
|
+
```
|
|
1973
|
+
|
|
1974
|
+
Stop all MCP servers, the daemon host, and CLI sessions before a restore or
|
|
1975
|
+
rekey. If a machine loses power during rekey, normal opens intentionally stop;
|
|
1976
|
+
run `tk-storage recover-rekey --db …` to have the OS secret store validate and
|
|
1977
|
+
finish the pending rotation without printing either key. Back up the operating
|
|
1978
|
+
system account and its secret store according to your platform's recovery
|
|
1979
|
+
procedure. Thread-keeper deliberately has no key-export command: exporting a
|
|
1980
|
+
raw database key into a shell history or ordinary file defeats this profile.
|
|
1981
|
+
|
|
1982
|
+
Move an existing store through a separately validated output path. The source
|
|
1983
|
+
is never replaced, so it remains available until the new store has been
|
|
1984
|
+
verified:
|
|
1985
|
+
|
|
1986
|
+
```bash
|
|
1987
|
+
tk-storage migrate ~/.threadkeeper/db.sqlite ~/.threadkeeper/secure.sqlite \
|
|
1988
|
+
--from-profile sqlite --to-profile sqlcipher
|
|
1989
|
+
tk-storage migrate ~/.threadkeeper/secure.sqlite ~/.threadkeeper/plain.sqlite \
|
|
1990
|
+
--from-profile sqlcipher --to-profile sqlite
|
|
1991
|
+
```
|
|
1992
|
+
|
|
1993
|
+
Only switch `THREADKEEPER_DB` and `THREADKEEPER_STORAGE_PROFILE` after the
|
|
1994
|
+
destination passes `tk-storage integrity-check` (for SQLCipher) or
|
|
1995
|
+
`sqlite3 <path> 'PRAGMA integrity_check'` (for ordinary SQLite). The migration
|
|
1996
|
+
checks integrity plus table/index/trigger coverage before the destination is
|
|
1997
|
+
atomically published.
|
|
1998
|
+
|
|
1999
|
+
This is an at-rest database profile, not full-device encryption. It does not
|
|
2000
|
+
encrypt task logs, curator reports/snapshots, exported briefs, source CLI
|
|
2001
|
+
transcripts, skills, `node.id`, or sync traffic. Put the whole state directory
|
|
2002
|
+
on full-disk encryption, use the existing owner-only permissions, set retention
|
|
2003
|
+
for logs/reports, and use TLS/private networking for sync. The profile is
|
|
2004
|
+
experimental: automated SQLCipher coverage currently runs on Linux; validate
|
|
2005
|
+
the OS keyring integration on macOS or Windows before relying on it for a
|
|
2006
|
+
recovery-sensitive deployment.
|
|
2007
|
+
|
|
1670
2008
|
### Selective Erasure
|
|
1671
2009
|
|
|
1672
2010
|
For one regretted or sensitive conversation, use targeted erasure instead of
|
|
@@ -1702,6 +2040,19 @@ the high-volume tables (`dialog_messages`, `dialog_fts`, `dialog_vec`,
|
|
|
1702
2040
|
`signals`, `events`, `tasks`, `probe_results`) so growth is visible before it
|
|
1703
2041
|
becomes a problem.
|
|
1704
2042
|
|
|
2043
|
+
Terminal Curator passes own their frozen batch text, `REPORT-*`, `AUDIT-*`, and
|
|
2044
|
+
destructive snapshot; Evolve handoffs own their `RESEARCH-*` digest. The
|
|
2045
|
+
retention daemon keeps active/retryable work, recovery material, and endorsed
|
|
2046
|
+
but unapplied reports. After both enabled workflow bounds expire, it first
|
|
2047
|
+
compacts batch/research bodies to a SHA-256 plus a 512-character summary and
|
|
2048
|
+
removes only owned files. A later pass removes the compacted rows. File removal
|
|
2049
|
+
is path-contained to the configured Curator or Evolve directory, rejects
|
|
2050
|
+
symlinks, and is journaled so an interrupted delete retries safely. Use
|
|
2051
|
+
`workflow_retention_preview()` for the dry-run counts/bytes and oldest terminal
|
|
2052
|
+
pass; `mp_dashboard()` shows its latest outcome. A selective `forget()` that
|
|
2053
|
+
matches a frozen workflow body compacts it immediately, including active work;
|
|
2054
|
+
the normal report/lesson review guidance still applies to generalized content.
|
|
2055
|
+
|
|
1705
2056
|
`db_compact()` is the opt-in disk-reclaim tool: `VACUUM` + a mandatory
|
|
1706
2057
|
`dialog_fts` rebuild (schema v2 keys the FTS index on `dialog_messages`
|
|
1707
2058
|
rowids, which `VACUUM` is permitted to renumber — the rebuild is what
|
|
@@ -1868,9 +2219,9 @@ reusable verdict logic lives in `threadkeeper/verify_ingest.py`.
|
|
|
1868
2219
|
## Memory-quality evaluation
|
|
1869
2220
|
|
|
1870
2221
|
The ingest verifier above answers *"did we capture the data?"*. The
|
|
1871
|
-
memory-quality harness answers
|
|
1872
|
-
|
|
1873
|
-
|
|
2222
|
+
memory-quality harness answers two separate questions: *"did we retrieve the
|
|
2223
|
+
right evidence?"* and *"does an agent produce the right final answer from
|
|
2224
|
+
that evidence?"* It's modeled on
|
|
1874
2225
|
[LongMemEval](https://arxiv.org/pdf/2410.10813) (ICLR 2025) plus mem0's 2026
|
|
1875
2226
|
[tokens-per-retrieval](https://mem0.ai/blog/ai-memory-benchmarks-in-2026)
|
|
1876
2227
|
cost axis, and runs the **real** `search()` / `dialog_search()` / `brief()`
|
|
@@ -1881,24 +2232,34 @@ python scripts/memory_eval/run.py # bundled demo corpus, lexical
|
|
|
1881
2232
|
python scripts/memory_eval/run.py --json # machine-readable report
|
|
1882
2233
|
python scripts/memory_eval/run.py --db snap.sqlite --ground-truth my_labels.json
|
|
1883
2234
|
python scripts/memory_eval/run.py --semantic # use embeddings if installed
|
|
1884
|
-
python scripts/memory_eval/run.py --
|
|
2235
|
+
python scripts/memory_eval/run.py --matrix --strict # FTS/hybrid migration gate
|
|
2236
|
+
age -d holdout.json.age | python scripts/memory_eval/run.py \
|
|
2237
|
+
--ground-truth /dev/stdin --private-holdout # no per-case output
|
|
1885
2238
|
```
|
|
1886
2239
|
|
|
1887
|
-
It reports
|
|
2240
|
+
It reports these headline groups over a fixed ground-truth set:
|
|
1888
2241
|
|
|
1889
|
-
- **
|
|
1890
|
-
|
|
1891
|
-
|
|
2242
|
+
- **evidence recall** and **final-answer correctness** — separate scores, both
|
|
2243
|
+
per axis. The final answer comes from a deterministic replay responder that
|
|
2244
|
+
may emit only facts found in the returned context; it gives CI a stable
|
|
2245
|
+
boundary between retrieval and reasoning failures.
|
|
1892
2246
|
- **abstention rate** — of the *never-happened* questions, the fraction the
|
|
1893
|
-
system correctly refused.
|
|
1894
|
-
|
|
1895
|
-
stale facts.
|
|
2247
|
+
system correctly refused. Retention/curation removals and egress-blocked
|
|
2248
|
+
personal memory are also required to end in a refusal.
|
|
1896
2249
|
- **tokens-per-retrieval** — mean / median / max tokens of what each query
|
|
1897
|
-
returned,
|
|
1898
|
-
recalls more also costs more).
|
|
2250
|
+
returned, plus equivalent final-answer token totals.
|
|
1899
2251
|
- **retrieval latency** — mean / p50 / p95 / max wall-clock milliseconds. With
|
|
1900
2252
|
`--semantic`, the backend is reported as `hybrid`, because dense candidates
|
|
1901
2253
|
augment rather than replace FTS.
|
|
2254
|
+
- **outcome classification** — retrieval failure, reasoning failure,
|
|
2255
|
+
stale-memory use, removed-memory use, and privacy-policy violation.
|
|
2256
|
+
|
|
2257
|
+
`--matrix` runs every public-fixture case through the FTS and hybrid backends,
|
|
2258
|
+
three fixture states (`baseline`, `retained`, `curated`), and every personal
|
|
2259
|
+
memory egress policy (`all`, `same-vendor`, `work-only`). A hybrid row is marked
|
|
2260
|
+
unavailable when the optional semantic extra is not installed. `--strict`
|
|
2261
|
+
requires the versioned fixture thresholds and zero safety violations, making
|
|
2262
|
+
the same command suitable for retrieval/model migration gates.
|
|
1902
2263
|
|
|
1903
2264
|
For DB concurrency, run the reproducible local gate:
|
|
1904
2265
|
|
|
@@ -1910,18 +2271,20 @@ The JSON result includes expected/actual writes, throughput, p50/p95/p99/max
|
|
|
1910
2271
|
write latency, worker errors, elapsed time, and `PRAGMA quick_check`; a non-zero
|
|
1911
2272
|
exit means a lost write, worker failure, or integrity failure.
|
|
1912
2273
|
|
|
1913
|
-
With no `--db` the harness builds the bundled
|
|
1914
|
-
(`scripts/memory_eval/ground_truth.json` — a fictional "billing
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
2274
|
+
With no `--db` the harness builds the bundled, versioned public synthetic
|
|
2275
|
+
fixture (`scripts/memory_eval/ground_truth.json` — a fictional "billing
|
|
2276
|
+
service") into a throwaway DB. Bump its `version` whenever cases, labels, or
|
|
2277
|
+
thresholds change; CI uses the checked-in version and its stable thresholds.
|
|
2278
|
+
`--db` runs **read-only**: the snapshot is copied to a temp file and the
|
|
2279
|
+
original is never opened for writing.
|
|
2280
|
+
|
|
2281
|
+
For a private holdout, keep the corpus encrypted or outside the repository and
|
|
2282
|
+
pass it with `--private-holdout`. The runner does not decrypt data itself; it
|
|
2283
|
+
accepts a locally decrypted file or stdin and deletes its temporary DB on exit.
|
|
2284
|
+
Private mode emits aggregate metrics only — no case rows, failure details,
|
|
2285
|
+
retrieved context, or replayed answer is written to stdout, artifacts, or the
|
|
2286
|
+
repository. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the corpus
|
|
2287
|
+
contract and migration-gate interpretation.
|
|
1925
2288
|
|
|
1926
2289
|
## Evaluating learning-loop decision quality
|
|
1927
2290
|
|
|
@@ -2023,7 +2386,9 @@ threadkeeper/
|
|
|
2023
2386
|
so `tools/list` carries MCP 2025-06-18 `ToolAnnotations` for every
|
|
2024
2387
|
registered tool: `readOnlyHint=True` for pure reads (`brief`, `context`, `search`,
|
|
2025
2388
|
`dialog_search`, the status tools, …) and `readOnlyHint=False`
|
|
2026
|
-
for mutations.
|
|
2389
|
+
for mutations. Advertised reads use query-only database connections and never
|
|
2390
|
+
bootstrap a session, start daemon work, refresh process data, or mutate
|
|
2391
|
+
lifecycle state. `lesson_list` / `lesson_get` are classified as non-destructive
|
|
2027
2392
|
writes because they bump lesson access counters. Delete/overwrite/kill tools
|
|
2028
2393
|
carry `destructiveHint=True` (`compost` is read-only — it only surfaces idle
|
|
2029
2394
|
threads). A confirmation/elicitation host reads this to decide which calls
|
|
@@ -2038,6 +2403,15 @@ capability before prompting, uses only a flat primitive schema, and leaves
|
|
|
2038
2403
|
unsupported clients on the existing text/tool fallback path. The first protected
|
|
2039
2404
|
write is `dialectic_supersede`.
|
|
2040
2405
|
|
|
2406
|
+
**Protocol compatibility (#344).** The stdio server supports the
|
|
2407
|
+
`2025-11-25` initialize-handshake era and the `2026-07-28` request-envelope
|
|
2408
|
+
era. Legacy hosts retain the existing tools, resources, and prompts. Modern
|
|
2409
|
+
calls derive identity and capabilities from every request, static discovery
|
|
2410
|
+
catalogs advertise public cache freshness, and replaying a completed write with
|
|
2411
|
+
the same caller-bound JSON-RPC id returns the original result instead of
|
|
2412
|
+
committing a second mutation. Sealed continuation state expires after five
|
|
2413
|
+
minutes; an interrupted write with no terminal result is rejected on replay.
|
|
2414
|
+
|
|
2041
2415
|
Detailed map in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
|
2042
2416
|
Open work in [docs/ROADMAP.md](docs/ROADMAP.md) and the
|
|
2043
2417
|
[Issues tab](https://github.com/po4erk91/thread-keeper/issues).
|