threadkeeper 0.18.3__tar.gz → 0.19.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.18.3/threadkeeper.egg-info → threadkeeper-0.19.0}/PKG-INFO +66 -19
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/README.md +64 -17
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/pyproject.toml +5 -7
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_daemon_liveness.py +1 -1
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_dialectic_tier.py +3 -9
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_error_paths.py +1 -1
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_eval_harness.py +15 -1
- threadkeeper-0.19.0/tests/test_evolve_apply_43.py +97 -0
- threadkeeper-0.19.0/tests/test_evolve_apply_45.py +107 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_mcp_resources_prompts.py +3 -4
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_mcp_sdk_compat.py +4 -5
- threadkeeper-0.19.0/tests/test_mcp_skills_extension.py +183 -0
- threadkeeper-0.19.0/tests/test_memory_authority_lifecycle.py +87 -0
- threadkeeper-0.19.0/tests/test_memory_compatibility.py +118 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_onnx_embeddings.py +4 -3
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_release_workflows.py +7 -11
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_tool_annotations.py +1 -1
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/_mcp.py +47 -24
- threadkeeper-0.19.0/threadkeeper/authority.py +322 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/brief.py +106 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/config.py +9 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/db.py +109 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/dialectic_validator.py +6 -5
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/elicitation.py +1 -2
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/embeddings.py +136 -30
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/eval/harness.py +95 -10
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/forget.py +12 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/ingest.py +9 -0
- threadkeeper-0.19.0/threadkeeper/mcp_skills.py +327 -0
- threadkeeper-0.19.0/threadkeeper/memory_compat.py +356 -0
- threadkeeper-0.19.0/threadkeeper/migrate_embeddings.py +244 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/server.py +1 -0
- threadkeeper-0.19.0/threadkeeper/tools/authority.py +21 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/dashboard.py +5 -1
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/dialectic.py +83 -9
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/extract.py +5 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/lessons.py +46 -1
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/pickup.py +6 -1
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/session.py +14 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/skills.py +65 -11
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/style.py +16 -2
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/threads.py +28 -3
- {threadkeeper-0.18.3 → threadkeeper-0.19.0/threadkeeper.egg-info}/PKG-INFO +66 -19
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper.egg-info/SOURCES.txt +9 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper.egg-info/requires.txt +1 -1
- threadkeeper-0.18.3/threadkeeper/migrate_embeddings.py +0 -162
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/LICENSE +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/setup.cfg +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_adapters.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_agent_status.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_antigravity_ingest.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_auto_update.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_backup.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_brief_footprint.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_brief_sections.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_candidate_reviewer.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_ci_parallel.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_concepts.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_config_settings.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_config_watcher.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_core_memory.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_curator.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_daemon_host_config.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_daemon_host_integration.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_daemon_state.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_dashboard.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_db_compact.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_db_contention.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_db_leak_guard.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_db_migration_fts.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_db_runtime.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_db_schema.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_delegated_search.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_dialectic.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_dialectic_feed_tools.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_dialectic_miner.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_dialectic_observation_resolve.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_dialectic_recompute.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_dialectic_validator.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_dialog_fts_external.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_documentation_inventory.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_egress_policy.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_embed_routing.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_embedding_generation.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_embedding_model_revision.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_embedding_storage_dedup.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_evolve_applier.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_evolve_apply_2.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_evolve_apply_3.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_evolve_daemon.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_extract_daemon.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_extract_dedup.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_forget.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_github_budget.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_github_safety.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_helpers.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_hook_child_guard.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_host_embed.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_host_env.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_host_main.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_host_wedged.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_hybrid_retrieval.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_i18n_multilang.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_identity.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_ingest_cursor.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_ingest_denylist.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_ingest_secret_redaction.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_ingest_status.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_ingest_transactions.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_lesson_violations.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_lessons.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_memory_eval.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_memory_guard.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_memory_guard_host.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_menubar_app.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_missed_spawns.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_model_catalog.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_notify_daemon.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_nudges.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_panel.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_permissions.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_pickup.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_probe_daemon.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_process_health.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_retention.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_review_prompts.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_search_fts_punctuation.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_session_read_purity.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_setup.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_shadow_review.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_single_flight_prompt_prefixes.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_skill_hint.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_skill_passive_tier.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_skill_tier.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_skill_updater.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_skill_use_parser.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_skill_watcher.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_skills.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_spawn_background_cwd.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_spawn_budget.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_spawn_child_env.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_spawn_codex_stdin.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_spawn_config.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_spawn_hint.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_spawn_permission_gate.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_spawn_reap.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_spawn_result.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_spawn_slim.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_spawn_watchdog.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_spawn_worktree_isolation.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_spawn_wrap.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_sync_capture.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_sync_identity.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_sync_migrate.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_sync_protocol.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_sync_reset_node.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_sync_transport.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_task_retention.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_task_spool.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_thin_session.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_thread_janitor.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_threads.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_tools_smoke.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_validate_threads.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_vec_search.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/tests/test_verify_ingest.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/__init__.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/_setup.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/_spawn_wrap.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/adapters/__init__.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/adapters/_hook_helpers.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/adapters/antigravity.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/adapters/base.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/adapters/claude_code.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/adapters/claude_desktop.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/adapters/codex.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/adapters/copilot.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/adapters/vscode.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/agent_metadata.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/agent_status.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/assets/macos-agent-status/Info.plist +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/assets/macos-agent-status/README.md +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/assets/macos-agent-status/ThreadKeeperAgentStatus.swift +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/assets/macos-agent-status/build.sh +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/assets/macos-agent-status/install.sh +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/auto_update.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/backup.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/candidate_reviewer.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/config_io.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/config_watcher.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/curator.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/curator_snapshots.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/daemon_liveness.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/daemon_state.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/daemon_supervisor.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/dialectic_miner.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/egress.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/eval/__init__.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/eval/__main__.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/evolve_applier.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/evolve_daemon.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/extract_daemon.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/github_budget.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/github_safety.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/harvest.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/helpers.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/host.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/host_embed.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/i18n.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/identity.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/lesson_violations.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/lessons.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/link_health.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/memory_guard.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/menubar_app.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/model_catalog.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/notify.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/nudges.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/permissions.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/probe_daemon.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/process_health.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/retention.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/retrieval.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/review_prompts.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/search_proxy.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/shadow_review.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/skill_audit.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/skill_updater.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/skill_watcher.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/spawn_budget.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/spawn_config.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/spawn_result.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/sync/__init__.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/sync/capture.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/sync/daemon.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/sync/identity.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/sync/migrate.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/sync/protocol.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/sync/reset_node.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/sync/server.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/task_spool.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/thread_janitor.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tool_schemas.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/__init__.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/agent_status.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/candidate_reviewer.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/concepts.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/config_watch.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/consolidate.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/core_memory.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/correlation.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/curator.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/db_maintenance.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/dialectic_feed.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/dialog.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/distill.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/evolve_applier.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/evolve_research.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/forget.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/graph.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/invariants.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/memory_guard.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/missed_spawns.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/panel.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/peers.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/probes.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/process_health.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/prompts.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/resources.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/shadow_review.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/spawn.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/sync.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/tools/validate.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/trash.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/verify_ingest.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper/wikilinks.py +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper.egg-info/dependency_links.txt +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.0}/threadkeeper.egg-info/entry_points.txt +0 -0
- {threadkeeper-0.18.3 → threadkeeper-0.19.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.19.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,>=
|
|
25
|
+
Requires-Dist: mcp<3,>=2.2.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
|
|
@@ -217,13 +217,12 @@ Cline, … — so a single registration there reaches all of them at once.
|
|
|
217
217
|
Adding a new CLI = one file under `threadkeeper/adapters/` implementing
|
|
218
218
|
the `CLIAdapter` contract. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
219
219
|
|
|
220
|
-
### Python MCP SDK
|
|
220
|
+
### Python MCP SDK surface
|
|
221
221
|
|
|
222
|
-
thread-keeper
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
the same public contract across both supported SDK majors.
|
|
222
|
+
thread-keeper requires MCP Python SDK 2.2 or later (`mcp>=2.2.0,<3`). Its
|
|
223
|
+
stable Skills extension uses the MCP 2026-07-28 extension surface, while tools,
|
|
224
|
+
resources, prompts, annotations, structured content, elicitation, and stdio
|
|
225
|
+
continue to use the same `MCPServer` instance.
|
|
227
226
|
|
|
228
227
|
### MCP primitives (tools, resources, prompts, elicitation)
|
|
229
228
|
|
|
@@ -263,6 +262,29 @@ Hosts without a capability fall back to the SessionStart hook plus the `brief()`
|
|
|
263
262
|
regression. Static URIs only for now (resource *templates* with `{param}` are
|
|
264
263
|
still unevenly supported across hosts).
|
|
265
264
|
|
|
265
|
+
### MCP Skills extension
|
|
266
|
+
|
|
267
|
+
On MCP 2026-07-28 hosts, thread-keeper also advertises the stable
|
|
268
|
+
`io.modelcontextprotocol/skills` extension. `skills/list` pages the canonical
|
|
269
|
+
library deterministically, and `skills/get` returns the full manifest for one
|
|
270
|
+
skill. Each resource URI is origin-qualified, for example
|
|
271
|
+
`skill://thread-keeper/release-check/SKILL.md`, so hosts retain the server
|
|
272
|
+
identity alongside a possibly colliding skill name.
|
|
273
|
+
|
|
274
|
+
The manifest covers `SKILL.md` and every permitted file below `references/`,
|
|
275
|
+
`templates/`, `scripts/`, or `assets/`, with its byte length and a
|
|
276
|
+
`sha256:<hex>` digest. Files are individually available through ordinary
|
|
277
|
+
`resources/read`; traversal, undeclared files, and reads over 1 MiB are
|
|
278
|
+
rejected. Hosts should compare both size and digest before using a retrieved
|
|
279
|
+
file. The canonical directory is published once; the existing per-CLI
|
|
280
|
+
filesystem mirrors remain the fallback for clients that do not implement the
|
|
281
|
+
extension.
|
|
282
|
+
|
|
283
|
+
Discovery and `resources/read` are delivery only. Reading `SKILL.md` does not
|
|
284
|
+
activate it, approve its tools, record use telemetry, or approve supporting
|
|
285
|
+
files. Activation and any user approval remain responsibilities of the host's
|
|
286
|
+
skill-loading path.
|
|
287
|
+
|
|
266
288
|
### Memory egress (cross-provider privacy)
|
|
267
289
|
|
|
268
290
|
thread-keeper is "one user model … shared across CLIs," and that sharing is by
|
|
@@ -1269,6 +1291,17 @@ loops: a claim that surfaces in `brief()` and then gets "confirmed" by a
|
|
|
1269
1291
|
review-fork reading the same dialog can't ride that internal evidence
|
|
1270
1292
|
all the way to high confidence — internal evidence buys half as much.
|
|
1271
1293
|
|
|
1294
|
+
**Immutable authority and action confirmation.** Confidence and tier describe
|
|
1295
|
+
how useful a claim seems; they do not upgrade the authority of its evidence.
|
|
1296
|
+
New dialog, claim, evidence, lesson, and skill artifacts retain a write-time
|
|
1297
|
+
authority class plus their source principal/channel. Declared references such
|
|
1298
|
+
as `dialog:<uuid>`, `claim:<id>`, `lesson:<slug>`, and `skill:<name>` form a
|
|
1299
|
+
derivation graph; summaries and promotions inherit the least-authoritative
|
|
1300
|
+
root. Unknown writer origins are rejected. Before any consequential action,
|
|
1301
|
+
call `memory_authorize_action`: observed memory needs either an independent
|
|
1302
|
+
trusted principal or explicit `confirmed=True`. `forget` quarantines every
|
|
1303
|
+
derived descendant, so it can no longer be retrieved or authorize an action.
|
|
1304
|
+
|
|
1272
1305
|
**Discrete tier on each claim** — `hypothesis → observed → validated`
|
|
1273
1306
|
(plus `disputed`). Independent of the continuous confidence band; tier
|
|
1274
1307
|
is the **action-gating** signal:
|
|
@@ -1385,6 +1418,9 @@ The most-used env knobs (full list in `threadkeeper/config.py`):
|
|
|
1385
1418
|
| `THREADKEEPER_EMBED_BACKEND` | `onnx` | embedding runtime: `onnx` (fastembed, no PyTorch) or `sentence-transformers` (legacy fallback) |
|
|
1386
1419
|
| `THREADKEEPER_EMBED_MODEL` | `paraphrase-multilingual-MiniLM-L12-v2` | 384-dim cross-lingual embedding model |
|
|
1387
1420
|
| `THREADKEEPER_EMBED_REVISION` | backend-specific immutable commit | Hugging Face snapshot pin; set an intentional replacement commit only together with a re-embedding plan |
|
|
1421
|
+
| `THREADKEEPER_WRITER_PROVIDER` | active client | optional provider identity recorded for newly derived lessons, skills, notes, and claims |
|
|
1422
|
+
| `THREADKEEPER_WRITER_MODEL` | resolved model / `unknown` | optional writer-model identity recorded with derived-memory provenance |
|
|
1423
|
+
| `THREADKEEPER_WRITER_REVISION` | `unknown` | optional immutable writer-model revision recorded with derived-memory provenance |
|
|
1388
1424
|
| `THREADKEEPER_EMBED_CACHE_DIR` | `~/.cache/huggingface/hub` | durable Hugging Face snapshot cache |
|
|
1389
1425
|
| `THREADKEEPER_EMBED_LOCAL_FILES_ONLY` | false | require the pinned snapshot in the local Hugging Face cache (offline / air-gapped mode) |
|
|
1390
1426
|
| `THREADKEEPER_SPAWNED_CHILD` | "" | spawn-internal marker; disables autonomous daemons in children |
|
|
@@ -1721,26 +1757,37 @@ export THREADKEEPER_EMBED_BACKEND=sentence-transformers
|
|
|
1721
1757
|
|
|
1722
1758
|
# After any backend switch, homogenize the stored corpus so queries and
|
|
1723
1759
|
# stored vectors live in the same space:
|
|
1724
|
-
tk-migrate-embeddings --all #
|
|
1725
|
-
tk-migrate-embeddings --
|
|
1760
|
+
tk-migrate-embeddings --all # stage both stores, validate, atomically activate
|
|
1761
|
+
tk-migrate-embeddings --notes-only # build part of a target; activation stays pending
|
|
1762
|
+
tk-migrate-embeddings --status # active/staging/coverage/validation report
|
|
1763
|
+
tk-migrate-embeddings --rollback # atomically return to the prior generation
|
|
1726
1764
|
```
|
|
1727
1765
|
|
|
1728
|
-
The migration is batched, resumable, and idempotent
|
|
1729
|
-
|
|
1730
|
-
|
|
1766
|
+
The migration is batched, resumable, and idempotent. It stores target vectors
|
|
1767
|
+
under a separate generation while the durable active-generation pointer keeps
|
|
1768
|
+
the prior space queryable. An interrupted command resumes the staged target;
|
|
1769
|
+
readers never see it until `--all` validates full note/dialog coverage and
|
|
1770
|
+
switches the one pointer in a SQLite transaction. `--rollback` is the inverse
|
|
1771
|
+
transaction and leaves the failed target available for inspection or a later
|
|
1772
|
+
retry. Both backends emit 384-dim vectors, so the `vec0` schema is unchanged.
|
|
1731
1773
|
|
|
1732
1774
|
**Intentional model revision upgrade.** Set `THREADKEEPER_EMBED_REVISION` to
|
|
1733
1775
|
the immutable commit for the selected backend's Hugging Face artifact, restart
|
|
1734
1776
|
the host, then run `tk-migrate-embeddings --all`. A changed revision is a new
|
|
1735
|
-
embedding generation
|
|
1736
|
-
|
|
1777
|
+
embedding generation. During staging, queries are encoded against the durable
|
|
1778
|
+
old generation and keep their semantic recall; the new configuration is not
|
|
1779
|
+
exposed until activation. The migration output and `mp_dashboard()` show
|
|
1780
|
+
active, staging, coverage, validation, and activation state.
|
|
1737
1781
|
|
|
1738
1782
|
Stored rows carry an embedding-generation fingerprint, not just the backend:
|
|
1739
1783
|
backend, model ID, vector dimension, pooling contract, and compatible runtime
|
|
1740
|
-
version.
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1784
|
+
version. Newly derived lessons, skills, notes, and dialectic claims additionally
|
|
1785
|
+
record writer provider/model/revision plus source event or thread references;
|
|
1786
|
+
those records contain pointers and hashes, not another copy of transcript text.
|
|
1787
|
+
The fixed anonymized upgrade corpus runs both old→new and new→old paths before
|
|
1788
|
+
activation, with explicit recall, abstention, knowledge-update, and
|
|
1789
|
+
derived-memory decision thresholds. Run `python -m threadkeeper.eval` to print
|
|
1790
|
+
that release-gate report.
|
|
1744
1791
|
|
|
1745
1792
|
Retrieval is hybrid by default. FTS candidate generation always runs, even
|
|
1746
1793
|
when embeddings are installed or only part of the corpus has vectors. Dense
|
|
@@ -173,13 +173,12 @@ Cline, … — so a single registration there reaches all of them at once.
|
|
|
173
173
|
Adding a new CLI = one file under `threadkeeper/adapters/` implementing
|
|
174
174
|
the `CLIAdapter` contract. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
175
175
|
|
|
176
|
-
### Python MCP SDK
|
|
176
|
+
### Python MCP SDK surface
|
|
177
177
|
|
|
178
|
-
thread-keeper
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
the same public contract across both supported SDK majors.
|
|
178
|
+
thread-keeper requires MCP Python SDK 2.2 or later (`mcp>=2.2.0,<3`). Its
|
|
179
|
+
stable Skills extension uses the MCP 2026-07-28 extension surface, while tools,
|
|
180
|
+
resources, prompts, annotations, structured content, elicitation, and stdio
|
|
181
|
+
continue to use the same `MCPServer` instance.
|
|
183
182
|
|
|
184
183
|
### MCP primitives (tools, resources, prompts, elicitation)
|
|
185
184
|
|
|
@@ -219,6 +218,29 @@ Hosts without a capability fall back to the SessionStart hook plus the `brief()`
|
|
|
219
218
|
regression. Static URIs only for now (resource *templates* with `{param}` are
|
|
220
219
|
still unevenly supported across hosts).
|
|
221
220
|
|
|
221
|
+
### MCP Skills extension
|
|
222
|
+
|
|
223
|
+
On MCP 2026-07-28 hosts, thread-keeper also advertises the stable
|
|
224
|
+
`io.modelcontextprotocol/skills` extension. `skills/list` pages the canonical
|
|
225
|
+
library deterministically, and `skills/get` returns the full manifest for one
|
|
226
|
+
skill. Each resource URI is origin-qualified, for example
|
|
227
|
+
`skill://thread-keeper/release-check/SKILL.md`, so hosts retain the server
|
|
228
|
+
identity alongside a possibly colliding skill name.
|
|
229
|
+
|
|
230
|
+
The manifest covers `SKILL.md` and every permitted file below `references/`,
|
|
231
|
+
`templates/`, `scripts/`, or `assets/`, with its byte length and a
|
|
232
|
+
`sha256:<hex>` digest. Files are individually available through ordinary
|
|
233
|
+
`resources/read`; traversal, undeclared files, and reads over 1 MiB are
|
|
234
|
+
rejected. Hosts should compare both size and digest before using a retrieved
|
|
235
|
+
file. The canonical directory is published once; the existing per-CLI
|
|
236
|
+
filesystem mirrors remain the fallback for clients that do not implement the
|
|
237
|
+
extension.
|
|
238
|
+
|
|
239
|
+
Discovery and `resources/read` are delivery only. Reading `SKILL.md` does not
|
|
240
|
+
activate it, approve its tools, record use telemetry, or approve supporting
|
|
241
|
+
files. Activation and any user approval remain responsibilities of the host's
|
|
242
|
+
skill-loading path.
|
|
243
|
+
|
|
222
244
|
### Memory egress (cross-provider privacy)
|
|
223
245
|
|
|
224
246
|
thread-keeper is "one user model … shared across CLIs," and that sharing is by
|
|
@@ -1225,6 +1247,17 @@ loops: a claim that surfaces in `brief()` and then gets "confirmed" by a
|
|
|
1225
1247
|
review-fork reading the same dialog can't ride that internal evidence
|
|
1226
1248
|
all the way to high confidence — internal evidence buys half as much.
|
|
1227
1249
|
|
|
1250
|
+
**Immutable authority and action confirmation.** Confidence and tier describe
|
|
1251
|
+
how useful a claim seems; they do not upgrade the authority of its evidence.
|
|
1252
|
+
New dialog, claim, evidence, lesson, and skill artifacts retain a write-time
|
|
1253
|
+
authority class plus their source principal/channel. Declared references such
|
|
1254
|
+
as `dialog:<uuid>`, `claim:<id>`, `lesson:<slug>`, and `skill:<name>` form a
|
|
1255
|
+
derivation graph; summaries and promotions inherit the least-authoritative
|
|
1256
|
+
root. Unknown writer origins are rejected. Before any consequential action,
|
|
1257
|
+
call `memory_authorize_action`: observed memory needs either an independent
|
|
1258
|
+
trusted principal or explicit `confirmed=True`. `forget` quarantines every
|
|
1259
|
+
derived descendant, so it can no longer be retrieved or authorize an action.
|
|
1260
|
+
|
|
1228
1261
|
**Discrete tier on each claim** — `hypothesis → observed → validated`
|
|
1229
1262
|
(plus `disputed`). Independent of the continuous confidence band; tier
|
|
1230
1263
|
is the **action-gating** signal:
|
|
@@ -1341,6 +1374,9 @@ The most-used env knobs (full list in `threadkeeper/config.py`):
|
|
|
1341
1374
|
| `THREADKEEPER_EMBED_BACKEND` | `onnx` | embedding runtime: `onnx` (fastembed, no PyTorch) or `sentence-transformers` (legacy fallback) |
|
|
1342
1375
|
| `THREADKEEPER_EMBED_MODEL` | `paraphrase-multilingual-MiniLM-L12-v2` | 384-dim cross-lingual embedding model |
|
|
1343
1376
|
| `THREADKEEPER_EMBED_REVISION` | backend-specific immutable commit | Hugging Face snapshot pin; set an intentional replacement commit only together with a re-embedding plan |
|
|
1377
|
+
| `THREADKEEPER_WRITER_PROVIDER` | active client | optional provider identity recorded for newly derived lessons, skills, notes, and claims |
|
|
1378
|
+
| `THREADKEEPER_WRITER_MODEL` | resolved model / `unknown` | optional writer-model identity recorded with derived-memory provenance |
|
|
1379
|
+
| `THREADKEEPER_WRITER_REVISION` | `unknown` | optional immutable writer-model revision recorded with derived-memory provenance |
|
|
1344
1380
|
| `THREADKEEPER_EMBED_CACHE_DIR` | `~/.cache/huggingface/hub` | durable Hugging Face snapshot cache |
|
|
1345
1381
|
| `THREADKEEPER_EMBED_LOCAL_FILES_ONLY` | false | require the pinned snapshot in the local Hugging Face cache (offline / air-gapped mode) |
|
|
1346
1382
|
| `THREADKEEPER_SPAWNED_CHILD` | "" | spawn-internal marker; disables autonomous daemons in children |
|
|
@@ -1677,26 +1713,37 @@ export THREADKEEPER_EMBED_BACKEND=sentence-transformers
|
|
|
1677
1713
|
|
|
1678
1714
|
# After any backend switch, homogenize the stored corpus so queries and
|
|
1679
1715
|
# stored vectors live in the same space:
|
|
1680
|
-
tk-migrate-embeddings --all #
|
|
1681
|
-
tk-migrate-embeddings --
|
|
1716
|
+
tk-migrate-embeddings --all # stage both stores, validate, atomically activate
|
|
1717
|
+
tk-migrate-embeddings --notes-only # build part of a target; activation stays pending
|
|
1718
|
+
tk-migrate-embeddings --status # active/staging/coverage/validation report
|
|
1719
|
+
tk-migrate-embeddings --rollback # atomically return to the prior generation
|
|
1682
1720
|
```
|
|
1683
1721
|
|
|
1684
|
-
The migration is batched, resumable, and idempotent
|
|
1685
|
-
|
|
1686
|
-
|
|
1722
|
+
The migration is batched, resumable, and idempotent. It stores target vectors
|
|
1723
|
+
under a separate generation while the durable active-generation pointer keeps
|
|
1724
|
+
the prior space queryable. An interrupted command resumes the staged target;
|
|
1725
|
+
readers never see it until `--all` validates full note/dialog coverage and
|
|
1726
|
+
switches the one pointer in a SQLite transaction. `--rollback` is the inverse
|
|
1727
|
+
transaction and leaves the failed target available for inspection or a later
|
|
1728
|
+
retry. Both backends emit 384-dim vectors, so the `vec0` schema is unchanged.
|
|
1687
1729
|
|
|
1688
1730
|
**Intentional model revision upgrade.** Set `THREADKEEPER_EMBED_REVISION` to
|
|
1689
1731
|
the immutable commit for the selected backend's Hugging Face artifact, restart
|
|
1690
1732
|
the host, then run `tk-migrate-embeddings --all`. A changed revision is a new
|
|
1691
|
-
embedding generation
|
|
1692
|
-
|
|
1733
|
+
embedding generation. During staging, queries are encoded against the durable
|
|
1734
|
+
old generation and keep their semantic recall; the new configuration is not
|
|
1735
|
+
exposed until activation. The migration output and `mp_dashboard()` show
|
|
1736
|
+
active, staging, coverage, validation, and activation state.
|
|
1693
1737
|
|
|
1694
1738
|
Stored rows carry an embedding-generation fingerprint, not just the backend:
|
|
1695
1739
|
backend, model ID, vector dimension, pooling contract, and compatible runtime
|
|
1696
|
-
version.
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1740
|
+
version. Newly derived lessons, skills, notes, and dialectic claims additionally
|
|
1741
|
+
record writer provider/model/revision plus source event or thread references;
|
|
1742
|
+
those records contain pointers and hashes, not another copy of transcript text.
|
|
1743
|
+
The fixed anonymized upgrade corpus runs both old→new and new→old paths before
|
|
1744
|
+
activation, with explicit recall, abstention, knowledge-update, and
|
|
1745
|
+
derived-memory decision thresholds. Run `python -m threadkeeper.eval` to print
|
|
1746
|
+
that release-gate report.
|
|
1700
1747
|
|
|
1701
1748
|
Retrieval is hybrid by default. FTS candidate generation always runs, even
|
|
1702
1749
|
when embeddings are installed or only part of the corpus has vectors. Dense
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "threadkeeper"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.19.0"
|
|
8
8
|
description = "Multi-agent shared brain across Claude Code/Desktop, Codex, Antigravity CLI, Copilot, and VS Code. Cross-session memory, self-improving skill loops, inter-agent signaling — one local MCP server."
|
|
9
9
|
requires-python = ">=3.11"
|
|
10
10
|
authors = [{ name = "thread-keeper contributors" }]
|
|
@@ -26,12 +26,10 @@ classifiers = [
|
|
|
26
26
|
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
27
27
|
]
|
|
28
28
|
dependencies = [
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
|
|
33
|
-
# future unreviewed major from changing the server at install time.
|
|
34
|
-
"mcp>=1.10.0,<3",
|
|
29
|
+
# MCP 2.2 provides the 2026-07-28 extension surface used for the stable
|
|
30
|
+
# Skills distribution protocol. <3 keeps a future unreviewed major from
|
|
31
|
+
# changing the server at install time.
|
|
32
|
+
"mcp>=2.2.0,<3",
|
|
35
33
|
"pydantic>=2.13.4",
|
|
36
34
|
"pydantic-settings>=2.14.2",
|
|
37
35
|
"pyyaml>=6.0.3",
|
|
@@ -9,7 +9,7 @@ def _loop(snapshot, loop_id: str):
|
|
|
9
9
|
return {row["id"]: row for row in snapshot["loops"]}[loop_id]
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
def
|
|
12
|
+
def test_daemon_health_is_recreated_during_schema_migration(mp_with_cid):
|
|
13
13
|
pkg = mp_with_cid("33334444-5555-6666-7777-888899990000")
|
|
14
14
|
db = pkg["db"]
|
|
15
15
|
conn = db.get_db()
|
|
@@ -125,18 +125,12 @@ def test_explicit_base_weight_still_multiplied_by_discount(
|
|
|
125
125
|
assert row["weight"] == pytest.approx(0.4)
|
|
126
126
|
|
|
127
127
|
|
|
128
|
-
def
|
|
129
|
-
"""
|
|
130
|
-
Tests don't break under custom origins; default behavior is no-op."""
|
|
128
|
+
def test_unknown_origin_fails_closed(tmp_path, monkeypatch):
|
|
129
|
+
"""An unrecognised writer cannot mint a durable authority record."""
|
|
131
130
|
pkg = _bootstrap(tmp_path, monkeypatch, write_origin="random_unknown")
|
|
132
131
|
t = _tools(pkg)
|
|
133
132
|
out = t["dialectic_claim"].fn(claim="probe", domain="style")
|
|
134
|
-
|
|
135
|
-
t["dialectic_evidence"].fn(claim_id=cid, kind="support")
|
|
136
|
-
row = pkg["db"].get_db().execute(
|
|
137
|
-
"SELECT weight FROM dialectic_evidence WHERE claim_id=?", (cid,)
|
|
138
|
-
).fetchone()
|
|
139
|
-
assert row["weight"] == 1.0
|
|
133
|
+
assert out == "ERR authority_unknown_origin"
|
|
140
134
|
|
|
141
135
|
|
|
142
136
|
# ── weighted confidence formula ─────────────────────────────────────────
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
on bad input rather than raising Python exceptions.
|
|
3
3
|
|
|
4
4
|
A future error-boundary refactor (a `@safe_tool` decorator, schema validation,
|
|
5
|
-
or a top-level
|
|
5
|
+
or a top-level MCPServer wrapper) must keep these tests green.
|
|
6
6
|
"""
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
@@ -191,6 +191,19 @@ def test_cli_golden_baseline_is_clean():
|
|
|
191
191
|
assert r["shadow"]["ready"] and r["candidate"]["ready"] and r["quality"]["ready"]
|
|
192
192
|
|
|
193
193
|
|
|
194
|
+
def test_upgrade_replay_regression_fails_the_release_gate(tmp_path):
|
|
195
|
+
"""A one-way model regression must not be downgraded to PARTIAL."""
|
|
196
|
+
for fixture in FIXTURES.iterdir():
|
|
197
|
+
(tmp_path / fixture.name).write_text(fixture.read_text())
|
|
198
|
+
replay = json.loads((tmp_path / "upgrade_replay.json").read_text())
|
|
199
|
+
replay["items"][0]["new_to_old"]["retrieved_memory_ids"] = []
|
|
200
|
+
(tmp_path / "upgrade_replay.json").write_text(json.dumps(replay))
|
|
201
|
+
|
|
202
|
+
report = h.run_eval(tmp_path)
|
|
203
|
+
assert not report["upgrade"]["passed"]
|
|
204
|
+
assert report["verdict"] == "FAIL"
|
|
205
|
+
|
|
206
|
+
|
|
194
207
|
def test_cli_llm_judge_without_key_exits_cleanly():
|
|
195
208
|
env = {k: v for k, v in os.environ.items() if k != "ANTHROPIC_API_KEY"}
|
|
196
209
|
proc = _run_cli("--judge", "llm", env=env)
|
|
@@ -211,7 +224,8 @@ _SECRET_PATTERNS = [
|
|
|
211
224
|
|
|
212
225
|
|
|
213
226
|
def test_fixtures_have_no_secrets_or_private_paths():
|
|
214
|
-
for name in ("shadow.json", "candidates.json", "skill_quality.json"
|
|
227
|
+
for name in ("shadow.json", "candidates.json", "skill_quality.json",
|
|
228
|
+
"upgrade_replay.json"):
|
|
215
229
|
text = (FIXTURES / name).read_text()
|
|
216
230
|
# valid JSON
|
|
217
231
|
json.loads(text)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Golden coverage for surfacing the surgical lesson_patch action in brief().
|
|
2
|
+
|
|
3
|
+
The format should teach a fresh agent to make a narrow lesson correction with
|
|
4
|
+
the atomic operation rather than overwrite an otherwise-correct lesson body.
|
|
5
|
+
The seeded fixture also pins nearby long-lived brief sections.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
import time
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_FAKE_CID = "eeee3333-4444-5555-6666-777788889999"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _bootstrap(tmp_path, monkeypatch):
|
|
18
|
+
env = {
|
|
19
|
+
"THREADKEEPER_DB": str(tmp_path / "db.sqlite"),
|
|
20
|
+
"THREADKEEPER_LESSONS": str(tmp_path / "lessons.md"),
|
|
21
|
+
"CLAUDE_PROJECTS_DIR": str(tmp_path / "fake_claude_projects"),
|
|
22
|
+
"THREADKEEPER_INGEST_INTERVAL_S": "0",
|
|
23
|
+
"THREADKEEPER_INGEST_CAP": "0",
|
|
24
|
+
"THREADKEEPER_SKILL_WATCH_INTERVAL_S": "0",
|
|
25
|
+
"THREADKEEPER_SPAWN_BUDGET_POLL_S": "0",
|
|
26
|
+
"THREADKEEPER_SEARCH_PROXY_POLL_S": "0",
|
|
27
|
+
"THREADKEEPER_MEMORY_GUARD_POLL_S": "0",
|
|
28
|
+
"THREADKEEPER_SHADOW_REVIEW_INTERVAL_S": "0",
|
|
29
|
+
"THREADKEEPER_CURATOR_INTERVAL_S": "0",
|
|
30
|
+
"THREADKEEPER_EXTRACT_INTERVAL_S": "0",
|
|
31
|
+
"THREADKEEPER_CANDIDATE_REVIEW_INTERVAL_S": "0",
|
|
32
|
+
"THREADKEEPER_PROBE_INTERVAL_S": "0",
|
|
33
|
+
"THREADKEEPER_EVOLVE_REVIEW_INTERVAL_S": "0",
|
|
34
|
+
"THREADKEEPER_DISABLE_BG_DAEMONS": "1",
|
|
35
|
+
"THREADKEEPER_TASK_LOG_DIR": str(tmp_path / "tasks"),
|
|
36
|
+
"THREADKEEPER_CLIENT": "pytest",
|
|
37
|
+
"THREADKEEPER_FORCE_CID": _FAKE_CID,
|
|
38
|
+
"THREADKEEPER_NO_EMBEDDINGS": "1",
|
|
39
|
+
}
|
|
40
|
+
for key, value in env.items():
|
|
41
|
+
monkeypatch.setenv(key, value)
|
|
42
|
+
Path(env["CLAUDE_PROJECTS_DIR"]).mkdir(parents=True, exist_ok=True)
|
|
43
|
+
for name in [m for m in list(sys.modules) if m.startswith("threadkeeper")]:
|
|
44
|
+
del sys.modules[name]
|
|
45
|
+
import threadkeeper.server # noqa: F401
|
|
46
|
+
from threadkeeper import _mcp, db
|
|
47
|
+
|
|
48
|
+
return {"mcp": _mcp.mcp, "db": db}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _tool(pkg, name):
|
|
52
|
+
return pkg["mcp"]._tool_manager._tools[name].fn
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _add_evolve(conn, suggestion):
|
|
56
|
+
conn.execute(
|
|
57
|
+
"INSERT INTO evolve (suggestion, applied, status, created_at) "
|
|
58
|
+
"VALUES (?,?,?,?)",
|
|
59
|
+
(suggestion, 0, "promoted", int(time.time())),
|
|
60
|
+
)
|
|
61
|
+
conn.commit()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_brief_surfaces_lesson_patch_and_preserves_existing_sections(
|
|
65
|
+
tmp_path, monkeypatch,
|
|
66
|
+
):
|
|
67
|
+
pkg = _bootstrap(tmp_path, monkeypatch)
|
|
68
|
+
conn = pkg["db"].get_db()
|
|
69
|
+
lesson_append = _tool(pkg, "lesson_append")
|
|
70
|
+
open_thread = _tool(pkg, "open_thread")
|
|
71
|
+
|
|
72
|
+
lesson_append(
|
|
73
|
+
title="repair stale cross-links",
|
|
74
|
+
body="Use the current cross-link target.",
|
|
75
|
+
summary="Keep links current.",
|
|
76
|
+
source="foreground",
|
|
77
|
+
)
|
|
78
|
+
open_thread(question="a seeded open thread that must survive")
|
|
79
|
+
_add_evolve(conn, "a promoted suggestion that must surface")
|
|
80
|
+
|
|
81
|
+
from threadkeeper.brief import render_brief
|
|
82
|
+
|
|
83
|
+
text = render_brief(conn)
|
|
84
|
+
|
|
85
|
+
# New: a precise, atomic lesson-edit action is available at session start.
|
|
86
|
+
assert "lesson_patch" in text
|
|
87
|
+
assert "lesson_patch(slug, old_string, new_string)" in text
|
|
88
|
+
assert "unique lesson-body substring" in text
|
|
89
|
+
|
|
90
|
+
# Existing: the added section leaves the surrounding brief intact.
|
|
91
|
+
assert "open" in text
|
|
92
|
+
assert "a seeded open thread that must survive" in text
|
|
93
|
+
assert "evolve_pending" in text
|
|
94
|
+
assert "★" in text
|
|
95
|
+
assert "a promoted suggestion that must surface" in text
|
|
96
|
+
assert "user-facing" in text
|
|
97
|
+
assert "Do NOT cite internal IDs" in text
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Golden coverage for lesson recommendation reconciliation in brief()."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
_FAKE_CID = "bbbb3333-4444-5555-6666-777788889999"
|
|
10
|
+
_OPEN_Q = "seeded open thread remains visible"
|
|
11
|
+
_OLD_LESSON = "aws-sso-preflight-uses-sts"
|
|
12
|
+
_NEW_LESSON = "aws-sso-token-freshness-sts-is-cached"
|
|
13
|
+
_COMMAND = "aws sts get-caller-identity"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _bootstrap(tmp_path, monkeypatch):
|
|
17
|
+
env = {
|
|
18
|
+
"THREADKEEPER_DB": str(tmp_path / "db.sqlite"),
|
|
19
|
+
"CLAUDE_PROJECTS_DIR": str(tmp_path / "fake_claude_projects"),
|
|
20
|
+
"THREADKEEPER_LESSONS": str(tmp_path / "lessons.md"),
|
|
21
|
+
"THREADKEEPER_DISABLE_BG_DAEMONS": "1",
|
|
22
|
+
"THREADKEEPER_INGEST_INTERVAL_S": "0",
|
|
23
|
+
"THREADKEEPER_INGEST_CAP": "0",
|
|
24
|
+
"THREADKEEPER_SKILL_WATCH_INTERVAL_S": "0",
|
|
25
|
+
"THREADKEEPER_SPAWN_BUDGET_POLL_S": "0",
|
|
26
|
+
"THREADKEEPER_SEARCH_PROXY_POLL_S": "0",
|
|
27
|
+
"THREADKEEPER_MEMORY_GUARD_POLL_S": "0",
|
|
28
|
+
"THREADKEEPER_SHADOW_REVIEW_INTERVAL_S": "0",
|
|
29
|
+
"THREADKEEPER_CURATOR_INTERVAL_S": "0",
|
|
30
|
+
"THREADKEEPER_EXTRACT_INTERVAL_S": "0",
|
|
31
|
+
"THREADKEEPER_CANDIDATE_REVIEW_INTERVAL_S": "0",
|
|
32
|
+
"THREADKEEPER_PROBE_INTERVAL_S": "0",
|
|
33
|
+
"THREADKEEPER_EVOLVE_REVIEW_INTERVAL_S": "0",
|
|
34
|
+
"THREADKEEPER_THREAD_JANITOR_INTERVAL_S": "0",
|
|
35
|
+
"THREADKEEPER_BRIEF_LEAN": "0",
|
|
36
|
+
"THREADKEEPER_TASK_LOG_DIR": str(tmp_path / "tasks"),
|
|
37
|
+
"THREADKEEPER_CLIENT": "pytest",
|
|
38
|
+
"THREADKEEPER_FORCE_CID": _FAKE_CID,
|
|
39
|
+
"THREADKEEPER_NO_EMBEDDINGS": "1",
|
|
40
|
+
}
|
|
41
|
+
for key, value in env.items():
|
|
42
|
+
monkeypatch.setenv(key, value)
|
|
43
|
+
Path(env["CLAUDE_PROJECTS_DIR"]).mkdir(parents=True, exist_ok=True)
|
|
44
|
+
for name in [module for module in list(sys.modules) if module.startswith("threadkeeper")]:
|
|
45
|
+
del sys.modules[name]
|
|
46
|
+
import threadkeeper.server # noqa: F401
|
|
47
|
+
from threadkeeper import _mcp, db, identity, lessons
|
|
48
|
+
return {"mcp": _mcp.mcp, "db": db, "identity": identity, "lessons": lessons}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _tool(pkg, name):
|
|
52
|
+
return pkg["mcp"]._tool_manager._tools[name].fn
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _add_evolve(conn, suggestion):
|
|
56
|
+
conn.execute(
|
|
57
|
+
"INSERT INTO evolve (suggestion, applied, status, created_at) "
|
|
58
|
+
"VALUES (?,?,?,?)",
|
|
59
|
+
(suggestion, 0, "promoted", int(time.time())),
|
|
60
|
+
)
|
|
61
|
+
conn.commit()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_brief_surfaces_lesson_reconciliation_and_keeps_existing_sections(
|
|
65
|
+
tmp_path, monkeypatch,
|
|
66
|
+
):
|
|
67
|
+
pkg = _bootstrap(tmp_path, monkeypatch)
|
|
68
|
+
conn = pkg["db"].get_db()
|
|
69
|
+
|
|
70
|
+
pkg["lessons"].append_lesson(
|
|
71
|
+
_OLD_LESSON,
|
|
72
|
+
body=(
|
|
73
|
+
f"Run `{_COMMAND}` to gate an AWS SSO freshness check before "
|
|
74
|
+
"using fixture-backed cloud tests."
|
|
75
|
+
),
|
|
76
|
+
source="shadow",
|
|
77
|
+
)
|
|
78
|
+
pkg["lessons"].append_lesson(
|
|
79
|
+
_NEW_LESSON,
|
|
80
|
+
body=(
|
|
81
|
+
f"Never gate SSO freshness on `{_COMMAND}`: it is cached and can "
|
|
82
|
+
"return success after the token expires."
|
|
83
|
+
),
|
|
84
|
+
source="shadow",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
_tool(pkg, "open_thread")(question=_OPEN_Q)
|
|
88
|
+
_add_evolve(conn, "a promoted suggestion that must remain visible")
|
|
89
|
+
|
|
90
|
+
from threadkeeper.brief import render_brief
|
|
91
|
+
text = render_brief(conn)
|
|
92
|
+
|
|
93
|
+
# New behavior: a newer debunk flags the older recommendation by command
|
|
94
|
+
# and lesson slug, with a concrete reconciliation action.
|
|
95
|
+
assert "lesson_reconciliation" in text
|
|
96
|
+
assert f"`{_COMMAND}`" in text
|
|
97
|
+
assert f"debunked_by={_NEW_LESSON}" in text
|
|
98
|
+
assert f"still_recommended_by={_OLD_LESSON}" in text
|
|
99
|
+
assert "patch its recommendation or cross-link the correcting lesson" in text
|
|
100
|
+
|
|
101
|
+
# Regression: the established brief sections still survive this addition.
|
|
102
|
+
assert "open" in text
|
|
103
|
+
assert _OPEN_Q in text
|
|
104
|
+
assert "evolve_pending" in text
|
|
105
|
+
assert "★ \"a promoted suggestion that must remain visible\"" in text
|
|
106
|
+
assert "user-facing: paraphrase plain" in text
|
|
107
|
+
assert "Do NOT cite internal IDs" in text
|
|
@@ -158,10 +158,9 @@ def test_audit_threadkeeper_prompt_renders(fresh_mp):
|
|
|
158
158
|
# ──────────────────────────────────────────────────────────────────────
|
|
159
159
|
|
|
160
160
|
def test_server_advertises_resources_and_prompts_capabilities(fresh_mp):
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
caps = lowlevel.get_capabilities(NotificationOptions(), {})
|
|
161
|
+
caps = fresh_mp["mcp"]._lowlevel_server.get_capabilities(
|
|
162
|
+
NotificationOptions(), {}, protocol_version="2026-07-28",
|
|
163
|
+
)
|
|
165
164
|
assert caps.resources is not None
|
|
166
165
|
assert caps.prompts is not None
|
|
167
166
|
assert caps.tools is not None # tools unaffected
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
"""MCP SDK
|
|
1
|
+
"""MCP SDK 2.x stdio-server smoke coverage.
|
|
2
2
|
|
|
3
|
-
The
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
is available. CI runs it once per supported SDK-major/Python combination.
|
|
3
|
+
The stable Skills extension requires MCP SDK 2.2 or later. This test starts
|
|
4
|
+
the real ``python -m threadkeeper.server`` process and verifies that the public
|
|
5
|
+
tools registry remains available over stdio on the supported SDK surface.
|
|
7
6
|
"""
|
|
8
7
|
from __future__ import annotations
|
|
9
8
|
|