threadkeeper 0.19.1__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.1/threadkeeper.egg-info → threadkeeper-0.20.0}/PKG-INFO +299 -47
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/README.md +294 -45
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/pyproject.toml +13 -5
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_curator.py +487 -17
- threadkeeper-0.20.0/tests/test_dashboard_app.py +143 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_db_schema.py +85 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_egress_policy.py +10 -3
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_evolve_applier.py +244 -5
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_host_env.py +38 -1
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_lessons.py +596 -22
- threadkeeper-0.20.0/tests/test_mcp_server_instructions.py +139 -0
- {threadkeeper-0.19.1 → 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.1 → 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.1 → threadkeeper-0.20.0}/tests/test_release_workflows.py +18 -4
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_setup.py +1 -1
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_skill_tier.py +137 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_skill_updater.py +69 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_skill_use_parser.py +33 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_skills.py +160 -37
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_spawn_config.py +193 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_spawn_watchdog.py +32 -0
- threadkeeper-0.20.0/tests/test_storage.py +256 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_tool_annotations.py +4 -3
- threadkeeper-0.20.0/tests/test_tool_profiles.py +170 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/_mcp.py +32 -1
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/_setup.py +7 -55
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/_spawn_wrap.py +14 -1
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/adapters/base.py +5 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/adapters/codex.py +1 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/agent_metadata.py +15 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/agent_status.py +104 -17
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/assets/macos-agent-status/README.md +5 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/assets/macos-agent-status/ThreadKeeperAgentStatus.swift +49 -13
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/backup.py +37 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/config.py +44 -3
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/curator.py +815 -262
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/curator_snapshots.py +93 -0
- threadkeeper-0.20.0/threadkeeper/dashboard_app.py +411 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/db.py +145 -26
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/evolve_applier.py +170 -32
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/github_budget.py +9 -1
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/host.py +42 -17
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/ingest.py +22 -1
- threadkeeper-0.20.0/threadkeeper/instructions.py +80 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/lessons.py +130 -6
- threadkeeper-0.20.0/threadkeeper/mcp_tasks.py +327 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/migrate_embeddings.py +3 -2
- {threadkeeper-0.19.1 → 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.1 → threadkeeper-0.20.0}/threadkeeper/notify.py +1 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/server.py +10 -1
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/skill_audit.py +4 -1
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/skill_updater.py +41 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/spawn_budget.py +31 -23
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/spawn_config.py +166 -45
- threadkeeper-0.20.0/threadkeeper/storage.py +740 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/sync/migrate.py +13 -13
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/sync/protocol.py +3 -1
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/sync/reset_node.py +5 -2
- threadkeeper-0.20.0/threadkeeper/tool_profiles.py +364 -0
- threadkeeper-0.20.0/threadkeeper/tools/capabilities.py +15 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/curator.py +34 -6
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/dashboard.py +1 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/evolve_applier.py +20 -1
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/lessons.py +577 -256
- threadkeeper-0.20.0/threadkeeper/tools/model_router.py +152 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/skills.py +139 -42
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/spawn.py +19 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/workflow_artifacts.py +7 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0/threadkeeper.egg-info}/PKG-INFO +299 -47
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper.egg-info/SOURCES.txt +19 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper.egg-info/entry_points.txt +1 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper.egg-info/requires.txt +5 -1
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/LICENSE +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/setup.cfg +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_adapters.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_agent_status.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_antigravity_ingest.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_auto_update.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_backup.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_brief_footprint.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_brief_sections.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_candidate_reviewer.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_ci_parallel.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_concepts.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_config_settings.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_config_watcher.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_core_memory.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_daemon_host_config.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_daemon_host_integration.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_daemon_liveness.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_daemon_state.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_dashboard.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_db_compact.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_db_contention.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_db_leak_guard.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_db_migration_fts.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_db_runtime.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_delegated_search.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_dialectic.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_dialectic_feed_tools.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_dialectic_miner.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_dialectic_observation_resolve.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_dialectic_recompute.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_dialectic_tier.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_dialectic_validator.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_dialog_fts_external.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_documentation_inventory.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_embed_routing.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_embedding_generation.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_embedding_model_revision.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_embedding_storage_dedup.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_error_paths.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_eval_harness.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_evolve_apply_2.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_evolve_apply_3.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_evolve_apply_43.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_evolve_apply_45.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_evolve_daemon.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_extract_daemon.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_extract_dedup.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_forget.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_github_budget.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_github_safety.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_helpers.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_hook_child_guard.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_host_embed.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_host_main.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_host_wedged.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_hybrid_retrieval.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_i18n_multilang.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_identity.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_ingest_cursor.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_ingest_denylist.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_ingest_secret_redaction.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_ingest_status.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_ingest_transactions.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_lesson_violations.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_mcp_protocol_conformance.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_mcp_resources_prompts.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_mcp_sdk_compat.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_memory_authority_lifecycle.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_memory_compatibility.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_memory_eval.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_memory_guard.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_memory_guard_host.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_missed_spawns.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_model_catalog.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_notify_daemon.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_nudges.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_onnx_embeddings.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_panel.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_permissions.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_pickup.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_probe_daemon.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_process_health.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_project_scope.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_retention.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_review_prompts.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_search_fts_punctuation.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_session_read_purity.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_shadow_review.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_single_flight_prompt_prefixes.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_skill_hint.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_skill_passive_tier.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_skill_watcher.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_spawn_background_cwd.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_spawn_budget.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_spawn_child_env.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_spawn_codex_stdin.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_spawn_hint.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_spawn_permission_gate.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_spawn_reap.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_spawn_result.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_spawn_slim.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_spawn_worktree_isolation.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_spawn_wrap.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_sync_capture.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_sync_identity.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_sync_migrate.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_sync_protocol.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_sync_reset_node.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_sync_transport.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_task_retention.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_task_spool.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_thin_session.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_thread_janitor.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_threads.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_tools_smoke.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_tracing.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_validate_threads.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_vec_search.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_verify_ingest.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/tests/test_workflow_retention.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/__init__.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/adapters/__init__.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/adapters/_hook_helpers.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/adapters/antigravity.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/adapters/claude_code.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/adapters/claude_desktop.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/adapters/copilot.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/adapters/vscode.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/assets/macos-agent-status/Info.plist +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/assets/macos-agent-status/build.sh +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/assets/macos-agent-status/install.sh +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/authority.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/auto_update.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/brief.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/candidate_reviewer.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/config_io.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/config_watcher.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/daemon_liveness.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/daemon_state.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/daemon_supervisor.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/dialectic_miner.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/dialectic_validator.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/egress.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/elicitation.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/embeddings.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/eval/__init__.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/eval/__main__.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/eval/harness.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/evolve_daemon.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/extract_daemon.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/forget.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/github_safety.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/harvest.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/helpers.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/host_embed.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/i18n.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/identity.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/lesson_violations.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/link_health.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/mcp_skills.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/memory_compat.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/memory_guard.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/menubar_app.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/nudges.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/permissions.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/probe_daemon.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/process_health.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/project_scope.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/protocol.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/read_context.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/retention.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/retrieval.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/review_prompts.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/search_proxy.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/shadow_review.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/skill_watcher.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/spawn_result.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/sync/__init__.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/sync/capture.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/sync/daemon.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/sync/identity.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/sync/server.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/task_spool.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/thread_janitor.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tool_schemas.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/__init__.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/agent_status.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/authority.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/candidate_reviewer.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/concepts.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/config_watch.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/consolidate.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/core_memory.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/correlation.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/db_maintenance.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/dialectic.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/dialectic_feed.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/dialog.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/distill.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/evolve_research.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/extract.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/forget.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/graph.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/invariants.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/memory_guard.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/missed_spawns.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/panel.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/peers.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/pickup.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/probes.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/process_health.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/project_scope.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/prompts.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/resources.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/session.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/shadow_review.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/style.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/sync.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/threads.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tools/validate.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/tracing.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/trash.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/verify_ingest.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper/wikilinks.py +0 -0
- {threadkeeper-0.19.1 → threadkeeper-0.20.0}/threadkeeper.egg-info/dependency_links.txt +0 -0
- {threadkeeper-0.19.1 → 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:
|
|
@@ -234,6 +243,7 @@ read/act split, plus MCP elicitation for host-native confirmations:
|
|
|
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
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
|
|
@@ -337,9 +383,13 @@ cross-project recall. Those results show `scope=`/`project=` provenance and
|
|
|
337
383
|
write a `cross_project_recall` audit event. The `memory_scope_status()` tool
|
|
338
384
|
shows the current namespace and the migration accounting.
|
|
339
385
|
|
|
340
|
-
Existing databases
|
|
341
|
-
repository provenance
|
|
342
|
-
|
|
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
|
|
343
393
|
boundary controls *which local memory is recalled*; it is separate from
|
|
344
394
|
`THREADKEEPER_MEMORY_EGRESS`, which controls whether personal memory may be
|
|
345
395
|
sent to another model provider.
|
|
@@ -688,9 +738,23 @@ can correct one unique substring without reserializing a lesson. Shadow-origin
|
|
|
688
738
|
though an existing same-slug long lesson may be corrected without increasing
|
|
689
739
|
its body size; near-duplicate slugs are blocked, and semantic body matches are
|
|
690
740
|
routed to the incumbent lesson or surfaced for curation instead of minting a
|
|
691
|
-
sibling lesson.
|
|
692
|
-
|
|
693
|
-
|
|
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`.
|
|
694
758
|
Before a genuinely new fallback lesson is written,
|
|
695
759
|
`lesson_neighbors(title, body, summary, k=3)` shows the nearest existing lesson
|
|
696
760
|
slugs (semantic, with a lexical fallback). Shadow and candidate reviewers use
|
|
@@ -790,10 +854,10 @@ flags a dense lesson subtopic when at least
|
|
|
790
854
|
concrete title terms and a non-generic body mechanism. Generic joins and
|
|
791
855
|
domain-wide/high-document-frequency title words cannot form a candidate. The
|
|
792
856
|
inventory and `curator_review_status()` report emitted/rejected counts and
|
|
793
|
-
rejection reasons, so the rule can be tuned from production data.
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
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
|
|
797
861
|
file, but has no web tools. External research is off by default. With explicit
|
|
798
862
|
`THREADKEEPER_CURATOR_WEB_RESEARCH=1`, a separate Claude-only researcher gets
|
|
799
863
|
only fixed public compatibility queries, `WebSearch`, `WebFetch`, and one
|
|
@@ -802,11 +866,44 @@ repository. The evaluator receives that handoff as fenced, untrusted data and
|
|
|
802
866
|
writes numbered per-skill verdicts to
|
|
803
867
|
`~/.threadkeeper/curator/REPORT-<isodate>.md` for a one-batch pass or
|
|
804
868
|
`REPORT-<isodate>-batch-NNN-of-MMM.md` for a multi-batch pass: KEEP / REPAIR /
|
|
805
|
-
UPDATE / MERGE / SPLIT / DEPRECATE / DELETE / CROSS_LINK /
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
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
|
|
810
907
|
single-flight across processes — a non-blocking `fcntl.flock` pidfile
|
|
811
908
|
(`<db dir>/curator.lock`) plus a running-children check serialize it, so
|
|
812
909
|
multiple MCP server instances can't run overlapping (now destructive) passes
|
|
@@ -868,11 +965,13 @@ SHA-256 as provenance. Research and evaluation are two phases of each batch
|
|
|
868
965
|
inside the durable pass: the web-free evaluator for a batch starts only after
|
|
869
966
|
that batch's handoff validates. A researcher that fails or leaves a missing,
|
|
870
967
|
malformed, swapped, or mismatched handoff is retried (three attempts); after
|
|
871
|
-
that the batch gets a non-mutating evaluator that
|
|
872
|
-
|
|
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
|
|
873
970
|
recovery snapshot right before its first mutating evaluator.
|
|
874
971
|
`THREADKEEPER_CURATOR_WEB_RESEARCH=0` skips the research phase entirely (and
|
|
875
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.
|
|
876
975
|
Each report path is then
|
|
877
976
|
authorized in the existing parent-authored `curator_pass` event;
|
|
878
977
|
`curator_report_write` records its SHA-256 in `curator_report_provenance`.
|
|
@@ -880,22 +979,22 @@ This makes the report directory an untrusted transport: a stray or forged
|
|
|
880
979
|
`REPORT-*.md` file cannot acquire the provenance needed by the applier.
|
|
881
980
|
|
|
882
981
|
The web-enabled researcher never receives lesson, skill, or concept mutation
|
|
883
|
-
tools. The evaluator never receives web tools.
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
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
|
|
887
986
|
`THREADKEEPER_CURATOR_DESTRUCTIVE=0` for advisory REPORT-only. Pinned and
|
|
888
|
-
untracked skills remain protected. Foreground-authored skills are
|
|
889
|
-
default
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
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
|
|
893
992
|
stamped with an explicit `origin=<THREADKEEPER_WRITE_ORIGIN>` marker when
|
|
894
993
|
appended; missing, legacy, or unknown lesson provenance is protected by
|
|
895
994
|
default. `lesson_remove` and `skill_manage(action='delete')` refuse protected
|
|
896
995
|
foreground/unknown-origin entries unless `force=True` is called from a
|
|
897
|
-
foreground writer
|
|
898
|
-
`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
|
|
899
998
|
a recoverable snapshot under
|
|
900
999
|
`<reports_dir>/snapshots/<pass-id>/` (default
|
|
901
1000
|
`~/.threadkeeper/curator/snapshots/<pass-id>/`). The snapshot contains
|
|
@@ -911,7 +1010,8 @@ Use `curator_restore(pass_id, lesson_slug="...")` or
|
|
|
911
1010
|
As a prevention layer before recovery is needed, a destructive Curator pass
|
|
912
1011
|
has one server-side shared admission budget for `lesson_remove` and
|
|
913
1012
|
`skill_manage(action='delete')`, including across bounded child batches.
|
|
914
|
-
`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
|
|
915
1015
|
disable those autonomous deletes. The pass ID makes the count durable and
|
|
916
1016
|
cross-process, while foreground/human deletes are unaffected. `mp_dashboard`
|
|
917
1017
|
shows admitted and refused operations with `status=HIT` when the Curator reaches
|
|
@@ -945,9 +1045,21 @@ The shared lesson file has its own write serialization: `lesson_append`,
|
|
|
945
1045
|
`lessons.md.lock` around file creation/read/mutate/write, so foreground calls
|
|
946
1046
|
and learning-loop children cannot last-writer-win over each other's sections.
|
|
947
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
|
+
|
|
948
1058
|
Lesson access is tracked the same way skill access is: `lesson_list` increments
|
|
949
1059
|
`lesson_usage.view_count` for displayed rows and `lesson_get` increments
|
|
950
|
-
`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
|
|
951
1063
|
ranked `STALE LESSONS (dry-run decay ranking)` section computed as
|
|
952
1064
|
`access_frequency × exp(-days_since_access / tau)`, filtered to unprotected
|
|
953
1065
|
lessons with no recent access and low pull-count. That decay list is advisory
|
|
@@ -1113,8 +1225,9 @@ If a killed child leaves an unresolved merge or plain tracked WIP in the default
|
|
|
1113
1225
|
auto-managed checkout, the next code-producing pass archives the diff before
|
|
1114
1226
|
recovering it. Merge recovery remains limited to `roadmap/…`/`evolve/…`
|
|
1115
1227
|
branches whose exact PR is confirmed open or merged. For an open PR, the parent
|
|
1116
|
-
archives the interrupted merge
|
|
1117
|
-
|
|
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
|
|
1118
1231
|
leftover merge is discarded as stale. Plain abandoned WIP is recoverable on
|
|
1119
1232
|
those applier branches when PR state is readable, and also on the configured
|
|
1120
1233
|
base branch: the disposable base can contain orphaned edits when an older child
|
|
@@ -1207,6 +1320,11 @@ daily deterministic `docs/roadmap-audit-YYYY-MM-DD` branch so repeated audit
|
|
|
1207
1320
|
passes update or skip the existing roadmap PR rather than opening a second one.
|
|
1208
1321
|
Automatic apply passes respect the configured interval so multiple foreground
|
|
1209
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.
|
|
1210
1328
|
Manual tools such as `evolve_apply_conflicted_pr()` and
|
|
1211
1329
|
`evolve_apply_roadmap_issue()` dispatch immediately. If no conflicted applier PR
|
|
1212
1330
|
or roadmap issue is startable, the pass falls back to Curator reports and then
|
|
@@ -1384,8 +1502,45 @@ Adding a new language is a two-file PR — see [CONTRIBUTING.md](CONTRIBUTING.md
|
|
|
1384
1502
|
|
|
1385
1503
|
The most-used env knobs (full list in `threadkeeper/config.py`):
|
|
1386
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
|
+
|
|
1387
1538
|
| Knob | Default | Purpose |
|
|
1388
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 |
|
|
1389
1544
|
| `THREADKEEPER_DB` | `~/.threadkeeper/db.sqlite` | SQLite file |
|
|
1390
1545
|
| `THREADKEEPER_TASK_LOG_DIR` | `~/.threadkeeper/tasks` | owner-only task spool for spawn logs, stdin prompts, command scripts, and small runtime logs |
|
|
1391
1546
|
| `THREADKEEPER_RETENTION_INTERVAL_S` | 0 (off) | SQLite retention/compaction daemon tick; 0 disables the daemon |
|
|
@@ -1434,11 +1589,11 @@ The most-used env knobs (full list in `threadkeeper/config.py`):
|
|
|
1434
1589
|
| `THREADKEEPER_CANDIDATE_REVIEW_BATCH_SIZE` | 40 | most pending candidates (oldest first) one reviewer child receives; the rest stay pending for the next pass |
|
|
1435
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 |
|
|
1436
1591
|
| `THREADKEEPER_CURATOR_INTERVAL_S` | 259200 | deep curator audit every three days; set `0` to disable |
|
|
1437
|
-
| `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 |
|
|
1438
1593
|
| `THREADKEEPER_CURATOR_MIN_LESSONS` | 3 | min lessons before curator engages |
|
|
1439
|
-
| `THREADKEEPER_CURATOR_WEB_RESEARCH` | `0` (off) | explicit consent for the isolated Curator web researcher; it receives only fixed public compatibility queries,
|
|
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 |
|
|
1440
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 |
|
|
1441
|
-
| `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 |
|
|
1442
1597
|
| `THREADKEEPER_CURATOR_SNAPSHOT_RETENTION` | 10 | number of destructive curator pre-mutation snapshots to retain under `<reports_dir>/snapshots`; current pass is always retained |
|
|
1443
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 |
|
|
1444
1599
|
| `THREADKEEPER_PROBE_INTERVAL_S` | 0 (off) | probe daemon tick (s); 1800 = 30 min recommended so finished probe answers are graded promptly |
|
|
@@ -1480,6 +1635,10 @@ The most-used env knobs (full list in `threadkeeper/config.py`):
|
|
|
1480
1635
|
| `THREADKEEPER_SKILL_NUDGE_INTERVAL` | 10 | events between `skill_hint` nudges |
|
|
1481
1636
|
| `THREADKEEPER_DIALECTIC_MINE_INTERVAL_S` | 0 (off) | dialectic_miner daemon tick (s); 0 disables mechanical observation capture |
|
|
1482
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 |
|
|
1483
1642
|
| `THREADKEEPER_DIALECTIC_VALIDATE_MIN` | 5 | min buffered observations before validator engages |
|
|
1484
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) |
|
|
1485
1644
|
| `THREADKEEPER_DIALECTIC_VALIDATE_BATCH_SIZE` | 50 | max observations sent to one validator child; prevents oversized prompts and drains large queues incrementally |
|
|
@@ -1546,7 +1705,7 @@ THREADKEEPER_SPAWN__DEFAULT=claude
|
|
|
1546
1705
|
# supported CLI keys: claude, codex, antigravity (agy executable), copilot
|
|
1547
1706
|
THREADKEEPER_SPAWN__LOOP__SHADOW_OBSERVER=claude # heaviest reasoning → keep on Claude
|
|
1548
1707
|
THREADKEEPER_SPAWN__LOOP__CURATOR=codex # weekly audit → Codex is fine
|
|
1549
|
-
THREADKEEPER_SPAWN__LOOP__CANDIDATE_REVIEWER=auto # "auto" =
|
|
1708
|
+
THREADKEEPER_SPAWN__LOOP__CANDIDATE_REVIEWER=auto # "auto" = model router decides (active CLI until it has)
|
|
1550
1709
|
# model pin per CLI or per role: THREADKEEPER_SPAWN__MODEL__<KEY>=<model>
|
|
1551
1710
|
THREADKEEPER_SPAWN__MODEL__CLAUDE=opus
|
|
1552
1711
|
THREADKEEPER_SPAWN__MODEL__CODEX=gpt-5.5
|
|
@@ -1559,7 +1718,8 @@ THREADKEEPER_SPAWN__EFFORT__CURATOR=xhigh
|
|
|
1559
1718
|
```
|
|
1560
1719
|
|
|
1561
1720
|
Resolution per role: `SPAWN__LOOP__<role>` → `SPAWN__DEFAULT` → active CLI →
|
|
1562
|
-
`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
|
|
1563
1723
|
variables override the `.env`. Force host detection with
|
|
1564
1724
|
`THREADKEEPER_ACTIVE_CLI=claude` (or `codex`, `antigravity`/`agy`,
|
|
1565
1725
|
`copilot`). `agy` is normalized to `antigravity`. See `.env.example`
|
|
@@ -1567,6 +1727,28 @@ for the full knob list. `spawn_status()` includes warnings when a configured
|
|
|
1567
1727
|
spawn CLI is unsupported or a model key does not match a supported CLI/startup
|
|
1568
1728
|
role, while keeping the same fallback resolution.
|
|
1569
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
|
+
|
|
1570
1752
|
Adapters without headless support (Claude Desktop, VS Code) can't be
|
|
1571
1753
|
spawn targets — `spawn_status()` reports them as "no adapter" and any
|
|
1572
1754
|
override pointing at them falls back to the next priority level.
|
|
@@ -1720,7 +1902,9 @@ that keeps a write transaction open for a minute or more.
|
|
|
1720
1902
|
|
|
1721
1903
|
Schema migration uses SQLite `PRAGMA user_version`: a current database skips
|
|
1722
1904
|
legacy `ALTER TABLE` work, while an old or fresh v0 database migrates once
|
|
1723
|
-
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
|
|
1724
1908
|
migrations are the only expected no-op; other DDL errors are logged and raised.
|
|
1725
1909
|
|
|
1726
1910
|
On POSIX systems, startup and `get_db()` harden the default local store
|
|
@@ -1728,9 +1912,10 @@ best-effort: `~/.threadkeeper` is `0700`, while `db.sqlite`, SQLite
|
|
|
1728
1912
|
`-wal`/`-shm` sidecars, `~/.threadkeeper/.env`, curator `REPORT-*.md`
|
|
1729
1913
|
files, and headless spawn logs are owner-only (`0600`).
|
|
1730
1914
|
|
|
1731
|
-
Use `tk-backup` for disaster recovery.
|
|
1732
|
-
|
|
1733
|
-
|
|
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:
|
|
1734
1919
|
|
|
1735
1920
|
```bash
|
|
1736
1921
|
tk-backup create ~/threadkeeper-backup.sqlite
|
|
@@ -1753,6 +1938,73 @@ first and copy `db.sqlite`, `db.sqlite-wal`, and `db.sqlite-shm` together. To
|
|
|
1753
1938
|
wipe memory, also stop thread-keeper first, then remove the main DB and both
|
|
1754
1939
|
sidecars.
|
|
1755
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
|
+
|
|
1756
2008
|
### Selective Erasure
|
|
1757
2009
|
|
|
1758
2010
|
For one regretted or sensitive conversation, use targeted erasure instead of
|