threadkeeper 0.17.7__tar.gz → 0.18.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.17.7 → threadkeeper-0.18.0}/PKG-INFO +170 -73
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/README.md +168 -71
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/pyproject.toml +5 -6
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_adapters.py +64 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_agent_status.py +57 -0
- threadkeeper-0.18.0/tests/test_antigravity_ingest.py +217 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_auto_update.py +27 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_candidate_reviewer.py +45 -0
- threadkeeper-0.18.0/tests/test_ci_parallel.py +48 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_config_settings.py +4 -1
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_curator.py +709 -12
- threadkeeper-0.18.0/tests/test_db_leak_guard.py +136 -0
- threadkeeper-0.18.0/tests/test_documentation_inventory.py +60 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_evolve_applier.py +292 -28
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_evolve_daemon.py +93 -6
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_i18n_multilang.py +23 -1
- threadkeeper-0.18.0/tests/test_lesson_violations.py +91 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_mcp_resources_prompts.py +4 -1
- threadkeeper-0.18.0/tests/test_mcp_sdk_compat.py +144 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_menubar_app.py +1 -1
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_notify_daemon.py +68 -2
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_panel.py +20 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_pickup.py +18 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_probe_daemon.py +16 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_release_workflows.py +20 -14
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_single_flight_prompt_prefixes.py +10 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_skill_watcher.py +41 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_skills.py +25 -0
- threadkeeper-0.18.0/tests/test_spawn_background_cwd.py +138 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_spawn_budget.py +89 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_spawn_codex_stdin.py +49 -4
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_spawn_permission_gate.py +61 -7
- threadkeeper-0.18.0/tests/test_spawn_result.py +17 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_spawn_watchdog.py +41 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_thread_janitor.py +20 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_tool_annotations.py +39 -16
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_verify_ingest.py +13 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/_mcp.py +20 -4
- threadkeeper-0.18.0/threadkeeper/adapters/antigravity.py +387 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/adapters/base.py +28 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/adapters/codex.py +50 -1
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/agent_metadata.py +12 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/agent_status.py +25 -5
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/auto_update.py +22 -2
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/candidate_reviewer.py +38 -11
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/config.py +43 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/curator.py +1379 -276
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/db.py +198 -19
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/elicitation.py +2 -1
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/evolve_applier.py +181 -31
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/evolve_daemon.py +28 -14
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/github_safety.py +10 -4
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/host.py +31 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/i18n.py +51 -18
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/ingest.py +25 -7
- threadkeeper-0.18.0/threadkeeper/lesson_violations.py +99 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/notify.py +35 -5
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/probe_daemon.py +8 -2
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/server.py +1 -1
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/shadow_review.py +12 -5
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/skill_watcher.py +65 -2
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/spawn_budget.py +42 -20
- threadkeeper-0.18.0/threadkeeper/spawn_result.py +47 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/thread_janitor.py +28 -9
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tool_schemas.py +1 -1
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/curator.py +125 -5
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/dashboard.py +31 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/dialectic.py +1 -3
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/lessons.py +40 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/panel.py +6 -3
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/pickup.py +26 -8
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/process_health.py +5 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/resources.py +1 -1
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/skills.py +6 -1
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/spawn.py +345 -167
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/verify_ingest.py +11 -7
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper.egg-info/PKG-INFO +170 -73
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper.egg-info/SOURCES.txt +10 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper.egg-info/requires.txt +1 -1
- threadkeeper-0.17.7/threadkeeper/adapters/antigravity.py +0 -193
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/LICENSE +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/setup.cfg +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_backup.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_brief_footprint.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_brief_sections.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_concepts.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_config_watcher.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_core_memory.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_daemon_host_config.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_daemon_host_integration.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_daemon_liveness.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_daemon_state.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_dashboard.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_db_compact.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_db_contention.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_db_migration_fts.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_db_runtime.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_db_schema.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_delegated_search.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_dialectic.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_dialectic_feed_tools.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_dialectic_miner.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_dialectic_observation_resolve.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_dialectic_recompute.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_dialectic_tier.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_dialectic_validator.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_dialog_fts_external.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_egress_policy.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_embed_routing.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_embedding_generation.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_embedding_model_revision.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_embedding_storage_dedup.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_error_paths.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_eval_harness.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_evolve_apply_2.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_evolve_apply_3.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_extract_daemon.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_extract_dedup.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_forget.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_github_budget.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_github_safety.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_helpers.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_host_embed.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_host_env.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_host_main.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_host_wedged.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_hybrid_retrieval.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_identity.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_ingest_cursor.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_ingest_denylist.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_ingest_secret_redaction.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_ingest_status.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_ingest_transactions.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_lessons.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_memory_eval.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_memory_guard.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_memory_guard_host.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_missed_spawns.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_model_catalog.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_nudges.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_onnx_embeddings.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_permissions.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_process_health.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_retention.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_review_prompts.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_search_fts_punctuation.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_session_read_purity.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_setup.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_shadow_review.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_skill_hint.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_skill_passive_tier.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_skill_tier.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_skill_updater.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_skill_use_parser.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_spawn_config.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_spawn_hint.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_spawn_reap.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_spawn_slim.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_spawn_worktree_isolation.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_spawn_wrap.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_sync_capture.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_sync_identity.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_sync_migrate.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_sync_protocol.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_sync_reset_node.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_sync_transport.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_task_retention.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_task_spool.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_thin_session.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_threads.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_tools_smoke.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_validate_threads.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/tests/test_vec_search.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/__init__.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/_setup.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/_spawn_wrap.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/adapters/__init__.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/adapters/_hook_helpers.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/adapters/claude_code.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/adapters/claude_desktop.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/adapters/copilot.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/adapters/vscode.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/assets/macos-agent-status/Info.plist +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/assets/macos-agent-status/README.md +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/assets/macos-agent-status/ThreadKeeperAgentStatus.swift +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/assets/macos-agent-status/build.sh +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/assets/macos-agent-status/install.sh +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/backup.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/brief.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/config_io.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/config_watcher.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/curator_snapshots.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/daemon_liveness.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/daemon_state.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/daemon_supervisor.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/dialectic_miner.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/dialectic_validator.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/egress.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/embeddings.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/eval/__init__.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/eval/__main__.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/eval/harness.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/extract_daemon.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/forget.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/github_budget.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/harvest.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/helpers.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/host_embed.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/identity.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/lessons.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/link_health.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/memory_guard.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/menubar_app.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/migrate_embeddings.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/model_catalog.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/nudges.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/permissions.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/process_health.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/retention.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/retrieval.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/review_prompts.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/search_proxy.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/skill_audit.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/skill_updater.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/spawn_config.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/sync/__init__.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/sync/capture.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/sync/daemon.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/sync/identity.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/sync/migrate.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/sync/protocol.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/sync/reset_node.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/sync/server.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/task_spool.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/__init__.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/agent_status.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/candidate_reviewer.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/concepts.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/config_watch.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/consolidate.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/core_memory.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/correlation.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/db_maintenance.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/dialectic_feed.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/dialog.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/distill.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/evolve_applier.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/evolve_research.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/extract.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/forget.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/graph.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/invariants.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/memory_guard.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/missed_spawns.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/peers.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/probes.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/prompts.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/session.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/shadow_review.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/style.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/sync.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/threads.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/tools/validate.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/trash.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper/wikilinks.py +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper.egg-info/dependency_links.txt +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.0}/threadkeeper.egg-info/entry_points.txt +0 -0
- {threadkeeper-0.17.7 → threadkeeper-0.18.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.18.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<
|
|
25
|
+
Requires-Dist: mcp<3,>=1.10.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
|
|
@@ -108,6 +108,8 @@ Integrity API provenance from the expected GitHub Trusted Publisher. Dirty or
|
|
|
108
108
|
diverged git checkouts are skipped rather than overwritten. Restarts are gated
|
|
109
109
|
on install/setup success plus a subprocess import smoke check, so a broken or
|
|
110
110
|
unverified update is recorded but the current server keeps running.
|
|
111
|
+
Interpreters without pip (uv-created or pipx venvs) install through
|
|
112
|
+
`uv pip install --python <interpreter>` when `uv` is available.
|
|
111
113
|
Upstream PyPI publishing is intentionally gated: green merge-to-main builds are
|
|
112
114
|
auto-tagged, but every upload pauses for a human approval on the protected
|
|
113
115
|
`pypi` GitHub Environment (a maintainer-signed annotated `v*` tag remains the
|
|
@@ -193,17 +195,19 @@ thread-keeper-setup --dry-run
|
|
|
193
195
|
| Claude Code | `~/.claude.json` `mcpServers` | `~/.claude/CLAUDE.md` | `~/.claude/settings.json` `hooks` | `~/.claude/projects/**/*.jsonl` |
|
|
194
196
|
| Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` `mcpServers` (macOS); `%APPDATA%\Claude\…` (Win); `~/.config/Claude/…` (Linux) | none (GUI-only) | not supported by the app | none — chats live in Electron IndexedDB |
|
|
195
197
|
| Codex (CLI + desktop) | `~/.codex/config.toml` `[mcp_servers]` (shared between CLI and `Codex.app`) | `~/.codex/AGENTS.md` | not supported | `~/.codex/sessions/**/rollout-*.jsonl` |
|
|
196
|
-
| Antigravity CLI (`agy`) | `~/.gemini/config/mcp_config.json` `mcpServers` | `~/.gemini/config/AGENTS.md` | not wired yet |
|
|
198
|
+
| Antigravity CLI (`agy`) | `~/.gemini/config/mcp_config.json` `mcpServers` | `~/.gemini/config/AGENTS.md` | not wired yet | `~/.gemini/antigravity-cli/conversations/*.db` (sqlite/protobuf, read-only: user prompts and final answers) |
|
|
197
199
|
| Copilot | `~/.copilot/mcp-config.json` `mcpServers` | `~/.copilot/copilot-instructions.md` | `~/.copilot/hooks.json` | `~/.copilot/session-store.db` (sqlite) |
|
|
198
200
|
| VS Code | `~/Library/Application Support/Code/User/mcp.json` `servers` (macOS); `%APPDATA%\Code\User\mcp.json` (Win); `~/.config/Code/User/mcp.json` (Linux) | none (per-workspace only) | not supported | none — extensions own their history |
|
|
199
201
|
|
|
200
202
|
Every CLI that produces parseable transcripts feeds the same
|
|
201
203
|
`dialog_messages` table with a `source` tag, so `dialog_search()` finds
|
|
202
|
-
matches regardless of where the conversation happened. Claude Desktop
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
204
|
+
matches regardless of where the conversation happened. Claude Desktop and
|
|
205
|
+
the VS Code adapter are the exceptions — MCP registration only; their chats
|
|
206
|
+
don't reach the table for now (Electron IndexedDB on the Claude Desktop side;
|
|
207
|
+
per-extension stores on the VS Code side). Antigravity keeps one SQLite file
|
|
208
|
+
per conversation with protobuf step payloads; thread-keeper opens it read-only
|
|
209
|
+
(never creating WAL sidecars next to it) and keeps only user prompts and final
|
|
210
|
+
model answers, skipping tool calls and thinking.
|
|
207
211
|
|
|
208
212
|
VS Code's user-level `mcp.json` is the central host that **every
|
|
209
213
|
MCP-aware VS Code extension** consumes — GitHub Copilot Chat, the
|
|
@@ -213,6 +217,14 @@ Cline, … — so a single registration there reaches all of them at once.
|
|
|
213
217
|
Adding a new CLI = one file under `threadkeeper/adapters/` implementing
|
|
214
218
|
the `CLIAdapter` contract. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
215
219
|
|
|
220
|
+
### Python MCP SDK compatibility
|
|
221
|
+
|
|
222
|
+
thread-keeper supports MCP Python SDK 1.x and 2.x (`mcp>=1.10.0,<3`). Fresh
|
|
223
|
+
installs resolve 2.x. A small internal adapter uses the SDK 2.x `MCPServer`
|
|
224
|
+
name and falls back to its SDK 1.x `FastMCP` predecessor, so tools, resources,
|
|
225
|
+
prompts, annotations, structured content, elicitation, and stdio behavior keep
|
|
226
|
+
the same public contract across both supported SDK majors.
|
|
227
|
+
|
|
216
228
|
### MCP primitives (tools, resources, prompts, elicitation)
|
|
217
229
|
|
|
218
230
|
MCP has three server primitives. thread-keeper uses all three, mapped to the
|
|
@@ -309,9 +321,10 @@ task row so dead children stop counting against the cap. Admission control
|
|
|
309
321
|
refuses a new spawn that would exceed `THREADKEEPER_SPAWN_BUDGET_MB`
|
|
310
322
|
(3 GB default). Slim children that need semantic search delegate to the parent
|
|
311
323
|
via `search_via_parent` — no per-child copy of the embedding model. Admission
|
|
312
|
-
uses a SQLite `BEGIN IMMEDIATE` reservation: `spawn()` re-checks the
|
|
313
|
-
|
|
314
|
-
concurrent spawns cannot both squeeze through the cap
|
|
324
|
+
uses a short SQLite `BEGIN IMMEDIATE` reservation: `spawn()` re-checks the
|
|
325
|
+
budget and commits the child task row with its RSS estimate before `Popen`, so
|
|
326
|
+
two concurrent spawns cannot both squeeze through the cap, and no write lock is
|
|
327
|
+
held while the child launches.
|
|
315
328
|
|
|
316
329
|
When `cwd` is inside a Git checkout, `spawn()` also requires the source
|
|
317
330
|
checkout's tracked files to be clean, then starts the child from a unique
|
|
@@ -319,6 +332,15 @@ branch/worktree under `THREADKEEPER_TASK_LOG_DIR/worktrees/`. Parallel children
|
|
|
319
332
|
therefore never share a mutable checkout or Git index. Non-Git directories keep
|
|
320
333
|
their existing behavior; a dirty Git checkout is refused before a child starts.
|
|
321
334
|
|
|
335
|
+
Learning-loop children that name no `cwd` (Curator, shadow review, candidate
|
|
336
|
+
and dialectic review, probes, panels, the archivist) start in the owner-only
|
|
337
|
+
`<db dir>/workspace` directory instead of the spawning process's working
|
|
338
|
+
directory. The daemon host keeps the directory of whichever session started it,
|
|
339
|
+
so these children used to run inside an unrelated user project, and a dirty Git
|
|
340
|
+
checkout there refused every loop spawn. Foreground spawns keep the caller's
|
|
341
|
+
directory, and callers that pass `cwd` (the Evolve reviewer and applier) are
|
|
342
|
+
unchanged.
|
|
343
|
+
|
|
322
344
|
The spawn wrapper also records each completed child's `duration_s`,
|
|
323
345
|
`tokens_in`, `tokens_out`, `tokens_total`, and `cost_usd` when the underlying
|
|
324
346
|
CLI emits a recognizable usage trailer. Optional daily ceilings
|
|
@@ -535,7 +557,7 @@ shows agents focused on their primary task rarely do).
|
|
|
535
557
|
| 2 | shadow_review daemon | every 15 min (env knob) | recent `dialog_messages` window | SKILL.md, lessons.md |
|
|
536
558
|
| 3 | extract daemon | every 10 min (env knob) | recent `dialog_messages` window | `extract_candidates` pending queue |
|
|
537
559
|
| 4 | candidate-reviewer daemon | every 1 h (env knob) | pending candidates queue | SKILL.md (create/patch) / notes / verbatim / reject |
|
|
538
|
-
| 5 | Curator daemon | every 7 days (env knob) | every existing lesson + recently-touched skill | `REPORT-<date>.md`; Evolve applier applies
|
|
560
|
+
| 5 | Curator daemon | every 7 days (env knob) | every existing lesson + recently-touched skill | pass-scoped research handoffs and `REPORT-<date>.md`; Evolve applier applies reports after roadmap issues |
|
|
539
561
|
| 6 | evolve_reviewer daemon | configurable (env knob; 0=off) | code/docs/issues; web research in a separate read-only phase (#79) | roadmap updates + GitHub issues |
|
|
540
562
|
| 7 | evolve_applier daemon | configurable (env knob; 0=off) | open GitHub issues, Curator reports, legacy promoted evolve suggestions | PRs + applied markers |
|
|
541
563
|
| 8 | dialectic_miner daemon | configurable (env knob; 0=off) | recent `dialog_messages` — user replies + preceding-assistant context | `dialectic_observations` buffer |
|
|
@@ -626,6 +648,15 @@ slugs (semantic, with a lexical fallback). Shadow and candidate reviewers use
|
|
|
626
648
|
that preflight to patch/consolidate an incumbent or add a `[[slug]]` cross-link
|
|
627
649
|
to a related, distinct lesson while its body is still editable.
|
|
628
650
|
|
|
651
|
+
When the dialog shows a rule being broken again although a lesson already
|
|
652
|
+
covers it, the reviewers call `lesson_violation(slug, evidence)` instead of
|
|
653
|
+
writing a duplicate. One conversation counts once per lesson per day; at
|
|
654
|
+
`THREADKEEPER_LESSON_VIOLATION_THRESHOLD` (3) violations inside
|
|
655
|
+
`THREADKEEPER_LESSON_VIOLATION_WINDOW_DAYS` (90) the lesson is
|
|
656
|
+
memory-insufficient: `mp_dashboard` lists it and the Curator inventory marks it
|
|
657
|
+
`[MEMORY-INSUFFICIENT]` and recommends escalating the rule to a PreToolUse-style
|
|
658
|
+
hook or other hard guard.
|
|
659
|
+
|
|
629
660
|
#### 3. Extract daemon
|
|
630
661
|
|
|
631
662
|
Every `THREADKEEPER_EXTRACT_INTERVAL_S` seconds (default off, 600 =
|
|
@@ -709,10 +740,12 @@ flags a dense lesson subtopic when at least
|
|
|
709
740
|
`THREADKEEPER_CURATOR_PROMOTION_MIN_LESSONS` lessons (default 3) share a pair
|
|
710
741
|
of meaningful title terms. A non-protected candidate must become one validated,
|
|
711
742
|
checklist-style canonical skill before its source lessons are retired; protected
|
|
712
|
-
clusters are left for human review.
|
|
743
|
+
clusters are left for human review. A read-only research child reads every
|
|
713
744
|
complete skill and relevant support file, performs current web research against
|
|
714
|
-
official docs and comparable
|
|
715
|
-
|
|
745
|
+
official docs and comparable public skills, then writes a bounded
|
|
746
|
+
`RESEARCH-<pass>-batch-NNN-of-MMM.json` handoff through a destination-scoped
|
|
747
|
+
tool. A separate web-free evaluator receives that handoff as fenced, untrusted
|
|
748
|
+
data and writes numbered per-skill verdicts to
|
|
716
749
|
`~/.threadkeeper/curator/REPORT-<isodate>.md` for a one-batch pass or
|
|
717
750
|
`REPORT-<isodate>-batch-NNN-of-MMM.md` for a multi-batch pass: KEEP / REPAIR /
|
|
718
751
|
UPDATE / MERGE / SPLIT / DEPRECATE / DELETE / CROSS_LINK / HUMAN_REVIEW.
|
|
@@ -744,25 +777,59 @@ have increased its patch counter. Patches are maintenance activity, not proof
|
|
|
744
777
|
that a foreground user or agent consulted the skill.
|
|
745
778
|
|
|
746
779
|
Before spawning, the scheduler hashes lessons, concepts, skill bodies, support
|
|
747
|
-
trees, validators, and mirror state.
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
780
|
+
trees, validators, and mirror state. It then persists a pass manifest with the
|
|
781
|
+
expected reports and the rendered text of every batch before launching any
|
|
782
|
+
child. A dispatch is not completion: every batch must exit successfully and
|
|
783
|
+
have a final, matching provenance record before the inventory is endorsed.
|
|
784
|
+
Repeated manual calls over identical bytes return `unchanged_inventory` only
|
|
785
|
+
after that endorsement; scheduled passes still run because CLI behavior,
|
|
786
|
+
official guidance, and external alternatives can change without local file
|
|
787
|
+
changes. Every required inventory source (lessons, skill telemetry, skill
|
|
788
|
+
files, and concepts) must read successfully before a pass is created; a
|
|
789
|
+
successfully empty source remains valid, while a failed one records
|
|
790
|
+
`inventory_error source=<source> error=<type>` without authorizing a report,
|
|
791
|
+
creating a recovery snapshot, or launching a child.
|
|
792
|
+
|
|
793
|
+
A pass keeps reviewing the batches it froze even when the live inventory
|
|
794
|
+
changes, and resuming it never re-reads the inventory. Failed and timed-out
|
|
795
|
+
batches are retried without redispatching completed work, up to three launched
|
|
796
|
+
attempts per batch; a spawn memory or spend-budget refusal leaves the batch
|
|
797
|
+
waiting without using an attempt (a spend-cap refusal also alerts). A pass
|
|
798
|
+
whose batch exhausts its attempts, or that outlives two Curator intervals
|
|
799
|
+
(at least six hours), is abandoned so the next due tick starts fresh.
|
|
800
|
+
`THREADKEEPER_CURATOR_MAX_CONCURRENT_BATCHES` (default `1`) bounds one pass's
|
|
801
|
+
live children while normal spawn admission still enforces the shared RSS
|
|
802
|
+
budget. While a pass is active, the daemon polls it every
|
|
803
|
+
`THREADKEEPER_CURATOR_BATCH_POLL_S` seconds (default `60`) instead of waiting
|
|
804
|
+
for the next full Curator interval. `curator_review_status()` and the Curator
|
|
805
|
+
row in `agent_status` expose expected, running, failed, complete, and
|
|
806
|
+
unapplied batch counts alongside the inventory hash, reports, audit manifest,
|
|
807
|
+
and recovery snapshot.
|
|
808
|
+
|
|
809
|
+
Each research handoff path is explicitly authorized for one pass, inventory
|
|
810
|
+
fingerprint, manifest digest, and batch before its web-enabled child is
|
|
811
|
+
launched. `curator_research_write` accepts only that destination from a spawned
|
|
812
|
+
`curator_researcher` child carrying the matching pass ID, and records the final
|
|
813
|
+
SHA-256 as provenance. Research and evaluation are two phases of each batch
|
|
814
|
+
inside the durable pass: the web-free evaluator for a batch starts only after
|
|
815
|
+
that batch's handoff validates. A researcher that fails or leaves a missing,
|
|
816
|
+
malformed, swapped, or mismatched handoff is retried (three attempts); after
|
|
817
|
+
that the batch gets a non-mutating evaluator that records `HUMAN_REVIEW` for
|
|
818
|
+
decisions that depend on current external facts. A destructive pass takes its
|
|
819
|
+
recovery snapshot right before its first mutating evaluator.
|
|
820
|
+
`THREADKEEPER_CURATOR_WEB_RESEARCH=0` skips the research phase entirely (and
|
|
821
|
+
its token cost); the evaluator then judges currency from local evidence only.
|
|
822
|
+
Each report path is then
|
|
823
|
+
authorized in the existing parent-authored `curator_pass` event;
|
|
824
|
+
`curator_report_write` records its SHA-256 in `curator_report_provenance`.
|
|
825
|
+
This makes the report directory an untrusted transport: a stray or forged
|
|
826
|
+
`REPORT-*.md` file cannot acquire the provenance needed by the applier.
|
|
827
|
+
|
|
828
|
+
The web-enabled researcher never receives lesson, skill, or concept mutation
|
|
829
|
+
tools. The evaluator never receives web tools. Curator applies its own PATCH /
|
|
830
|
+
PRUNE / CONSOLIDATE directly by default (the evaluator writes the REPORT first,
|
|
831
|
+
then mutates — `lesson_remove` is in its toolset so it can actually prune and
|
|
832
|
+
consolidate duplicate lessons). Set
|
|
766
833
|
`THREADKEEPER_CURATOR_DESTRUCTIVE=0` for advisory REPORT-only. Pinned and
|
|
767
834
|
untracked skills remain protected. Foreground-authored skills are protected by
|
|
768
835
|
default; set `THREADKEEPER_CURATOR_MANAGE_FOREGROUND_SKILLS=1` to grant the
|
|
@@ -803,11 +870,13 @@ or `skill_manage(action='restore', name=...)`. Trash retention is bounded by
|
|
|
803
870
|
`THREADKEEPER_CURATOR_TRASH_TTL_DAYS` (30 days by default) and swept on new
|
|
804
871
|
trash writes. Advisory mode does not write snapshots. The existing Evolve
|
|
805
872
|
applier is
|
|
806
|
-
also the Curator apply worker: after the roadmap issue queue is empty, it
|
|
807
|
-
|
|
808
|
-
current SHA-256 match an unapplied
|
|
809
|
-
spawns an `evolve_applier` child to
|
|
810
|
-
|
|
873
|
+
also the Curator apply worker: after the roadmap issue queue is empty, it
|
|
874
|
+
enumerates every complete, unapplied report in the oldest endorsed Curator pass
|
|
875
|
+
(`CURATOR_PASS_COMPLETE`) whose path and current SHA-256 match an unapplied
|
|
876
|
+
`curator_report_provenance` event, then spawns an `evolve_applier` child to
|
|
877
|
+
apply only safe, still-current memory
|
|
878
|
+
maintenance through `lesson_append` / `lesson_patch` / `lesson_remove` /
|
|
879
|
+
`skill_manage` /
|
|
811
880
|
`concept_manage`. It never touches `[PROTECTED]`,
|
|
812
881
|
foreground/user, pinned, or validated entries. Only after the child finishes
|
|
813
882
|
does it call `evolve_mark_curator_report_applied(...)` with the verified hash;
|
|
@@ -889,8 +958,18 @@ shell/`bypassPermissions` to the same child:
|
|
|
889
958
|
|
|
890
959
|
A full research → audit cycle therefore spans two due passes.
|
|
891
960
|
|
|
892
|
-
|
|
893
|
-
|
|
961
|
+
Privileged reviewer and applier launches use private server-owned launchers.
|
|
962
|
+
The public `spawn()` tool refuses `permission_mode="bypassPermissions"` even if
|
|
963
|
+
a caller supplies Evolve-looking role or provenance metadata; only the explicit
|
|
964
|
+
`THREADKEEPER_ALLOW_BYPASS_PERMISSIONS_SPAWN=1` operator override opens that
|
|
965
|
+
mode for public calls.
|
|
966
|
+
|
|
967
|
+
Before a privileged audit can create more issues, the parent counts only open,
|
|
968
|
+
not-yet-applied issues carrying the deliberate `roadmap` label with a paginated
|
|
969
|
+
GitHub REST read. Issues without that label do not consume the reviewer cap.
|
|
970
|
+
Roadmap issues with an applier skip label or an untrusted author still count:
|
|
971
|
+
those gates stop autonomous pickup, not the reviewer's view of intended roadmap
|
|
972
|
+
work. At
|
|
894
973
|
`THREADKEEPER_EVOLVE_REVIEW_BACKLOG_MAX` (default 25), it withholds that audit
|
|
895
974
|
and records `backlog_saturated open=<n> cap=<max>` on the
|
|
896
975
|
`evolve_review_pass` event; set the knob to `0` to opt out. The read-only
|
|
@@ -1014,19 +1093,24 @@ which bypasses the gate as explicit promotion. This removes the untrusted input
|
|
|
1014
1093
|
at the boundary and complements the in-prompt data-fencing of #22/#76. The
|
|
1015
1094
|
public claim comment also carries only an opaque per-host token (a 6-char hash
|
|
1016
1095
|
of the hostname), never the raw hostname/PID/git-rev; the full host identity is
|
|
1017
|
-
recorded in the local event log for multi-host triage.
|
|
1018
|
-
|
|
1019
|
-
|
|
1096
|
+
recorded in the local event log for multi-host triage. A visible claim marker is
|
|
1097
|
+
also not a bearer token: the claim lock accepts it only when the comment's
|
|
1098
|
+
GitHub `author_association` is in that trusted association set, or when its
|
|
1099
|
+
author login is listed in `THREADKEEPER_EVOLVE_CLAIM_AUTOMATION_ACTORS`. Missing
|
|
1100
|
+
author metadata and untrusted marker comments remain visible on GitHub but do
|
|
1101
|
+
not block roadmap work or affect claim-race resolution.
|
|
1102
|
+
|
|
1103
|
+
**Privilege + public-body guard.** Stored evolve suggestions and external
|
|
1020
1104
|
GitHub issue bodies are wrapped in explicit data fences before a privileged
|
|
1021
|
-
child sees them. The
|
|
1022
|
-
`permission_mode="bypassPermissions"`
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
`THREADKEEPER_ALLOW_BYPASS_PERMISSIONS_SPAWN=1
|
|
1026
|
-
get a PATH-prepended `gh` wrapper that scrubs `gh issue
|
|
1027
|
-
comment`, and `gh pr create` bodies before the real GitHub
|
|
1028
|
-
home-directory paths and common token shapes are redacted, and a
|
|
1029
|
-
refused if a known unsafe pattern remains.
|
|
1105
|
+
child sees them. The public `spawn()` tool refuses
|
|
1106
|
+
`permission_mode="bypassPermissions"` regardless of caller-supplied role or
|
|
1107
|
+
provenance metadata; privileged Evolve launches use private server-owned
|
|
1108
|
+
capabilities, and public calls need the explicit
|
|
1109
|
+
`THREADKEEPER_ALLOW_BYPASS_PERMISSIONS_SPAWN=1` operator override. Privileged
|
|
1110
|
+
Evolve children also get a PATH-prepended `gh` wrapper that scrubs `gh issue
|
|
1111
|
+
create`, `gh issue comment`, and `gh pr create` bodies before the real GitHub
|
|
1112
|
+
CLI sees them: home-directory paths and common token shapes are redacted, and a
|
|
1113
|
+
body is refused if a known unsafe pattern remains.
|
|
1030
1114
|
|
|
1031
1115
|
Fallback/manual paths remain:
|
|
1032
1116
|
|
|
@@ -1090,7 +1174,9 @@ Three detection sources per tick:
|
|
|
1090
1174
|
|
|
1091
1175
|
1. **Admission failures / terminal timeouts** — a `<loop>_pass` event whose
|
|
1092
1176
|
summary is a spawn/budget failure (e.g. `token_budget_exceeded`,
|
|
1093
|
-
`claude_cli_not_found`), plus `spawn_timeout_retry_failed`.
|
|
1177
|
+
`claude_cli_not_found`), plus `spawn_timeout_retry_failed`. A budget
|
|
1178
|
+
refusal names the local cap that refused the child — the spawn memory
|
|
1179
|
+
budget or the daily token/cost budget — not the CLI subscription.
|
|
1094
1180
|
2. **Dead children** — a `tasks` row that ended with a non-zero, non-timeout
|
|
1095
1181
|
return code. This is the important one: `spawn()` returns `ok task=…` at
|
|
1096
1182
|
*launch*, so a `*_pass` summary is a false success when a child later dies
|
|
@@ -1140,7 +1226,11 @@ category off silences the banner without hiding the history. Each materializatio
|
|
|
1140
1226
|
uses its own skill or lesson toggle; generic task reports are history-only.
|
|
1141
1227
|
Foreground writes appear too, without waiting for a background child to finish.
|
|
1142
1228
|
A pathless skill mark only silences its reminder and has no materialization
|
|
1143
|
-
banner. Enabling a toggle never replays backlog.
|
|
1229
|
+
banner. Enabling a toggle never replays backlog. Child-log excerpts and spawn
|
|
1230
|
+
error reasons in both lists are bounded sanitized summaries: credential-shaped
|
|
1231
|
+
values and private home paths are redacted before they reach the MCP status
|
|
1232
|
+
response, menu, or notification; raw logs remain available only through
|
|
1233
|
+
`task_logs`.
|
|
1144
1234
|
|
|
1145
1235
|
**Settings in the app.** The menu-bar app's **Settings ▸ Notifications** tab
|
|
1146
1236
|
edits these same `THREADKEEPER_NOTIFY_*` keys visually — switches for the
|
|
@@ -1197,9 +1287,9 @@ claim earned trust. Thresholds:
|
|
|
1197
1287
|
|
|
1198
1288
|
All multilingual regex and prompt fragments live in
|
|
1199
1289
|
`threadkeeper/i18n.py` — the rest of the codebase stays English-only.
|
|
1200
|
-
Currently ships
|
|
1201
|
-
Spanish, Portuguese, French, German, Arabic,
|
|
1202
|
-
|
|
1290
|
+
Currently ships twelve locales: **English, Mandarin Chinese, Hindi,
|
|
1291
|
+
Spanish, Portuguese (Brazilian and European), French, German, Arabic,
|
|
1292
|
+
Russian, Japanese, Korean, Bengali**.
|
|
1203
1293
|
|
|
1204
1294
|
Adding a new language is a two-file PR — see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
1205
1295
|
|
|
@@ -1248,10 +1338,14 @@ The most-used env knobs (full list in `threadkeeper/config.py`):
|
|
|
1248
1338
|
| `THREADKEEPER_CANDIDATE_REVIEW_INTERVAL_S` | 0 (off) | candidate-reviewer daemon tick (s), restart-throttled by the last `candidate_review_pass`; 3600 = 1h recommended |
|
|
1249
1339
|
| `THREADKEEPER_CANDIDATE_REVIEW_MIN` | 3 | min pending candidates before reviewer engages |
|
|
1250
1340
|
| `THREADKEEPER_CANDIDATE_REVIEW_FLUSH_AGE_S` | 259200 | age-flush: an undersized pending queue is still reviewed once its oldest candidate is this old (0 = threshold only) |
|
|
1341
|
+
| `THREADKEEPER_LESSON_VIOLATION_THRESHOLD` | 3 | violations of one lesson inside the window that make it memory-insufficient (recommend a hook) |
|
|
1342
|
+
| `THREADKEEPER_LESSON_VIOLATION_WINDOW_DAYS` | 90 | window for counting `lesson_violation` records |
|
|
1343
|
+
| `THREADKEEPER_CANDIDATE_REVIEW_BATCH_SIZE` | 40 | most pending candidates (oldest first) one reviewer child receives; the rest stay pending for the next pass |
|
|
1251
1344
|
| `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 |
|
|
1252
1345
|
| `THREADKEEPER_CURATOR_INTERVAL_S` | 259200 | deep curator audit every three days; set `0` to disable |
|
|
1253
1346
|
| `THREADKEEPER_CURATOR_MANAGE_FOREGROUND_SKILLS` | 0 | allow snapshot-scoped Curator repair/merge/delete of foreground skills; pinned/untracked skills remain protected |
|
|
1254
1347
|
| `THREADKEEPER_CURATOR_MIN_LESSONS` | 3 | min lessons before curator engages |
|
|
1348
|
+
| `THREADKEEPER_CURATOR_WEB_RESEARCH` | `1` (on) | run a read-only web research child per batch before the web-free evaluator; `0` skips research (the evaluator uses local evidence only) |
|
|
1255
1349
|
| `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 |
|
|
1256
1350
|
| `THREADKEEPER_CURATOR_MAX_DESTRUCTIVE_PER_PASS` | 10 | 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 |
|
|
1257
1351
|
| `THREADKEEPER_CURATOR_SNAPSHOT_RETENTION` | 10 | number of destructive curator pre-mutation snapshots to retain under `<reports_dir>/snapshots`; current pass is always retained |
|
|
@@ -1259,7 +1353,7 @@ The most-used env knobs (full list in `threadkeeper/config.py`):
|
|
|
1259
1353
|
| `THREADKEEPER_PROBE_INTERVAL_S` | 0 (off) | probe daemon tick (s); 1800 = 30 min recommended so finished probe answers are graded promptly |
|
|
1260
1354
|
| `THREADKEEPER_PROBE_COOLDOWN_S` | 604800 | per-category probe cooldown; 86400 = 1d recommended for active reliability tracking |
|
|
1261
1355
|
| `THREADKEEPER_SPAWN_BUDGET_MB` | 3072 | combined child RSS cap (MB); 0 disables |
|
|
1262
|
-
| `THREADKEEPER_ALLOW_BYPASS_PERMISSIONS_SPAWN` | "" (off) | explicit override that lets
|
|
1356
|
+
| `THREADKEEPER_ALLOW_BYPASS_PERMISSIONS_SPAWN` | "" (off) | explicit operator override that lets public `spawn()` calls request `permission_mode="bypassPermissions"`; otherwise only private server-owned Evolve launchers can use it |
|
|
1263
1357
|
| `THREADKEEPER_SPAWN_TOKEN_BUDGET` | 0 | recorded 24h spawned-child token ceiling; 0 disables |
|
|
1264
1358
|
| `THREADKEEPER_SPAWN_COST_BUDGET_USD` | 0 | recorded 24h spawned-child dollar ceiling; 0 disables |
|
|
1265
1359
|
| `THREADKEEPER_SPAWN_MAX_RUNTIME_S` | 3600 | wall-clock lifetime cap (s) for a spawned child; over-cap live children are SIGTERM→SIGKILL'd and closed with `return_code` 124; 0 disables |
|
|
@@ -1296,7 +1390,7 @@ The most-used env knobs (full list in `threadkeeper/config.py`):
|
|
|
1296
1390
|
| `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) |
|
|
1297
1391
|
| `THREADKEEPER_DIALECTIC_VALIDATE_BATCH_SIZE` | 50 | max observations sent to one validator child; prevents oversized prompts and drains large queues incrementally |
|
|
1298
1392
|
| `THREADKEEPER_EVOLVE_REVIEW_INTERVAL_S` | 0 (off) | evolve-reviewer daemon tick (s); audits thread-keeper for safety/leaks/optimization/new ideas, updates roadmap/issues, and includes legacy evolve suggestions as input. Runs as two alternating phases — read-only web research, then a privileged web-free audit that consumes the fenced research digest (#79) — so a full cycle spans two ticks |
|
|
1299
|
-
| `THREADKEEPER_EVOLVE_REVIEW_BACKLOG_MAX` | 25 | max open, not-yet-applied roadmap issues before the issue-creating audit is skipped and records `backlog_saturated`; `0` disables the cap |
|
|
1393
|
+
| `THREADKEEPER_EVOLVE_REVIEW_BACKLOG_MAX` | 25 | max open, not-yet-applied `roadmap`-label issues before the issue-creating audit is skipped and records `backlog_saturated`; skip-labelled and untrusted-author roadmap issues still count; `0` disables the cap |
|
|
1300
1394
|
| `THREADKEEPER_EVOLVE_APPLY_INTERVAL_S` | 0 (off) | evolve-applier daemon tick (s); implements one open GitHub issue at a time, then falls back to Curator reports and promoted legacy evolve suggestions. Empty checks are throttled between intervals; actionable work and manual apply tools still dispatch |
|
|
1301
1395
|
| `THREADKEEPER_EVOLVE_REPO_ROOT` | (auto) | absolute path to the thread-keeper git checkout the evolve reviewer/applier branch, test, and open PRs against. When empty, the repo is resolved automatically: the package's parent dir for an editable `install.sh`, else a managed checkout under the DB dir that is auto-cloned on first use. Set this to pin an explicit checkout |
|
|
1302
1396
|
| `THREADKEEPER_EVOLVE_AUTO_CLONE` | true | auto-provision a managed checkout that runs remote `pip install -e` and tests; set `0`/`false` on shared or multi-user hosts unless that remote-code-execution boundary is explicitly accepted |
|
|
@@ -1307,6 +1401,7 @@ The most-used env knobs (full list in `threadkeeper/config.py`):
|
|
|
1307
1401
|
| `THREADKEEPER_EVOLVE_REPO_PROVISION_LOCK_TIMEOUT_S` | 5 | maximum seconds to wait for another clone/venv provisioning operation before returning `ERR evolve_repo_provisioning_in_progress retry_later=1`; `0` is immediate |
|
|
1308
1402
|
| `THREADKEEPER_EVOLVE_APPLY_SKIP_LABELS` | `blocked,needs-design,wontfix,question,discussion,help wanted` | comma-separated labels that exclude GitHub issues from autonomous Evolve applier pickup. Exact-number apply returns `skipped: label X`; set to `off` to clear |
|
|
1309
1403
|
| `THREADKEEPER_EVOLVE_TRUSTED_AUTHOR_ASSOCIATIONS` | `OWNER,MEMBER,COLLABORATOR` | comma-separated GitHub author associations eligible for **autonomous** issue pickup on this public repo; issues from other authors are skipped unless promoted (trust label or exact-number invocation) |
|
|
1404
|
+
| `THREADKEEPER_EVOLVE_CLAIM_AUTOMATION_ACTORS` | (empty) | comma-separated GitHub logins allowed to own visible Evolve claim comments without a trusted repository association; all other marker comments are advisory text only |
|
|
1310
1405
|
| `THREADKEEPER_EVOLVE_TRUST_LABELS` | (empty) | comma-separated labels that promote an untrusted-author issue into the autonomous queue; on a public repo only collaborators can apply labels, so a trust label is a maintainer endorsement |
|
|
1311
1406
|
| `THREADKEEPER_ROADMAP_ISSUE_MAX_ATTEMPTS` | 3 | poison-issue dead-letter cap: after this many implementer spawns for a roadmap issue with no resulting PR, the issue gets a `blocked` label + one summary comment and is excluded from the auto-drain until a human intervenes. A manual `evolve_apply_roadmap_issue(issue_number=N)` bypasses the cap, but the default skip-label gate still refuses the `blocked` label until it is removed or reconfigured |
|
|
1312
1407
|
| `THREADKEEPER_ROADMAP_ISSUE_BACKOFF_BASE_S` | 172800 (2d) | base failure-backoff window for a roadmap issue; doubles per attempt (`base * 2^(attempts-1)`, capped at 30d). Defers re-selection of a repeatedly-aborting issue beyond the fixed 24h claim TTL |
|
|
@@ -1491,8 +1586,10 @@ hardening, WAL/schema migration, and vec-table setup once per process.
|
|
|
1491
1586
|
`PRAGMA query_only=ON`, so retrieval cannot accidentally migrate, heartbeat,
|
|
1492
1587
|
or write. `run_write()` opens a fresh connection, acquires `BEGIN IMMEDIATE`,
|
|
1493
1588
|
runs a DB-only callback, and closes it; only `SQLITE_BUSY`/`SQLITE_LOCKED` are
|
|
1494
|
-
retried with bounded jitter
|
|
1495
|
-
low-level call sites
|
|
1589
|
+
retried with bounded jitter, including a lock hit while the connection is being
|
|
1590
|
+
set up. `get_db()` remains a compatibility API for older low-level call sites;
|
|
1591
|
+
its connections are tracked, and the daemon host logs the call site of any
|
|
1592
|
+
that keeps a write transaction open for a minute or more.
|
|
1496
1593
|
|
|
1497
1594
|
Schema migration uses SQLite `PRAGMA user_version`: a current database skips
|
|
1498
1595
|
legacy `ALTER TABLE` work, while an old or fresh v0 database migrates once
|
|
@@ -1827,8 +1924,8 @@ pip install -e '.[semantic,dev]'
|
|
|
1827
1924
|
python -m pytest
|
|
1828
1925
|
```
|
|
1829
1926
|
|
|
1830
|
-
|
|
1831
|
-
|
|
1927
|
+
The suite runs on Python 3.11 / 3.12 / 3.13. CI runs it, together with a
|
|
1928
|
+
resolved-dependency `pip-audit` gate, on every push and PR.
|
|
1832
1929
|
CodeQL scans the Python source on those changes and weekly; see
|
|
1833
1930
|
[SECURITY.md](SECURITY.md#continuous-security-scanning) for the exception
|
|
1834
1931
|
process and reporting policy.
|
|
@@ -1840,7 +1937,7 @@ process and reporting policy.
|
|
|
1840
1937
|
```
|
|
1841
1938
|
threadkeeper/
|
|
1842
1939
|
├── server.py # MCP entry: python -m threadkeeper.server
|
|
1843
|
-
├── _mcp.py #
|
|
1940
|
+
├── _mcp.py # MCPServer singleton + read_tool()/write_tool() annotation wrappers
|
|
1844
1941
|
├── tool_schemas.py # typed outputSchema models for the structured status tools
|
|
1845
1942
|
├── _setup.py # `thread-keeper-setup` installer
|
|
1846
1943
|
├── config.py # env-driven defaults
|
|
@@ -1851,7 +1948,7 @@ threadkeeper/
|
|
|
1851
1948
|
├── eval/ # offline learning-loop decision-quality harness (python -m threadkeeper.eval)
|
|
1852
1949
|
├── brief.py # render_brief / render_context
|
|
1853
1950
|
├── shadow_review.py # autonomous learning observer
|
|
1854
|
-
├── i18n.py #
|
|
1951
|
+
├── i18n.py # 12 locales of regex + prompt bundles
|
|
1855
1952
|
├── adapters/ # one file per supported CLI
|
|
1856
1953
|
│ ├── claude_code.py
|
|
1857
1954
|
│ ├── claude_desktop.py
|
|
@@ -1859,7 +1956,7 @@ threadkeeper/
|
|
|
1859
1956
|
│ ├── antigravity.py
|
|
1860
1957
|
│ ├── copilot.py
|
|
1861
1958
|
│ └── vscode.py
|
|
1862
|
-
└── tools/ # @read_tool()/@write_tool() entries
|
|
1959
|
+
└── tools/ # registered @read_tool()/@write_tool() entries
|
|
1863
1960
|
├── threads.py
|
|
1864
1961
|
├── peers.py
|
|
1865
1962
|
├── spawn.py
|
|
@@ -1871,14 +1968,14 @@ threadkeeper/
|
|
|
1871
1968
|
|
|
1872
1969
|
**Tool annotation contract (#67).** Every tool registers through
|
|
1873
1970
|
`@read_tool()` or `@write_tool(destructive=…, idempotent=…)` (in `_mcp.py`),
|
|
1874
|
-
so `tools/list` carries MCP 2025-06-18 `ToolAnnotations` for
|
|
1875
|
-
`readOnlyHint=True` for pure reads (`brief`, `context`, `search`,
|
|
1971
|
+
so `tools/list` carries MCP 2025-06-18 `ToolAnnotations` for every
|
|
1972
|
+
registered tool: `readOnlyHint=True` for pure reads (`brief`, `context`, `search`,
|
|
1876
1973
|
`dialog_search`, the status tools, …) and `readOnlyHint=False`
|
|
1877
1974
|
for mutations. `lesson_list` / `lesson_get` are classified as non-destructive
|
|
1878
|
-
writes because they bump lesson access counters.
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
warrant a prompt. The
|
|
1975
|
+
writes because they bump lesson access counters. Delete/overwrite/kill tools
|
|
1976
|
+
carry `destructiveHint=True` (`compost` is read-only — it only surfaces idle
|
|
1977
|
+
threads). A confirmation/elicitation host reads this to decide which calls
|
|
1978
|
+
warrant a prompt. The status tools (`context`, `spawn_budget_status`,
|
|
1882
1979
|
`spawn_status`, `mp_health`, `agent_status`) additionally advertise an
|
|
1883
1980
|
`outputSchema` and return `structuredContent` alongside the legacy text
|
|
1884
1981
|
block. The contract is enforced by `tests/test_tool_annotations.py`.
|