claude-smart 0.2.44 → 0.2.46
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.
- package/.claude-plugin/marketplace.json +3 -3
- package/README.md +34 -5
- package/bin/claude-smart.js +295 -5
- package/package.json +22 -3
- package/plugin/.claude-plugin/plugin.json +4 -2
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/README.md +22 -2
- package/plugin/commands/clear-all.md +1 -0
- package/plugin/commands/dashboard.md +1 -0
- package/plugin/commands/learn.md +1 -0
- package/plugin/commands/restart.md +1 -0
- package/plugin/commands/show.md +1 -0
- package/plugin/dashboard/app/configure/env/page.tsx +40 -14
- package/plugin/dashboard/app/configure/server/page.tsx +51 -1
- package/plugin/dashboard/app/preferences/[id]/page.tsx +8 -8
- package/plugin/dashboard/app/skills/project/[id]/page.tsx +7 -3
- package/plugin/dashboard/app/skills/shared/[id]/page.tsx +10 -3
- package/plugin/dashboard/lib/claude-settings-file.ts +20 -10
- package/plugin/dashboard/lib/reflexio-client.ts +16 -0
- package/plugin/dashboard/lib/status.ts +10 -3
- package/plugin/dashboard/lib/types.ts +18 -6
- package/plugin/hooks/codex-hooks.json +0 -1
- package/plugin/opencode/assistant-buffer.ts +108 -0
- package/plugin/opencode/dist/assistant-buffer.js +96 -0
- package/plugin/opencode/dist/internal.js +12 -0
- package/plugin/opencode/dist/payload.js +85 -0
- package/plugin/opencode/dist/server.mjs +158 -0
- package/plugin/opencode/internal.ts +18 -0
- package/plugin/opencode/package.json +3 -0
- package/plugin/opencode/payload.ts +90 -0
- package/plugin/opencode/server.mts +174 -0
- package/plugin/opencode/tsconfig.json +13 -0
- package/plugin/pyproject.toml +20 -3
- package/plugin/scripts/_lib.sh +77 -29
- package/plugin/scripts/backend-service.sh +27 -5
- package/plugin/scripts/dashboard-service.sh +7 -5
- package/plugin/scripts/ensure-plugin-root.sh +4 -4
- package/plugin/scripts/hook_entry.sh +3 -3
- package/plugin/scripts/opencode-claude-compat +4 -0
- package/plugin/scripts/opencode-claude-compat.cmd +3 -0
- package/plugin/scripts/opencode-claude-compat.js +225 -0
- package/plugin/scripts/smart-install.sh +10 -8
- package/plugin/src/README.md +1 -1
- package/plugin/src/claude_smart/cli.py +304 -6
- package/plugin/src/claude_smart/env_config.py +12 -4
- package/plugin/src/claude_smart/events/session_start.py +26 -7
- package/plugin/src/claude_smart/events/stop.py +1 -1
- package/plugin/src/claude_smart/ids.py +1 -1
- package/plugin/src/claude_smart/reflexio_adapter.py +5 -5
- package/plugin/src/claude_smart/runtime.py +7 -1
- package/plugin/uv.lock +1 -1
- package/plugin/vendor/reflexio/.env.example +54 -30
- package/plugin/vendor/reflexio/README.md +14 -8
- package/plugin/vendor/reflexio/pyproject.toml +13 -1
- package/plugin/vendor/reflexio/reflexio/README.md +1 -0
- package/plugin/vendor/reflexio/reflexio/cli/README.md +9 -7
- package/plugin/vendor/reflexio/reflexio/cli/__main__.py +10 -1
- package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +22 -5
- package/plugin/vendor/reflexio/reflexio/cli/commands/interactions.py +34 -4
- package/plugin/vendor/reflexio/reflexio/cli/commands/profiles.py +12 -7
- package/plugin/vendor/reflexio/reflexio/cli/commands/services.py +13 -6
- package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +210 -50
- package/plugin/vendor/reflexio/reflexio/cli/commands/shortcuts.py +15 -2
- package/plugin/vendor/reflexio/reflexio/cli/env_loader.py +292 -22
- package/plugin/vendor/reflexio/reflexio/cli/log_format.py +29 -4
- package/plugin/vendor/reflexio/reflexio/cli/run_services.py +17 -8
- package/plugin/vendor/reflexio/reflexio/cli/stop_services.py +13 -10
- package/plugin/vendor/reflexio/reflexio/client/client.py +57 -13
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/README.md +135 -257
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/bin/openclaw-smart.js +13 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/package.json +15 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/README.md +38 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/index.ts +151 -110
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/openclaw.plugin.json +20 -10
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/package.json +28 -6
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/pyproject.toml +42 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/_lib.sh +371 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-log-runner.sh +33 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-service.sh +271 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/cli.sh +68 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/dashboard-open.sh +15 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/ensure-plugin-root.sh +84 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/hook_entry.sh +106 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/npm-cli.js +219 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/smart-install.sh +269 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/clear-all/SKILL.md +8 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/dashboard/SKILL.md +8 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/learn/SKILL.md +10 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md +15 -44
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/restart/SKILL.md +6 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/show/SKILL.md +8 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/cli.py +634 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_format.py +224 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_inject.py +79 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/after_tool_call.py +160 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/agent_end.py +187 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_prompt_build.py +69 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_tool_call.py +30 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_end.py +36 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_start.py +130 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/hook.py +131 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/ids.py +94 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/internal_call.py +75 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/oc_cite.py +196 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/optimizer_assistant.py +272 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/publish.py +96 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/query_compose.py +66 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/reflexio_adapter.py +336 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/runtime.py +47 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/stall_banner.py +61 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py +323 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_e2e_session_loop.py +190 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_publish_to_local_reflexio_integration.py +112 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_recursion_guard_integration.py +86 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_search_inject_integration.py +144 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_cli.py +184 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_after_tool_call.py +142 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_agent_end.py +233 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_prompt_build.py +116 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_tool_call.py +35 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_end.py +47 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_start.py +109 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_hook_dispatch.py +117 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_ids.py +41 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_internal_call.py +50 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_oc_cite.py +88 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_publish.py +48 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_query_compose.py +48 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_reflexio_adapter.py +188 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_runtime.py +41 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_state.py +235 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/__mocks__/plugin-entry-stub.ts +6 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_npm_cli.test.ts +56 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_shim_dispatch.test.ts +171 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tsconfig.build.json +18 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{tsconfig.json → plugin/tsconfig.json} +2 -2
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/uv.lock +3835 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/vitest.config.ts +18 -0
- package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +238 -10
- package/plugin/vendor/reflexio/reflexio/lib/_config.py +11 -4
- package/plugin/vendor/reflexio/reflexio/lib/_generation.py +16 -5
- package/plugin/vendor/reflexio/reflexio/lib/_lineage_parity_readers.py +187 -0
- package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +198 -6
- package/plugin/vendor/reflexio/reflexio/lib/_reflection.py +1 -1
- package/plugin/vendor/reflexio/reflexio/lib/_search.py +29 -20
- package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +2 -1
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +127 -38
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +4 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +64 -41
- package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +19 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/pending_tool_call_schema.py +12 -5
- package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +52 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +5 -1
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +5 -1
- package/plugin/vendor/reflexio/reflexio/models/config_schema.py +88 -7
- package/plugin/vendor/reflexio/reflexio/models/structured_output.py +148 -0
- package/plugin/vendor/reflexio/reflexio/server/OVERVIEW.md +3 -3
- package/plugin/vendor/reflexio/reflexio/server/README.md +45 -32
- package/plugin/vendor/reflexio/reflexio/server/__init__.py +29 -5
- package/plugin/vendor/reflexio/reflexio/server/__main__.py +2 -2
- package/plugin/vendor/reflexio/reflexio/server/_auth.py +65 -2
- package/plugin/vendor/reflexio/reflexio/server/api.py +734 -85
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/precondition_checks.py +7 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +3 -1
- package/plugin/vendor/reflexio/reflexio/server/billing_meter.py +173 -0
- package/plugin/vendor/reflexio/reflexio/server/billing_signals.py +64 -0
- package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +46 -16
- package/plugin/vendor/reflexio/reflexio/server/env_utils.py +65 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -4
- package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +264 -77
- package/plugin/vendor/reflexio/reflexio/server/llm/llm_utils.py +69 -7
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +75 -16
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/openclaw_provider.py +280 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/token_accounting.py +48 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/tools.py +107 -17
- package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +360 -4
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.6.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.7.0.prompt.md +85 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.2.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.3.0.prompt.md +253 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.2.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.3.prompt.md +74 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.3.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.3.0.prompt.md +266 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.4.0.prompt.md +266 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.3.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.4.0.prompt.md +137 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.2.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.3.0.prompt.md +34 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.0.0.prompt.md +5 -12
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.1.0.prompt.md +5 -12
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.2.0.prompt.md +155 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/tagging/v1.0.0.prompt.md +23 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_manager.py +89 -32
- package/plugin/vendor/reflexio/reflexio/server/services/README.md +26 -11
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/README.md +22 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_constants.py +4 -2
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_utils.py +1 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/components/__init__.py +7 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluator.py → components/evaluator.py} +3 -16
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +28 -72
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{group_evaluation_runner.py → runner.py} +12 -10
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{delayed_group_evaluator.py → scheduler.py} +10 -2
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluation_service.py → service.py} +36 -1
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +160 -0
- package/plugin/vendor/reflexio/reflexio/server/services/configurator/base_configurator.py +5 -0
- package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/README.md +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{rule_attribution.py → components/rule_attribution.py} +10 -9
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/eval_sampler.py +15 -24
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +238 -164
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/README.md +31 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +13 -3
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/pending_tool_call_dispatch.py +3 -2
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +32 -118
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +39 -16
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +105 -23
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +193 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/resolve.py +98 -0
- package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +26 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +11 -12
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/__init__.py +5 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_aggregator.py → components/aggregator.py} +316 -178
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_consolidator.py → components/consolidator.py} +168 -62
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_extractor.py → components/extractor.py} +5 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_edit_apply.py +74 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_utils.py +33 -3
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_generation_service.py → service.py} +121 -19
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/user_detail_stripping.py +84 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/README.md +10 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/models.py +2 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +178 -30
- package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/README.md +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/profile/components/__init__.py +17 -0
- package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_deduplicator.py → components/consolidator.py} +7 -6
- package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_extractor.py → components/extractor.py} +30 -22
- package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service_utils.py +6 -13
- package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_generation_service.py → service.py} +32 -50
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/__init__.py +4 -8
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/components/__init__.py +7 -0
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service_utils.py +12 -1
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_service.py → service.py} +36 -16
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +83 -6
- package/plugin/vendor/reflexio/reflexio/server/services/service_utils.py +36 -1
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/README.md +8 -0
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/outcome.py +1 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +27 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +9 -9
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +2 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +4 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +642 -197
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +61 -104
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +628 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_operations.py +38 -6
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +978 -184
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +519 -128
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +77 -3
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +23 -9
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_stall_state.py +5 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +131 -20
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +2 -8
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_base.py +5 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +36 -53
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_lineage.py +215 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +20 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +243 -7
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +121 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +60 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py +51 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +17 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/README.md +8 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/service.py +200 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/tagging_scheduler.py +149 -0
- package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +229 -32
- package/plugin/vendor/reflexio/reflexio/server/site_var/README.md +2 -1
- package/plugin/vendor/reflexio/reflexio/server/site_var/feature_flags.py +120 -1
- package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/feature_flags.json +4 -0
- package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/search_settings.json +5 -0
- package/plugin/vendor/reflexio/reflexio/server/tracing.py +30 -0
- package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +18 -0
- package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +61 -26
- package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +41 -4
- package/scripts/setup-claude-smart.sh +8 -3
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/TESTING.md +0 -517
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/hook/handler.js +0 -473
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package-lock.json +0 -2156
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package.json +0 -18
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/handler.ts +0 -241
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/setup.ts +0 -140
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/publish.ts +0 -113
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/search.ts +0 -52
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/server.ts +0 -103
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/sqlite-buffer.ts +0 -156
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/user-id.ts +0 -134
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/rules/reflexio.md +0 -24
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/publish_clawhub.sh +0 -278
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/references/HOOK.md +0 -164
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/install.sh +0 -36
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/uninstall.sh +0 -35
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/publish.test.ts +0 -27
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/search.test.ts +0 -31
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/server.test.ts +0 -42
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/setup.test.ts +0 -49
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/sqlite-buffer.test.ts +0 -91
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/user-id.test.ts +0 -50
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/vitest.config.ts +0 -13
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/group_aggregation.py +0 -192
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/invariants.py +0 -303
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/plan.py +0 -138
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/tools.py +0 -1125
- /package/plugin/vendor/reflexio/reflexio/integrations/{__init__.py → openclaw/plugin/src/openclaw_smart/__init__.py} +0 -0
- /package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{types → plugin/types}/openclaw.d.ts +0 -0
- /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{distribution.py → components/distribution.py} +0 -0
- /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{hero_state.py → components/hero_state.py} +0 -0
- /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{shadow_aggregation.py → components/shadow_aggregation.py} +0 -0
- /package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_extractor.py → components/extractor.py} +0 -0
|
@@ -14,7 +14,7 @@ import math
|
|
|
14
14
|
import re
|
|
15
15
|
import sqlite3
|
|
16
16
|
import threading
|
|
17
|
-
from collections.abc import Callable
|
|
17
|
+
from collections.abc import Callable, Sequence
|
|
18
18
|
from datetime import UTC, datetime
|
|
19
19
|
from pathlib import Path
|
|
20
20
|
from typing import Any, ClassVar, Literal
|
|
@@ -22,14 +22,10 @@ from typing import Any, ClassVar, Literal
|
|
|
22
22
|
from reflexio.models.api_schema.common import BlockingIssue
|
|
23
23
|
from reflexio.models.api_schema.service_schemas import (
|
|
24
24
|
AgentPlaybook,
|
|
25
|
-
AgentPlaybookSnapshot,
|
|
26
|
-
AgentPlaybookUpdateEntry,
|
|
27
25
|
AgentSuccessEvaluationResult,
|
|
28
26
|
Citation,
|
|
29
27
|
Interaction,
|
|
30
|
-
PlaybookAggregationChangeLog,
|
|
31
28
|
PlaybookStatus,
|
|
32
|
-
ProfileChangeLog,
|
|
33
29
|
ProfileTimeToLive,
|
|
34
30
|
RegularVsShadow,
|
|
35
31
|
Request,
|
|
@@ -53,7 +49,10 @@ from reflexio.server.llm.model_defaults import (
|
|
|
53
49
|
from reflexio.server.llm.providers.embedding_service_provider import (
|
|
54
50
|
EmbeddingUnavailableError,
|
|
55
51
|
)
|
|
56
|
-
from reflexio.server.services.storage.error import
|
|
52
|
+
from reflexio.server.services.storage.error import (
|
|
53
|
+
StorageError,
|
|
54
|
+
require_non_empty_session_id,
|
|
55
|
+
)
|
|
57
56
|
from reflexio.server.services.storage.retention import RetentionTarget
|
|
58
57
|
from reflexio.server.services.storage.retention_mixin import (
|
|
59
58
|
RETENTION_DELETE_CHUNK,
|
|
@@ -182,10 +181,10 @@ def _effective_search_mode(
|
|
|
182
181
|
|
|
183
182
|
|
|
184
183
|
def _vector_rank_rows(
|
|
185
|
-
rows:
|
|
184
|
+
rows: Sequence[Any],
|
|
186
185
|
query_embedding: list[float],
|
|
187
186
|
match_count: int,
|
|
188
|
-
) -> list[
|
|
187
|
+
) -> list[Any]:
|
|
189
188
|
"""Rank rows by cosine similarity to the query embedding.
|
|
190
189
|
|
|
191
190
|
Args:
|
|
@@ -196,7 +195,7 @@ def _vector_rank_rows(
|
|
|
196
195
|
Returns:
|
|
197
196
|
Top ``match_count`` rows sorted by cosine similarity descending.
|
|
198
197
|
"""
|
|
199
|
-
scored: list[tuple[
|
|
198
|
+
scored: list[tuple[Any, float]] = []
|
|
200
199
|
for row in rows:
|
|
201
200
|
raw_emb = row["embedding"] if "embedding" in row.keys() else None # noqa: SIM118
|
|
202
201
|
emb = _json_loads(raw_emb) if raw_emb else None
|
|
@@ -225,14 +224,14 @@ def _vector_rank_rows(
|
|
|
225
224
|
|
|
226
225
|
|
|
227
226
|
def _true_rrf_merge(
|
|
228
|
-
fts_rows:
|
|
229
|
-
vec_rows:
|
|
227
|
+
fts_rows: Sequence[Any],
|
|
228
|
+
vec_rows: Sequence[Any],
|
|
230
229
|
id_column: str,
|
|
231
230
|
match_count: int,
|
|
232
231
|
rrf_k: int = 60,
|
|
233
232
|
vector_weight: float = 1.0,
|
|
234
233
|
fts_weight: float = 1.0,
|
|
235
|
-
) -> list[
|
|
234
|
+
) -> list[Any]:
|
|
236
235
|
"""Merge independent FTS and vector result sets via Reciprocal Rank Fusion.
|
|
237
236
|
|
|
238
237
|
Unlike ``_rrf_rerank`` (which re-ranks FTS results only), this function
|
|
@@ -255,7 +254,7 @@ def _true_rrf_merge(
|
|
|
255
254
|
return []
|
|
256
255
|
|
|
257
256
|
# Collect unique rows by ID (first-seen wins for the Row object)
|
|
258
|
-
row_by_id: dict[str | int,
|
|
257
|
+
row_by_id: dict[str | int, Any] = {}
|
|
259
258
|
for row in (*fts_rows, *vec_rows):
|
|
260
259
|
rid = row[id_column]
|
|
261
260
|
if rid not in row_by_id:
|
|
@@ -271,7 +270,7 @@ def _true_rrf_merge(
|
|
|
271
270
|
fts_penalty = len(fts_rows) + 1
|
|
272
271
|
vec_penalty = len(vec_rows) + 1
|
|
273
272
|
|
|
274
|
-
scored: list[tuple[
|
|
273
|
+
scored: list[tuple[Any, float]] = []
|
|
275
274
|
for rid, row in row_by_id.items():
|
|
276
275
|
f_rank = fts_rank.get(rid, fts_penalty)
|
|
277
276
|
v_rank = vec_rank.get(rid, vec_penalty)
|
|
@@ -282,6 +281,13 @@ def _true_rrf_merge(
|
|
|
282
281
|
return [row for row, _ in scored[:match_count]]
|
|
283
282
|
|
|
284
283
|
|
|
284
|
+
# Tombstone statuses: rows with these values are excluded from default reads.
|
|
285
|
+
# Tasks 5/9/10 create tombstones; this constant ensures they stay hidden unless
|
|
286
|
+
# explicitly requested via include_tombstones=True on by-id getters, or an
|
|
287
|
+
# explicit status_filter on list/count methods.
|
|
288
|
+
_TOMBSTONE_STATUS_VALUES = (Status.MERGED.value, Status.SUPERSEDED.value)
|
|
289
|
+
|
|
290
|
+
|
|
285
291
|
def _status_value(status: Status | None) -> str | None:
|
|
286
292
|
"""Convert a Status enum (or None) to its DB string value."""
|
|
287
293
|
if status is None:
|
|
@@ -340,14 +346,32 @@ def _iso_to_epoch(iso_str: str | None) -> int:
|
|
|
340
346
|
return _epoch_now()
|
|
341
347
|
try:
|
|
342
348
|
cleaned = iso_str.replace("Z", "+00:00")
|
|
343
|
-
|
|
349
|
+
parsed = datetime.fromisoformat(cleaned)
|
|
350
|
+
if parsed.tzinfo is None:
|
|
351
|
+
parsed = parsed.replace(tzinfo=UTC)
|
|
352
|
+
return int(parsed.timestamp())
|
|
344
353
|
except (ValueError, TypeError):
|
|
345
354
|
return _epoch_now()
|
|
346
355
|
|
|
347
356
|
|
|
357
|
+
# Bounds that ``datetime.fromtimestamp(tz=UTC)`` can represent (year 1..9999).
|
|
358
|
+
# Callers pass sentinel "open" bounds — e.g. ``to_ts=10**12`` for "no upper
|
|
359
|
+
# limit" or ``0`` for "from the beginning" — which would otherwise overflow
|
|
360
|
+
# ``datetime.fromtimestamp`` with a ``ValueError``. Clamping to these bounds
|
|
361
|
+
# yields the same query semantics (the ISO string still sorts before/after every
|
|
362
|
+
# stored row) with a valid value.
|
|
363
|
+
_MAX_SAFE_EPOCH_TS = 253_402_300_799 # 9999-12-31T23:59:59Z
|
|
364
|
+
_MIN_SAFE_EPOCH_TS = 0 # 1970-01-01T00:00:00Z
|
|
365
|
+
|
|
366
|
+
|
|
348
367
|
def _epoch_to_iso(ts: int) -> str:
|
|
349
|
-
"""Convert a Unix timestamp to ISO 8601 string.
|
|
350
|
-
|
|
368
|
+
"""Convert a Unix timestamp (seconds) to an ISO 8601 string.
|
|
369
|
+
|
|
370
|
+
Out-of-range sentinel bounds are clamped to the representable range so that
|
|
371
|
+
callers passing "open" window bounds never trigger a ``ValueError``.
|
|
372
|
+
"""
|
|
373
|
+
clamped = max(_MIN_SAFE_EPOCH_TS, min(ts, _MAX_SAFE_EPOCH_TS))
|
|
374
|
+
return datetime.fromtimestamp(clamped, tz=UTC).isoformat()
|
|
351
375
|
|
|
352
376
|
|
|
353
377
|
# ---------------------------------------------------------------------------
|
|
@@ -373,6 +397,10 @@ def _row_to_profile(row: sqlite3.Row) -> UserProfile:
|
|
|
373
397
|
source_span=d.get("source_span"),
|
|
374
398
|
notes=d.get("notes"),
|
|
375
399
|
reader_angle=d.get("reader_angle"),
|
|
400
|
+
tags=_json_loads(d.get("tags")),
|
|
401
|
+
source_interaction_ids=_json_loads(d.get("source_interaction_ids")) or [],
|
|
402
|
+
merged_into=d.get("merged_into"),
|
|
403
|
+
superseded_by=d.get("superseded_by"),
|
|
376
404
|
)
|
|
377
405
|
|
|
378
406
|
|
|
@@ -400,6 +428,7 @@ def _row_to_interaction(row: sqlite3.Row) -> Interaction:
|
|
|
400
428
|
user_action=UserActionType(d["user_action"]),
|
|
401
429
|
user_action_description=d["user_action_description"],
|
|
402
430
|
interacted_image_url=d["interacted_image_url"],
|
|
431
|
+
image_encoding=d.get("image_encoding") or "",
|
|
403
432
|
shadow_content=d.get("shadow_content") or "",
|
|
404
433
|
expert_content=d.get("expert_content") or "",
|
|
405
434
|
tools_used=tools_used,
|
|
@@ -409,24 +438,14 @@ def _row_to_interaction(row: sqlite3.Row) -> Interaction:
|
|
|
409
438
|
|
|
410
439
|
def _row_to_request(row: sqlite3.Row) -> Request:
|
|
411
440
|
d = dict(row)
|
|
412
|
-
metadata_raw = d.get("metadata") or "{}"
|
|
413
|
-
try:
|
|
414
|
-
parsed = _json_loads(metadata_raw)
|
|
415
|
-
metadata = parsed if isinstance(parsed, dict) else {}
|
|
416
|
-
except json.JSONDecodeError:
|
|
417
|
-
logger.warning(
|
|
418
|
-
"Malformed metadata JSON for request %s; defaulting to empty dict",
|
|
419
|
-
d.get("request_id"),
|
|
420
|
-
)
|
|
421
|
-
metadata = {}
|
|
422
441
|
return Request(
|
|
423
442
|
request_id=d["request_id"],
|
|
424
443
|
user_id=d["user_id"],
|
|
425
444
|
created_at=_iso_to_epoch(d["created_at"]),
|
|
426
445
|
source=d.get("source") or "",
|
|
427
446
|
agent_version=d.get("agent_version") or "",
|
|
428
|
-
session_id=d.get("session_id"),
|
|
429
|
-
|
|
447
|
+
session_id=require_non_empty_session_id(d.get("session_id")),
|
|
448
|
+
evaluation_only=bool(d.get("evaluation_only", 0)),
|
|
430
449
|
)
|
|
431
450
|
|
|
432
451
|
|
|
@@ -455,11 +474,14 @@ def _row_to_user_playbook(
|
|
|
455
474
|
status=Status(d["status"]) if d.get("status") else None,
|
|
456
475
|
source=d.get("source"),
|
|
457
476
|
source_interaction_ids=_json_loads(d.get("source_interaction_ids")) or [],
|
|
477
|
+
tags=_json_loads(d.get("tags")),
|
|
458
478
|
embedding=embedding,
|
|
459
479
|
expanded_terms=d.get("expanded_terms"),
|
|
460
480
|
source_span=d.get("source_span"),
|
|
461
481
|
notes=d.get("notes"),
|
|
462
482
|
reader_angle=d.get("reader_angle"),
|
|
483
|
+
merged_into=d.get("merged_into"),
|
|
484
|
+
superseded_by=d.get("superseded_by"),
|
|
463
485
|
)
|
|
464
486
|
|
|
465
487
|
|
|
@@ -480,9 +502,12 @@ def _row_to_agent_playbook(row: sqlite3.Row) -> AgentPlaybook:
|
|
|
480
502
|
if d.get("playbook_status")
|
|
481
503
|
else PlaybookStatus.PENDING,
|
|
482
504
|
playbook_metadata=d.get("playbook_metadata") or "",
|
|
505
|
+
tags=_json_loads(d.get("tags")),
|
|
483
506
|
embedding=[],
|
|
484
507
|
status=Status(d["status"]) if d.get("status") else None,
|
|
485
508
|
expanded_terms=d.get("expanded_terms"),
|
|
509
|
+
merged_into=d.get("merged_into"),
|
|
510
|
+
superseded_by=d.get("superseded_by"),
|
|
486
511
|
)
|
|
487
512
|
|
|
488
513
|
|
|
@@ -490,6 +515,7 @@ def _row_to_eval_result(row: sqlite3.Row) -> AgentSuccessEvaluationResult:
|
|
|
490
515
|
d = dict(row)
|
|
491
516
|
return AgentSuccessEvaluationResult(
|
|
492
517
|
result_id=d["result_id"],
|
|
518
|
+
user_id=d.get("user_id") or "",
|
|
493
519
|
session_id=d["session_id"],
|
|
494
520
|
agent_version=d["agent_version"],
|
|
495
521
|
evaluation_name=d.get("evaluation_name"),
|
|
@@ -509,53 +535,6 @@ def _row_to_eval_result(row: sqlite3.Row) -> AgentSuccessEvaluationResult:
|
|
|
509
535
|
)
|
|
510
536
|
|
|
511
537
|
|
|
512
|
-
def _row_to_profile_change_log(row: sqlite3.Row) -> ProfileChangeLog:
|
|
513
|
-
d = dict(row)
|
|
514
|
-
return ProfileChangeLog(
|
|
515
|
-
id=d["id"],
|
|
516
|
-
user_id=d["user_id"],
|
|
517
|
-
request_id=d["request_id"],
|
|
518
|
-
created_at=d["created_at"],
|
|
519
|
-
added_profiles=[
|
|
520
|
-
UserProfile(**p) for p in (_json_loads(d["added_profiles"]) or [])
|
|
521
|
-
],
|
|
522
|
-
removed_profiles=[
|
|
523
|
-
UserProfile(**p) for p in (_json_loads(d["removed_profiles"]) or [])
|
|
524
|
-
],
|
|
525
|
-
mentioned_profiles=[
|
|
526
|
-
UserProfile(**p) for p in (_json_loads(d["mentioned_profiles"]) or [])
|
|
527
|
-
],
|
|
528
|
-
)
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
def _row_to_playbook_aggregation_change_log(
|
|
532
|
-
row: sqlite3.Row,
|
|
533
|
-
) -> PlaybookAggregationChangeLog:
|
|
534
|
-
d = dict(row)
|
|
535
|
-
return PlaybookAggregationChangeLog(
|
|
536
|
-
id=d["id"],
|
|
537
|
-
created_at=d["created_at"],
|
|
538
|
-
playbook_name=d["playbook_name"],
|
|
539
|
-
agent_version=d["agent_version"],
|
|
540
|
-
run_mode=d["run_mode"],
|
|
541
|
-
added_agent_playbooks=[
|
|
542
|
-
AgentPlaybookSnapshot(**fb)
|
|
543
|
-
for fb in (_json_loads(d.get("added_playbooks")) or [])
|
|
544
|
-
],
|
|
545
|
-
removed_agent_playbooks=[
|
|
546
|
-
AgentPlaybookSnapshot(**fb)
|
|
547
|
-
for fb in (_json_loads(d.get("removed_playbooks")) or [])
|
|
548
|
-
],
|
|
549
|
-
updated_agent_playbooks=[
|
|
550
|
-
AgentPlaybookUpdateEntry(
|
|
551
|
-
before=AgentPlaybookSnapshot(**entry["before"]),
|
|
552
|
-
after=AgentPlaybookSnapshot(**entry["after"]),
|
|
553
|
-
)
|
|
554
|
-
for entry in (_json_loads(d.get("updated_playbooks")) or [])
|
|
555
|
-
],
|
|
556
|
-
)
|
|
557
|
-
|
|
558
|
-
|
|
559
538
|
# ---------------------------------------------------------------------------
|
|
560
539
|
# SQLiteStorageBase
|
|
561
540
|
# ---------------------------------------------------------------------------
|
|
@@ -652,6 +631,14 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
652
631
|
def migrate(self) -> bool:
|
|
653
632
|
self._migrate_feedback_schema()
|
|
654
633
|
self._migrate_interactions_schema()
|
|
634
|
+
# Backfill columns that _DDL indexes depend on BEFORE running _DDL.
|
|
635
|
+
# _DDL builds idx_eval_identity_created_at_desc on
|
|
636
|
+
# agent_success_evaluation_result(user_id, ...). On a pre-existing DB that
|
|
637
|
+
# table predates user_id, so executescript(_DDL) raises "no such column:
|
|
638
|
+
# user_id" and migrate() never reaches the backfill below — leaving the DB
|
|
639
|
+
# permanently stuck. The helper is guarded (no-ops when the table is
|
|
640
|
+
# absent), so running it before _DDL is safe on fresh databases too.
|
|
641
|
+
self._migrate_eval_result_user_id()
|
|
655
642
|
with self._lock:
|
|
656
643
|
cur = self.conn.cursor()
|
|
657
644
|
cur.executescript(_DDL)
|
|
@@ -663,11 +650,22 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
663
650
|
self._migrate_agent_runs_schema()
|
|
664
651
|
self._migrate_pending_tool_calls_schema()
|
|
665
652
|
self._migrate_expanded_terms()
|
|
653
|
+
self._migrate_tags()
|
|
654
|
+
self._migrate_profile_source_interaction_ids()
|
|
655
|
+
self._migrate_interaction_window_indexes()
|
|
666
656
|
self._migrate_agentic_signals()
|
|
667
657
|
self._migrate_agent_playbook_source_windows()
|
|
668
|
-
self.
|
|
658
|
+
self._migrate_request_evaluation_only()
|
|
659
|
+
self._migrate_request_session_id_required()
|
|
660
|
+
# _migrate_eval_result_user_id() runs before _DDL (see above).
|
|
669
661
|
self._migrate_shadow_comparison_verdicts()
|
|
670
662
|
self._migrate_user_playbook_polarity()
|
|
663
|
+
self._migrate_lineage()
|
|
664
|
+
self._migrate_retired_at()
|
|
665
|
+
self._migrate_lineage_event_table()
|
|
666
|
+
self._migrate_playbook_optimization_candidate_metadata()
|
|
667
|
+
self._migrate_retire_profile_change_logs()
|
|
668
|
+
self._migrate_retire_playbook_aggregation_change_logs()
|
|
671
669
|
init_stall_state_table(self.conn)
|
|
672
670
|
return True
|
|
673
671
|
|
|
@@ -706,9 +704,13 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
706
704
|
# Wrap dependency + target deletes in a single critical section so
|
|
707
705
|
# concurrent writers see either both or neither.
|
|
708
706
|
with self._lock:
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
707
|
+
try:
|
|
708
|
+
self._retention_delete_dependencies(target, keys)
|
|
709
|
+
self._retention_delete_target_rows(target, keys)
|
|
710
|
+
self.conn.commit()
|
|
711
|
+
except Exception:
|
|
712
|
+
self.conn.rollback()
|
|
713
|
+
raise
|
|
712
714
|
|
|
713
715
|
def _retention_delete_dependencies(
|
|
714
716
|
self, target: RetentionTarget, keys: list[tuple[Any, ...]]
|
|
@@ -723,10 +725,14 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
723
725
|
self._delete_profile_search_rows([str(v) for v in ids])
|
|
724
726
|
elif target_name == "user_playbooks":
|
|
725
727
|
self._delete_source_windows_for_user_playbook_ids([int(v) for v in ids])
|
|
726
|
-
self._delete_playbook_search_rows(
|
|
728
|
+
self._delete_playbook_search_rows(
|
|
729
|
+
"user", [int(v) for v in ids], commit=False
|
|
730
|
+
)
|
|
727
731
|
elif target_name == "agent_playbooks":
|
|
728
732
|
self._delete_source_windows_for_agent_playbook_ids([int(v) for v in ids])
|
|
729
|
-
self._delete_playbook_search_rows(
|
|
733
|
+
self._delete_playbook_search_rows(
|
|
734
|
+
"agent", [int(v) for v in ids], commit=False
|
|
735
|
+
)
|
|
730
736
|
elif target_name == "playbook_optimization_jobs":
|
|
731
737
|
self._delete_optimizer_rows_for_job_ids([int(v) for v in ids])
|
|
732
738
|
elif target_name == "playbook_optimization_candidates":
|
|
@@ -801,29 +807,63 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
801
807
|
self._delete_in_chunks("interactions", "request_id", request_ids)
|
|
802
808
|
|
|
803
809
|
def _delete_interaction_search_rows(self, interaction_ids: list[int]) -> None:
|
|
810
|
+
"""Remove fts + vec index rows for the given interaction IDs.
|
|
811
|
+
|
|
812
|
+
Non-committing: participates in the caller's transaction. Only called
|
|
813
|
+
from inside the retention atomic block (_retention_perform_delete).
|
|
814
|
+
"""
|
|
804
815
|
if not interaction_ids:
|
|
805
816
|
return
|
|
806
817
|
self._delete_in_chunks("interactions_fts", "rowid", interaction_ids)
|
|
807
|
-
|
|
808
|
-
self.
|
|
818
|
+
if self._has_sqlite_vec:
|
|
819
|
+
self._delete_in_chunks("interactions_vec", "rowid", interaction_ids)
|
|
809
820
|
|
|
810
821
|
def _delete_profile_search_rows(self, profile_ids: list[str]) -> None:
|
|
822
|
+
"""Remove fts + vec index rows for the given profile IDs.
|
|
823
|
+
|
|
824
|
+
Non-committing: participates in the caller's transaction. Only called
|
|
825
|
+
from inside the retention atomic block (_retention_perform_delete).
|
|
826
|
+
profiles_fts is keyed by profile_id (TEXT); profiles_vec by rowid (INT).
|
|
827
|
+
"""
|
|
811
828
|
if not profile_ids:
|
|
812
829
|
return
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
830
|
+
self._delete_in_chunks("profiles_fts", "profile_id", profile_ids)
|
|
831
|
+
if self._has_sqlite_vec:
|
|
832
|
+
rows = self._select_in_chunks(
|
|
833
|
+
"SELECT rowid FROM profiles WHERE profile_id IN ({placeholders})",
|
|
834
|
+
profile_ids,
|
|
835
|
+
)
|
|
836
|
+
rowids = [row["rowid"] for row in rows]
|
|
837
|
+
if rowids:
|
|
838
|
+
self._delete_in_chunks("profiles_vec", "rowid", rowids)
|
|
820
839
|
|
|
821
|
-
def _delete_playbook_search_rows(
|
|
840
|
+
def _delete_playbook_search_rows(
|
|
841
|
+
self, kind: str, ids: list[int], *, commit: bool = True
|
|
842
|
+
) -> None:
|
|
843
|
+
"""Remove fts + vec index rows for the given playbook IDs.
|
|
844
|
+
|
|
845
|
+
Args:
|
|
846
|
+
kind: ``"user"`` or ``"agent"``.
|
|
847
|
+
ids: Playbook row IDs to remove from the search indexes.
|
|
848
|
+
commit: When ``True`` (default) commits after the deletes so the
|
|
849
|
+
after-commit callers in ``_playbook.py`` get a clean, durable
|
|
850
|
+
cleanup. Pass ``commit=False`` from inside the retention atomic
|
|
851
|
+
block so the deletes participate in the single block-level commit
|
|
852
|
+
(``_retention_perform_delete``).
|
|
853
|
+
|
|
854
|
+
Note: callers may already hold ``self._lock`` when calling this (the
|
|
855
|
+
``commit=False`` retention/atomic-delete call sites do). The internal
|
|
856
|
+
``with self._lock:`` re-acquire is safe ONLY because ``self._lock`` is a
|
|
857
|
+
reentrant ``threading.RLock``; a non-reentrant lock would deadlock here.
|
|
858
|
+
"""
|
|
822
859
|
if not ids:
|
|
823
860
|
return
|
|
824
|
-
self.
|
|
825
|
-
|
|
826
|
-
self.
|
|
861
|
+
with self._lock:
|
|
862
|
+
self._delete_in_chunks(f"{kind}_playbooks_fts", "rowid", ids)
|
|
863
|
+
if self._has_sqlite_vec:
|
|
864
|
+
self._delete_in_chunks(f"{kind}_playbooks_vec", "rowid", ids)
|
|
865
|
+
if commit:
|
|
866
|
+
self.conn.commit()
|
|
827
867
|
|
|
828
868
|
def _delete_source_windows_for_agent_playbook_ids(
|
|
829
869
|
self, agent_playbook_ids: list[int]
|
|
@@ -947,6 +987,14 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
947
987
|
self.conn.execute("ALTER TABLE interactions ADD COLUMN citations TEXT")
|
|
948
988
|
self.conn.commit()
|
|
949
989
|
|
|
990
|
+
if "image_encoding" not in columns:
|
|
991
|
+
logger.info("Adding image_encoding column to interactions table.")
|
|
992
|
+
with self._lock:
|
|
993
|
+
self.conn.execute(
|
|
994
|
+
"ALTER TABLE interactions ADD COLUMN image_encoding TEXT NOT NULL DEFAULT ''"
|
|
995
|
+
)
|
|
996
|
+
self.conn.commit()
|
|
997
|
+
|
|
950
998
|
def _migrate_feedback_schema(self) -> None:
|
|
951
999
|
"""Drop old-schema feedback/playbook tables so _DDL can recreate them.
|
|
952
1000
|
|
|
@@ -1109,6 +1157,39 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
1109
1157
|
logger.info("Added expanded_terms column to %s", table)
|
|
1110
1158
|
self.conn.commit()
|
|
1111
1159
|
|
|
1160
|
+
def _migrate_tags(self) -> None:
|
|
1161
|
+
"""Add tags column if missing."""
|
|
1162
|
+
for table in ("profiles", "user_playbooks", "agent_playbooks"):
|
|
1163
|
+
cols = {
|
|
1164
|
+
row["name"]
|
|
1165
|
+
for row in self.conn.execute(f"PRAGMA table_info({table})").fetchall()
|
|
1166
|
+
}
|
|
1167
|
+
if "tags" not in cols:
|
|
1168
|
+
self.conn.execute(f"ALTER TABLE {table} ADD COLUMN tags TEXT")
|
|
1169
|
+
logger.info("Added tags column to %s", table)
|
|
1170
|
+
self.conn.commit()
|
|
1171
|
+
|
|
1172
|
+
def _migrate_profile_source_interaction_ids(self) -> None:
|
|
1173
|
+
"""Add profile source interaction ids for provenance on existing DBs."""
|
|
1174
|
+
cols = {
|
|
1175
|
+
row["name"]
|
|
1176
|
+
for row in self.conn.execute("PRAGMA table_info(profiles)").fetchall()
|
|
1177
|
+
}
|
|
1178
|
+
if "source_interaction_ids" not in cols:
|
|
1179
|
+
self.conn.execute(
|
|
1180
|
+
"ALTER TABLE profiles ADD COLUMN source_interaction_ids TEXT"
|
|
1181
|
+
)
|
|
1182
|
+
logger.info("Added source_interaction_ids column to profiles")
|
|
1183
|
+
self.conn.commit()
|
|
1184
|
+
|
|
1185
|
+
def _migrate_interaction_window_indexes(self) -> None:
|
|
1186
|
+
"""Add composite indexes used by sliding-window provenance lookups."""
|
|
1187
|
+
self.conn.execute(
|
|
1188
|
+
"CREATE INDEX IF NOT EXISTS idx_interactions_user_created_at_desc "
|
|
1189
|
+
"ON interactions(user_id, created_at DESC, interaction_id DESC)"
|
|
1190
|
+
)
|
|
1191
|
+
self.conn.commit()
|
|
1192
|
+
|
|
1112
1193
|
def _migrate_agent_runs_schema(self) -> None:
|
|
1113
1194
|
"""Add resumable-agent run columns if missing from existing SQLite DBs."""
|
|
1114
1195
|
cols = {
|
|
@@ -1178,6 +1259,194 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
1178
1259
|
logger.info("Dropped legacy polarity column from user_playbooks")
|
|
1179
1260
|
self.conn.commit()
|
|
1180
1261
|
|
|
1262
|
+
def _migrate_lineage(self) -> None:
|
|
1263
|
+
"""Add merged_into/superseded_by forward-pointer columns if missing.
|
|
1264
|
+
|
|
1265
|
+
Backfill-safe: columns are nullable with no default. INTEGER for playbook
|
|
1266
|
+
tables (int foreign-key pointers), TEXT for profiles (str profile_id pointers).
|
|
1267
|
+
"""
|
|
1268
|
+
int_tables = {"user_playbooks": "INTEGER", "agent_playbooks": "INTEGER"}
|
|
1269
|
+
str_tables = {"profiles": "TEXT"}
|
|
1270
|
+
for table, coltype in {**int_tables, **str_tables}.items():
|
|
1271
|
+
cols = {
|
|
1272
|
+
row["name"]
|
|
1273
|
+
for row in self.conn.execute(f"PRAGMA table_info({table})").fetchall()
|
|
1274
|
+
}
|
|
1275
|
+
for col in ("merged_into", "superseded_by"):
|
|
1276
|
+
if col not in cols:
|
|
1277
|
+
self.conn.execute(
|
|
1278
|
+
f"ALTER TABLE {table} ADD COLUMN {col} {coltype}" # noqa: S608
|
|
1279
|
+
)
|
|
1280
|
+
logger.info("Added %s column to %s", col, table)
|
|
1281
|
+
self.conn.commit()
|
|
1282
|
+
|
|
1283
|
+
def _migrate_retired_at(self) -> None:
|
|
1284
|
+
"""Add nullable ``retired_at INTEGER`` GC column to tombstone-bearing tables.
|
|
1285
|
+
|
|
1286
|
+
Backfill-safe: column is nullable with no default. Existing tombstones
|
|
1287
|
+
will have ``retired_at = NULL`` (conservative — GC T2 uses ``retired_at``
|
|
1288
|
+
as the age signal, so old tombstones without it are simply not yet eligible
|
|
1289
|
+
by the new clock; ops can backfill via T4 if needed).
|
|
1290
|
+
Also creates the covering index for GC queries (idempotent).
|
|
1291
|
+
"""
|
|
1292
|
+
with self._lock:
|
|
1293
|
+
for table in ("profiles", "user_playbooks", "agent_playbooks"):
|
|
1294
|
+
cols = {
|
|
1295
|
+
row["name"]
|
|
1296
|
+
for row in self.conn.execute(
|
|
1297
|
+
f"PRAGMA table_info({table})" # noqa: S608
|
|
1298
|
+
).fetchall()
|
|
1299
|
+
}
|
|
1300
|
+
if "retired_at" not in cols:
|
|
1301
|
+
self.conn.execute(
|
|
1302
|
+
f"ALTER TABLE {table} ADD COLUMN retired_at INTEGER" # noqa: S608
|
|
1303
|
+
)
|
|
1304
|
+
logger.info("Added retired_at column to %s", table)
|
|
1305
|
+
self.conn.execute(
|
|
1306
|
+
f"CREATE INDEX IF NOT EXISTS idx_{table}_retired_at " # noqa: S608
|
|
1307
|
+
f"ON {table}(status, retired_at)"
|
|
1308
|
+
)
|
|
1309
|
+
self.conn.commit()
|
|
1310
|
+
|
|
1311
|
+
def _migrate_lineage_event_table(self) -> None:
|
|
1312
|
+
"""Create the lineage_event table + index for existing databases (idempotent)."""
|
|
1313
|
+
with self._lock:
|
|
1314
|
+
self.conn.executescript("""
|
|
1315
|
+
CREATE TABLE IF NOT EXISTS lineage_event (
|
|
1316
|
+
event_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1317
|
+
org_id TEXT NOT NULL,
|
|
1318
|
+
entity_type TEXT NOT NULL,
|
|
1319
|
+
entity_id TEXT NOT NULL,
|
|
1320
|
+
op TEXT NOT NULL,
|
|
1321
|
+
prov_relation TEXT NOT NULL DEFAULT '',
|
|
1322
|
+
source_ids TEXT NOT NULL DEFAULT '[]',
|
|
1323
|
+
actor TEXT NOT NULL DEFAULT '',
|
|
1324
|
+
request_id TEXT NOT NULL DEFAULT '',
|
|
1325
|
+
reason TEXT NOT NULL DEFAULT '',
|
|
1326
|
+
created_at INTEGER NOT NULL,
|
|
1327
|
+
UNIQUE (org_id, entity_type, entity_id, op, request_id)
|
|
1328
|
+
);
|
|
1329
|
+
CREATE INDEX IF NOT EXISTS idx_lineage_entity
|
|
1330
|
+
ON lineage_event (entity_type, entity_id);
|
|
1331
|
+
""")
|
|
1332
|
+
existing_cols = {
|
|
1333
|
+
row["name"]
|
|
1334
|
+
for row in self.conn.execute(
|
|
1335
|
+
"PRAGMA table_info(lineage_event)"
|
|
1336
|
+
).fetchall()
|
|
1337
|
+
}
|
|
1338
|
+
for col in ("from_status", "to_status", "status_namespace"):
|
|
1339
|
+
if col not in existing_cols:
|
|
1340
|
+
self.conn.execute(
|
|
1341
|
+
f"ALTER TABLE lineage_event ADD COLUMN {col} TEXT" # noqa: S608
|
|
1342
|
+
)
|
|
1343
|
+
logger.info("Added %s column to lineage_event", col)
|
|
1344
|
+
self.conn.commit()
|
|
1345
|
+
|
|
1346
|
+
def _migrate_playbook_optimization_candidate_metadata(self) -> None:
|
|
1347
|
+
"""Add metadata_json to legacy optimizer candidate tables when missing."""
|
|
1348
|
+
cols = {
|
|
1349
|
+
row["name"]
|
|
1350
|
+
for row in self.conn.execute(
|
|
1351
|
+
"PRAGMA table_info(playbook_optimization_candidates)"
|
|
1352
|
+
).fetchall()
|
|
1353
|
+
}
|
|
1354
|
+
if not cols:
|
|
1355
|
+
return
|
|
1356
|
+
if "metadata_json" not in cols:
|
|
1357
|
+
self.conn.execute(
|
|
1358
|
+
"ALTER TABLE playbook_optimization_candidates "
|
|
1359
|
+
"ADD COLUMN metadata_json TEXT NOT NULL DEFAULT '{}'"
|
|
1360
|
+
)
|
|
1361
|
+
logger.info(
|
|
1362
|
+
"Added metadata_json column to playbook_optimization_candidates"
|
|
1363
|
+
)
|
|
1364
|
+
self.conn.commit()
|
|
1365
|
+
|
|
1366
|
+
def _migrate_retire_profile_change_logs(self) -> None:
|
|
1367
|
+
"""Retire the frozen ``profile_change_logs`` table via a reversible RENAME.
|
|
1368
|
+
|
|
1369
|
+
Lineage B3 Task 8: the legacy change log is fully de-referenced (no
|
|
1370
|
+
readers, writers, or GDPR delete callers remain) and the view is served
|
|
1371
|
+
from reconstruction. We rename the table out of the way now and DROP it
|
|
1372
|
+
in a later migration after the recovery window — keeping the data
|
|
1373
|
+
recoverable in the interim.
|
|
1374
|
+
|
|
1375
|
+
Idempotent: SQLite has no ``RENAME ... IF EXISTS``, so we guard on the
|
|
1376
|
+
source table's presence and no-op once it has been renamed. The
|
|
1377
|
+
``CREATE TABLE`` for ``profile_change_logs`` was deleted from ``_DDL`` so
|
|
1378
|
+
``executescript(_DDL)`` does not recreate an empty table after the rename.
|
|
1379
|
+
"""
|
|
1380
|
+
with self._lock:
|
|
1381
|
+
row = self.conn.execute(
|
|
1382
|
+
"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?",
|
|
1383
|
+
("profile_change_logs",),
|
|
1384
|
+
).fetchone()
|
|
1385
|
+
if row is None:
|
|
1386
|
+
return
|
|
1387
|
+
target_row = self.conn.execute(
|
|
1388
|
+
"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?",
|
|
1389
|
+
("profile_change_logs_retired_20260623",),
|
|
1390
|
+
).fetchone()
|
|
1391
|
+
if target_row is not None:
|
|
1392
|
+
logger.info(
|
|
1393
|
+
"Retired target profile_change_logs_retired_20260623 already exists;"
|
|
1394
|
+
" skipping rename (idempotent no-op)."
|
|
1395
|
+
)
|
|
1396
|
+
return
|
|
1397
|
+
self.conn.execute(
|
|
1398
|
+
"ALTER TABLE profile_change_logs "
|
|
1399
|
+
"RENAME TO profile_change_logs_retired_20260623"
|
|
1400
|
+
)
|
|
1401
|
+
logger.info(
|
|
1402
|
+
"Renamed profile_change_logs to "
|
|
1403
|
+
"profile_change_logs_retired_20260623 (B3 Task 8 retirement)"
|
|
1404
|
+
)
|
|
1405
|
+
self.conn.commit()
|
|
1406
|
+
|
|
1407
|
+
def _migrate_retire_playbook_aggregation_change_logs(self) -> None:
|
|
1408
|
+
"""Retire the frozen ``playbook_aggregation_change_logs`` table via a reversible RENAME.
|
|
1409
|
+
|
|
1410
|
+
Lineage Track B Task 4: the legacy change log is fully de-referenced (no
|
|
1411
|
+
readers, writers, or GDPR delete callers remain) and the view is served
|
|
1412
|
+
from reconstruction. We rename the table out of the way now and DROP it
|
|
1413
|
+
in a later migration after the recovery window — keeping the data
|
|
1414
|
+
recoverable in the interim.
|
|
1415
|
+
|
|
1416
|
+
Idempotent: SQLite has no ``RENAME ... IF EXISTS``, so we guard on the
|
|
1417
|
+
source table's presence and no-op once it has been renamed. The
|
|
1418
|
+
``CREATE TABLE`` for ``playbook_aggregation_change_logs`` was deleted from
|
|
1419
|
+
``_DDL`` so ``executescript(_DDL)`` does not recreate an empty table after
|
|
1420
|
+
the rename.
|
|
1421
|
+
"""
|
|
1422
|
+
with self._lock:
|
|
1423
|
+
row = self.conn.execute(
|
|
1424
|
+
"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?",
|
|
1425
|
+
("playbook_aggregation_change_logs",),
|
|
1426
|
+
).fetchone()
|
|
1427
|
+
if row is None:
|
|
1428
|
+
return
|
|
1429
|
+
target_row = self.conn.execute(
|
|
1430
|
+
"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?",
|
|
1431
|
+
("playbook_aggregation_change_logs_retired_20260624",),
|
|
1432
|
+
).fetchone()
|
|
1433
|
+
if target_row is not None:
|
|
1434
|
+
logger.info(
|
|
1435
|
+
"Retired target playbook_aggregation_change_logs_retired_20260624"
|
|
1436
|
+
" already exists; skipping rename (idempotent no-op)."
|
|
1437
|
+
)
|
|
1438
|
+
return
|
|
1439
|
+
self.conn.execute(
|
|
1440
|
+
"ALTER TABLE playbook_aggregation_change_logs "
|
|
1441
|
+
"RENAME TO playbook_aggregation_change_logs_retired_20260624"
|
|
1442
|
+
)
|
|
1443
|
+
logger.info(
|
|
1444
|
+
"Renamed playbook_aggregation_change_logs to "
|
|
1445
|
+
"playbook_aggregation_change_logs_retired_20260624 "
|
|
1446
|
+
"(Track B Task 4 retirement)"
|
|
1447
|
+
)
|
|
1448
|
+
self.conn.commit()
|
|
1449
|
+
|
|
1181
1450
|
def _migrate_agent_playbook_source_windows(self) -> None:
|
|
1182
1451
|
"""Add source window snapshots to existing agent source mappings."""
|
|
1183
1452
|
cols = {
|
|
@@ -1203,24 +1472,122 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
1203
1472
|
)
|
|
1204
1473
|
self.conn.commit()
|
|
1205
1474
|
|
|
1206
|
-
def
|
|
1207
|
-
"""Add
|
|
1475
|
+
def _migrate_request_evaluation_only(self) -> None:
|
|
1476
|
+
"""Add evaluation_only column to requests for learning exclusion."""
|
|
1477
|
+
cols = {
|
|
1478
|
+
row["name"]
|
|
1479
|
+
for row in self.conn.execute("PRAGMA table_info(requests)").fetchall()
|
|
1480
|
+
}
|
|
1481
|
+
if not cols:
|
|
1482
|
+
return
|
|
1483
|
+
if "evaluation_only" not in cols:
|
|
1484
|
+
self.conn.execute(
|
|
1485
|
+
"ALTER TABLE requests ADD COLUMN evaluation_only INTEGER NOT NULL DEFAULT 0"
|
|
1486
|
+
)
|
|
1487
|
+
logger.info("Added evaluation_only column to requests")
|
|
1488
|
+
self.conn.commit()
|
|
1208
1489
|
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1490
|
+
def _migrate_request_session_id_required(self) -> None:
|
|
1491
|
+
"""Require non-empty session ids on ``requests``.
|
|
1492
|
+
|
|
1493
|
+
SQLite cannot add a ``NOT NULL`` or ``CHECK`` constraint to an
|
|
1494
|
+
existing column, so existing databases are rebuilt in place. Historical
|
|
1495
|
+
null/blank sessions are intentionally backfilled per request to avoid
|
|
1496
|
+
inventing conversation groupings that were never recorded.
|
|
1212
1497
|
"""
|
|
1213
1498
|
cols = {
|
|
1214
|
-
row["name"]
|
|
1499
|
+
row["name"]: row
|
|
1215
1500
|
for row in self.conn.execute("PRAGMA table_info(requests)").fetchall()
|
|
1216
1501
|
}
|
|
1502
|
+
if not cols or "session_id" not in cols:
|
|
1503
|
+
return
|
|
1504
|
+
|
|
1505
|
+
table_sql_row = self.conn.execute(
|
|
1506
|
+
"SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'requests'"
|
|
1507
|
+
).fetchone()
|
|
1508
|
+
table_sql = (table_sql_row["sql"] if table_sql_row else "") or ""
|
|
1509
|
+
blank_count = self.conn.execute(
|
|
1510
|
+
"SELECT COUNT(*) FROM requests WHERE session_id IS NULL OR trim(session_id) = ''"
|
|
1511
|
+
).fetchone()[0]
|
|
1512
|
+
has_required_schema = (
|
|
1513
|
+
bool(cols["session_id"]["notnull"])
|
|
1514
|
+
and "CHECK (trim(session_id) != '')" in table_sql
|
|
1515
|
+
)
|
|
1516
|
+
if has_required_schema and blank_count == 0:
|
|
1517
|
+
return
|
|
1518
|
+
|
|
1519
|
+
evaluation_only_expr = (
|
|
1520
|
+
"COALESCE(evaluation_only, 0)" if "evaluation_only" in cols else "0"
|
|
1521
|
+
)
|
|
1522
|
+
# NOTE: this rebuild hardcodes the full `requests` column set. If a
|
|
1523
|
+
# future migration adds a column to `requests`, it MUST be added here
|
|
1524
|
+
# too (and to the SELECT below) or the rebuild will silently drop it.
|
|
1525
|
+
self.conn.executescript(
|
|
1526
|
+
f"""
|
|
1527
|
+
CREATE TABLE requests_new (
|
|
1528
|
+
request_id TEXT PRIMARY KEY,
|
|
1529
|
+
user_id TEXT NOT NULL,
|
|
1530
|
+
created_at TEXT NOT NULL,
|
|
1531
|
+
source TEXT NOT NULL DEFAULT '',
|
|
1532
|
+
agent_version TEXT NOT NULL DEFAULT '',
|
|
1533
|
+
session_id TEXT NOT NULL CHECK (trim(session_id) != ''),
|
|
1534
|
+
evaluation_only INTEGER NOT NULL DEFAULT 0
|
|
1535
|
+
);
|
|
1536
|
+
INSERT INTO requests_new
|
|
1537
|
+
(
|
|
1538
|
+
request_id,
|
|
1539
|
+
user_id,
|
|
1540
|
+
created_at,
|
|
1541
|
+
source,
|
|
1542
|
+
agent_version,
|
|
1543
|
+
session_id,
|
|
1544
|
+
evaluation_only
|
|
1545
|
+
)
|
|
1546
|
+
SELECT
|
|
1547
|
+
request_id,
|
|
1548
|
+
user_id,
|
|
1549
|
+
created_at,
|
|
1550
|
+
COALESCE(source, ''),
|
|
1551
|
+
COALESCE(agent_version, ''),
|
|
1552
|
+
CASE
|
|
1553
|
+
WHEN session_id IS NULL OR trim(session_id) = ''
|
|
1554
|
+
THEN 'legacy-' || lower(hex(randomblob(16)))
|
|
1555
|
+
ELSE trim(session_id)
|
|
1556
|
+
END,
|
|
1557
|
+
{evaluation_only_expr}
|
|
1558
|
+
FROM requests;
|
|
1559
|
+
DROP TABLE requests;
|
|
1560
|
+
ALTER TABLE requests_new RENAME TO requests;
|
|
1561
|
+
CREATE INDEX IF NOT EXISTS idx_requests_user_id ON requests(user_id);
|
|
1562
|
+
CREATE INDEX IF NOT EXISTS idx_requests_session_id ON requests(session_id);
|
|
1563
|
+
CREATE INDEX IF NOT EXISTS idx_requests_created_at ON requests(created_at);
|
|
1564
|
+
"""
|
|
1565
|
+
)
|
|
1566
|
+
self.conn.commit()
|
|
1567
|
+
logger.info("Migrated requests.session_id to required non-empty values")
|
|
1568
|
+
|
|
1569
|
+
def _migrate_eval_result_user_id(self) -> None:
|
|
1570
|
+
"""Add user_id to session evaluation results for per-user identity."""
|
|
1571
|
+
cols = {
|
|
1572
|
+
row["name"]
|
|
1573
|
+
for row in self.conn.execute(
|
|
1574
|
+
"PRAGMA table_info(agent_success_evaluation_result)"
|
|
1575
|
+
).fetchall()
|
|
1576
|
+
}
|
|
1217
1577
|
if not cols:
|
|
1218
1578
|
return
|
|
1219
|
-
if "
|
|
1579
|
+
if "user_id" not in cols:
|
|
1220
1580
|
self.conn.execute(
|
|
1221
|
-
"ALTER TABLE
|
|
1581
|
+
"ALTER TABLE agent_success_evaluation_result "
|
|
1582
|
+
"ADD COLUMN user_id TEXT NOT NULL DEFAULT ''"
|
|
1222
1583
|
)
|
|
1223
|
-
logger.info("Added
|
|
1584
|
+
logger.info("Added user_id column to agent_success_evaluation_result")
|
|
1585
|
+
self.conn.execute("DROP INDEX IF EXISTS idx_eval_identity_created_at_desc")
|
|
1586
|
+
self.conn.execute(
|
|
1587
|
+
"CREATE INDEX IF NOT EXISTS idx_eval_identity_created_at_desc "
|
|
1588
|
+
"ON agent_success_evaluation_result"
|
|
1589
|
+
"(user_id, session_id, evaluation_name, agent_version, created_at DESC)"
|
|
1590
|
+
)
|
|
1224
1591
|
self.conn.commit()
|
|
1225
1592
|
|
|
1226
1593
|
def _migrate_shadow_comparison_verdicts(self) -> None:
|
|
@@ -1228,8 +1595,7 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
1228
1595
|
|
|
1229
1596
|
Idempotent; safe to run on every startup. The PRAGMA-LBYL guard
|
|
1230
1597
|
avoids running the CREATE statements on every boot for
|
|
1231
|
-
already-migrated DBs
|
|
1232
|
-
pattern. The ``CREATE TABLE IF NOT EXISTS`` in :data:`_DDL` will
|
|
1598
|
+
already-migrated DBs. The ``CREATE TABLE IF NOT EXISTS`` in :data:`_DDL` will
|
|
1233
1599
|
also create this table on a fresh database, so this helper is a
|
|
1234
1600
|
no-op there; its purpose is explicit symmetry with the per-feature
|
|
1235
1601
|
migration convention and a single named hook the disk/supabase
|
|
@@ -1461,28 +1827,51 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
1461
1827
|
# ------------------------------------------------------------------
|
|
1462
1828
|
|
|
1463
1829
|
def clear_user_data(self, user_id: str) -> dict[str, int]:
|
|
1464
|
-
"""
|
|
1830
|
+
"""Per-``user_id`` row deletion across all user-scoped tables.
|
|
1465
1831
|
|
|
1466
|
-
Overrides the BaseStorage default with
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
skills and have no ``user_id`` column.
|
|
1832
|
+
Overrides the BaseStorage default with an optimized SQL implementation.
|
|
1833
|
+
Removes interactions, user playbooks, profiles, and requests scoped to
|
|
1834
|
+
the user. Intentionally does NOT touch ``agent_playbooks`` — they are
|
|
1835
|
+
the cross-project rollup of skills and have no ``user_id`` column.
|
|
1471
1836
|
|
|
1472
1837
|
Also cleans up FTS and vector sidecars for the user's rows so
|
|
1473
1838
|
subsequent searches don't surface deleted data.
|
|
1474
1839
|
|
|
1840
|
+
**Lineage-aware erasure for profiles and user_playbooks:**
|
|
1841
|
+
Rows that are tombstones (``merged_into`` or ``superseded_by`` is set)
|
|
1842
|
+
*or* are pointed-to by another row (``has_inbound_lineage_refs`` returns
|
|
1843
|
+
True) are **content-purged** (skeleton kept, body blanked) rather than
|
|
1844
|
+
hard-deleted. This preserves chain resolution across user erasures.
|
|
1845
|
+
Standalone rows with no lineage involvement are hard-deleted as before.
|
|
1846
|
+
|
|
1847
|
+
The purge/delete decision delegates to
|
|
1848
|
+
``BaseStorage._partition_purge_vs_delete`` so the logic is defined once
|
|
1849
|
+
and shared with the default ``clear_user_data`` implementation used by
|
|
1850
|
+
Supabase/Postgres backends.
|
|
1851
|
+
|
|
1852
|
+
**Commit ordering (atomicity invariant):**
|
|
1853
|
+
The hard-deletes for interactions, requests, and the delete-sets of
|
|
1854
|
+
profiles/user_playbooks are committed in one transaction first. Then
|
|
1855
|
+
``purge_content`` is called for each purge-eligible row — each call
|
|
1856
|
+
commits atomically on its own. This two-phase approach is required
|
|
1857
|
+
because ``purge_content`` issues its own ``conn.commit()``, and nesting
|
|
1858
|
+
it inside the outer transaction would prematurely flush the still-pending
|
|
1859
|
+
hard-DELETEs.
|
|
1860
|
+
|
|
1475
1861
|
Args:
|
|
1476
1862
|
user_id (str): The user id whose rows should be deleted.
|
|
1477
1863
|
|
|
1478
1864
|
Returns:
|
|
1479
|
-
dict[str, int]: Per-entity
|
|
1480
|
-
``
|
|
1481
|
-
``
|
|
1865
|
+
dict[str, int]: Per-entity counts with keys ``interactions``,
|
|
1866
|
+
``user_playbooks``, ``profiles``, ``requests``,
|
|
1867
|
+
``purged_profiles``, and ``purged_user_playbooks``.
|
|
1868
|
+
``profiles`` and ``user_playbooks`` reflect hard-deleted counts;
|
|
1869
|
+
purged rows are counted separately.
|
|
1482
1870
|
"""
|
|
1483
1871
|
with self._lock:
|
|
1484
|
-
#
|
|
1485
|
-
#
|
|
1872
|
+
# ------------------------------------------------------------------
|
|
1873
|
+
# Phase 1: snapshot all user-scoped ids before any mutations.
|
|
1874
|
+
# ------------------------------------------------------------------
|
|
1486
1875
|
interaction_ids = [
|
|
1487
1876
|
r["interaction_id"]
|
|
1488
1877
|
for r in self.conn.execute(
|
|
@@ -1490,7 +1879,7 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
1490
1879
|
(user_id,),
|
|
1491
1880
|
).fetchall()
|
|
1492
1881
|
]
|
|
1493
|
-
|
|
1882
|
+
raw_upb_ids = [
|
|
1494
1883
|
r["user_playbook_id"]
|
|
1495
1884
|
for r in self.conn.execute(
|
|
1496
1885
|
"SELECT user_playbook_id FROM user_playbooks WHERE user_id = ?",
|
|
@@ -1501,67 +1890,97 @@ class SQLiteStorageBase(RetentionMixin, BaseStorage):
|
|
|
1501
1890
|
"SELECT rowid, profile_id FROM profiles WHERE user_id = ?",
|
|
1502
1891
|
(user_id,),
|
|
1503
1892
|
).fetchall()
|
|
1504
|
-
profile_rowids = [r["rowid"] for r in profile_rows]
|
|
1505
|
-
profile_ids = [r["profile_id"] for r in profile_rows]
|
|
1506
1893
|
|
|
1507
|
-
# FTS cleanup
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1894
|
+
# Build a rowid lookup for FTS/vec cleanup (SQLite-specific need).
|
|
1895
|
+
profile_rowid_by_id: dict[str, int] = {
|
|
1896
|
+
r["profile_id"]: r["rowid"] for r in profile_rows
|
|
1897
|
+
}
|
|
1898
|
+
all_profile_ids = list(profile_rowid_by_id.keys())
|
|
1899
|
+
|
|
1900
|
+
# ------------------------------------------------------------------
|
|
1901
|
+
# Phase 2: partition profiles and user_playbooks into purge vs delete.
|
|
1902
|
+
# Delegates to the shared BaseStorage helper so the decision logic
|
|
1903
|
+
# is defined once and reused by all backends.
|
|
1904
|
+
# ------------------------------------------------------------------
|
|
1905
|
+
purge_profile_ids, delete_profile_ids = self._partition_purge_vs_delete(
|
|
1906
|
+
"profile", all_profile_ids
|
|
1907
|
+
)
|
|
1908
|
+
purge_upb_str_ids, delete_upb_str_ids = self._partition_purge_vs_delete(
|
|
1909
|
+
"user_playbook", [str(uid) for uid in raw_upb_ids]
|
|
1910
|
+
)
|
|
1911
|
+
purge_upb_ids = [int(s) for s in purge_upb_str_ids]
|
|
1912
|
+
delete_upb_ids = [int(s) for s in delete_upb_str_ids]
|
|
1913
|
+
|
|
1914
|
+
# Rowids for the delete-set only (purge_content handles its own cleanup).
|
|
1915
|
+
delete_profile_rowids = [
|
|
1916
|
+
profile_rowid_by_id[pid]
|
|
1917
|
+
for pid in delete_profile_ids
|
|
1918
|
+
if pid in profile_rowid_by_id
|
|
1919
|
+
]
|
|
1920
|
+
|
|
1921
|
+
# ------------------------------------------------------------------
|
|
1922
|
+
# Phase 3: FTS and vector cleanup — only for the delete-sets
|
|
1923
|
+
# (purge_content handles its own index cleanup for purged rows).
|
|
1924
|
+
# Use _delete_in_chunks to stay under SQLite's SQLITE_MAX_VARIABLE_NUMBER
|
|
1925
|
+
# limit for large user datasets.
|
|
1926
|
+
# ------------------------------------------------------------------
|
|
1927
|
+
self._delete_in_chunks("interactions_fts", "rowid", interaction_ids)
|
|
1928
|
+
self._delete_in_chunks("user_playbooks_fts", "rowid", delete_upb_ids)
|
|
1929
|
+
self._delete_in_chunks("profiles_fts", "profile_id", delete_profile_ids)
|
|
1526
1930
|
|
|
1527
|
-
# Vector index cleanup (best-effort: only if sqlite-vec loaded)
|
|
1528
1931
|
if self._has_sqlite_vec:
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
("profiles_vec", profile_rowids),
|
|
1533
|
-
)
|
|
1534
|
-
for vec_table, rowids in vec_targets:
|
|
1535
|
-
if rowids:
|
|
1536
|
-
ph = ",".join("?" for _ in rowids)
|
|
1537
|
-
self.conn.execute(
|
|
1538
|
-
f"DELETE FROM {vec_table} WHERE rowid IN ({ph})",
|
|
1539
|
-
rowids,
|
|
1540
|
-
)
|
|
1932
|
+
self._delete_in_chunks("interactions_vec", "rowid", interaction_ids)
|
|
1933
|
+
self._delete_in_chunks("user_playbooks_vec", "rowid", delete_upb_ids)
|
|
1934
|
+
self._delete_in_chunks("profiles_vec", "rowid", delete_profile_rowids)
|
|
1541
1935
|
|
|
1542
|
-
#
|
|
1543
|
-
#
|
|
1544
|
-
#
|
|
1936
|
+
# ------------------------------------------------------------------
|
|
1937
|
+
# Phase 4: hard-delete the delete-sets and all interactions/requests.
|
|
1938
|
+
# ------------------------------------------------------------------
|
|
1545
1939
|
interactions_cur = self.conn.execute(
|
|
1546
1940
|
"DELETE FROM interactions WHERE user_id = ?", (user_id,)
|
|
1547
1941
|
)
|
|
1548
|
-
user_playbooks_cur = self.conn.execute(
|
|
1549
|
-
"DELETE FROM user_playbooks WHERE user_id = ?", (user_id,)
|
|
1550
|
-
)
|
|
1551
|
-
profiles_cur = self.conn.execute(
|
|
1552
|
-
"DELETE FROM profiles WHERE user_id = ?", (user_id,)
|
|
1553
|
-
)
|
|
1554
1942
|
requests_cur = self.conn.execute(
|
|
1555
1943
|
"DELETE FROM requests WHERE user_id = ?", (user_id,)
|
|
1556
1944
|
)
|
|
1945
|
+
upb_deleted_count = 0
|
|
1946
|
+
if delete_upb_ids:
|
|
1947
|
+
# Clean up source-window join rows before deleting the parent rows.
|
|
1948
|
+
self._delete_source_windows_for_user_playbook_ids(delete_upb_ids)
|
|
1949
|
+
self._delete_in_chunks(
|
|
1950
|
+
"user_playbooks", "user_playbook_id", delete_upb_ids
|
|
1951
|
+
)
|
|
1952
|
+
# rowcount not available from _delete_in_chunks; derive from list length
|
|
1953
|
+
# (all ids came from a pre-snapshot so they exist at delete time).
|
|
1954
|
+
upb_deleted_count = len(delete_upb_ids)
|
|
1955
|
+
profile_deleted_count = 0
|
|
1956
|
+
if delete_profile_ids:
|
|
1957
|
+
self._delete_in_chunks("profiles", "profile_id", delete_profile_ids)
|
|
1958
|
+
profile_deleted_count = len(delete_profile_ids)
|
|
1959
|
+
|
|
1960
|
+
# Commit the hard-deletes before calling purge_content, because
|
|
1961
|
+
# purge_content issues its own conn.commit() and nesting it here
|
|
1962
|
+
# would prematurely flush the still-pending deletes.
|
|
1557
1963
|
self.conn.commit()
|
|
1558
1964
|
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1965
|
+
# Phase 5: content-purge the purge-sets WITHOUT releasing the lock,
|
|
1966
|
+
# so erase-eligible rows are never observable by another thread with
|
|
1967
|
+
# PII still intact between the hard-delete commit and the purge. Each
|
|
1968
|
+
# purge_content call self-commits; self._lock is an RLock so its
|
|
1969
|
+
# internal ``with self._lock`` re-acquires cleanly, and the commit
|
|
1970
|
+
# above already closed the outer transaction (no flush hazard).
|
|
1971
|
+
for pid in purge_profile_ids:
|
|
1972
|
+
self.purge_content(entity_type="profile", entity_id=str(pid))
|
|
1973
|
+
for upid in purge_upb_ids:
|
|
1974
|
+
self.purge_content(entity_type="user_playbook", entity_id=str(upid))
|
|
1975
|
+
|
|
1976
|
+
return {
|
|
1977
|
+
"interactions": interactions_cur.rowcount,
|
|
1978
|
+
"user_playbooks": upb_deleted_count,
|
|
1979
|
+
"profiles": profile_deleted_count,
|
|
1980
|
+
"requests": requests_cur.rowcount,
|
|
1981
|
+
"purged_profiles": len(purge_profile_ids),
|
|
1982
|
+
"purged_user_playbooks": len(purge_upb_ids),
|
|
1983
|
+
}
|
|
1565
1984
|
|
|
1566
1985
|
|
|
1567
1986
|
# ---------------------------------------------------------------------------
|
|
@@ -1583,10 +2002,15 @@ CREATE TABLE IF NOT EXISTS profiles (
|
|
|
1583
2002
|
status TEXT,
|
|
1584
2003
|
extractor_names TEXT,
|
|
1585
2004
|
expanded_terms TEXT,
|
|
2005
|
+
tags TEXT,
|
|
2006
|
+
source_interaction_ids TEXT,
|
|
1586
2007
|
source_span TEXT,
|
|
1587
2008
|
notes TEXT,
|
|
1588
2009
|
reader_angle TEXT,
|
|
1589
|
-
|
|
2010
|
+
merged_into TEXT,
|
|
2011
|
+
superseded_by TEXT,
|
|
2012
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
2013
|
+
retired_at INTEGER
|
|
1590
2014
|
);
|
|
1591
2015
|
CREATE INDEX IF NOT EXISTS idx_profiles_user_id ON profiles(user_id);
|
|
1592
2016
|
CREATE INDEX IF NOT EXISTS idx_profiles_status ON profiles(status);
|
|
@@ -1601,6 +2025,7 @@ CREATE TABLE IF NOT EXISTS interactions (
|
|
|
1601
2025
|
user_action TEXT NOT NULL DEFAULT 'none',
|
|
1602
2026
|
user_action_description TEXT NOT NULL DEFAULT '',
|
|
1603
2027
|
interacted_image_url TEXT NOT NULL DEFAULT '',
|
|
2028
|
+
image_encoding TEXT NOT NULL DEFAULT '',
|
|
1604
2029
|
shadow_content TEXT NOT NULL DEFAULT '',
|
|
1605
2030
|
expert_content TEXT NOT NULL DEFAULT '',
|
|
1606
2031
|
tools_used TEXT,
|
|
@@ -1610,6 +2035,8 @@ CREATE TABLE IF NOT EXISTS interactions (
|
|
|
1610
2035
|
CREATE INDEX IF NOT EXISTS idx_interactions_user_id ON interactions(user_id);
|
|
1611
2036
|
CREATE INDEX IF NOT EXISTS idx_interactions_request_id ON interactions(request_id);
|
|
1612
2037
|
CREATE INDEX IF NOT EXISTS idx_interactions_created_at ON interactions(created_at);
|
|
2038
|
+
CREATE INDEX IF NOT EXISTS idx_interactions_user_created_at_desc
|
|
2039
|
+
ON interactions(user_id, created_at DESC, interaction_id DESC);
|
|
1613
2040
|
|
|
1614
2041
|
CREATE TABLE IF NOT EXISTS requests (
|
|
1615
2042
|
request_id TEXT PRIMARY KEY,
|
|
@@ -1617,12 +2044,14 @@ CREATE TABLE IF NOT EXISTS requests (
|
|
|
1617
2044
|
created_at TEXT NOT NULL,
|
|
1618
2045
|
source TEXT NOT NULL DEFAULT '',
|
|
1619
2046
|
agent_version TEXT NOT NULL DEFAULT '',
|
|
1620
|
-
session_id TEXT,
|
|
1621
|
-
|
|
2047
|
+
session_id TEXT NOT NULL CHECK (trim(session_id) != ''),
|
|
2048
|
+
evaluation_only INTEGER NOT NULL DEFAULT 0
|
|
1622
2049
|
);
|
|
1623
2050
|
CREATE INDEX IF NOT EXISTS idx_requests_user_id ON requests(user_id);
|
|
1624
2051
|
CREATE INDEX IF NOT EXISTS idx_requests_session_id ON requests(session_id);
|
|
1625
2052
|
CREATE INDEX IF NOT EXISTS idx_requests_created_at ON requests(created_at);
|
|
2053
|
+
CREATE INDEX IF NOT EXISTS idx_requests_session_created_at_asc
|
|
2054
|
+
ON requests(session_id, created_at ASC, request_id ASC);
|
|
1626
2055
|
|
|
1627
2056
|
CREATE TABLE IF NOT EXISTS user_playbooks (
|
|
1628
2057
|
user_playbook_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -1640,9 +2069,13 @@ CREATE TABLE IF NOT EXISTS user_playbooks (
|
|
|
1640
2069
|
source TEXT,
|
|
1641
2070
|
embedding TEXT,
|
|
1642
2071
|
expanded_terms TEXT,
|
|
2072
|
+
tags TEXT,
|
|
1643
2073
|
source_span TEXT,
|
|
1644
2074
|
notes TEXT,
|
|
1645
|
-
reader_angle TEXT
|
|
2075
|
+
reader_angle TEXT,
|
|
2076
|
+
merged_into INTEGER,
|
|
2077
|
+
superseded_by INTEGER,
|
|
2078
|
+
retired_at INTEGER
|
|
1646
2079
|
);
|
|
1647
2080
|
CREATE INDEX IF NOT EXISTS idx_user_playbooks_playbook_name ON user_playbooks(playbook_name);
|
|
1648
2081
|
CREATE INDEX IF NOT EXISTS idx_user_playbooks_agent_version ON user_playbooks(agent_version);
|
|
@@ -1662,7 +2095,11 @@ CREATE TABLE IF NOT EXISTS agent_playbooks (
|
|
|
1662
2095
|
playbook_metadata TEXT NOT NULL DEFAULT '',
|
|
1663
2096
|
embedding TEXT,
|
|
1664
2097
|
expanded_terms TEXT,
|
|
1665
|
-
|
|
2098
|
+
tags TEXT,
|
|
2099
|
+
status TEXT,
|
|
2100
|
+
merged_into INTEGER,
|
|
2101
|
+
superseded_by INTEGER,
|
|
2102
|
+
retired_at INTEGER
|
|
1666
2103
|
);
|
|
1667
2104
|
CREATE INDEX IF NOT EXISTS idx_agent_playbooks_playbook_name ON agent_playbooks(playbook_name);
|
|
1668
2105
|
CREATE INDEX IF NOT EXISTS idx_agent_playbooks_agent_version ON agent_playbooks(agent_version);
|
|
@@ -1671,6 +2108,7 @@ CREATE INDEX IF NOT EXISTS idx_agent_playbooks_created_at ON agent_playbooks(cre
|
|
|
1671
2108
|
|
|
1672
2109
|
CREATE TABLE IF NOT EXISTS agent_success_evaluation_result (
|
|
1673
2110
|
result_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2111
|
+
user_id TEXT NOT NULL DEFAULT '',
|
|
1674
2112
|
session_id TEXT NOT NULL,
|
|
1675
2113
|
agent_version TEXT NOT NULL DEFAULT '',
|
|
1676
2114
|
evaluation_name TEXT,
|
|
@@ -1686,31 +2124,12 @@ CREATE TABLE IF NOT EXISTS agent_success_evaluation_result (
|
|
|
1686
2124
|
);
|
|
1687
2125
|
CREATE INDEX IF NOT EXISTS idx_eval_agent_version ON agent_success_evaluation_result(agent_version);
|
|
1688
2126
|
CREATE INDEX IF NOT EXISTS idx_eval_created_at ON agent_success_evaluation_result(created_at);
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
added_profiles TEXT NOT NULL DEFAULT '[]',
|
|
1696
|
-
removed_profiles TEXT NOT NULL DEFAULT '[]',
|
|
1697
|
-
mentioned_profiles TEXT NOT NULL DEFAULT '[]'
|
|
1698
|
-
);
|
|
1699
|
-
CREATE INDEX IF NOT EXISTS idx_pcl_user_id ON profile_change_logs(user_id);
|
|
1700
|
-
CREATE INDEX IF NOT EXISTS idx_pcl_created_at ON profile_change_logs(created_at);
|
|
1701
|
-
|
|
1702
|
-
CREATE TABLE IF NOT EXISTS playbook_aggregation_change_logs (
|
|
1703
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1704
|
-
created_at INTEGER NOT NULL,
|
|
1705
|
-
playbook_name TEXT NOT NULL,
|
|
1706
|
-
agent_version TEXT NOT NULL,
|
|
1707
|
-
run_mode TEXT NOT NULL,
|
|
1708
|
-
added_playbooks TEXT NOT NULL DEFAULT '[]',
|
|
1709
|
-
removed_playbooks TEXT NOT NULL DEFAULT '[]',
|
|
1710
|
-
updated_playbooks TEXT NOT NULL DEFAULT '[]'
|
|
1711
|
-
);
|
|
1712
|
-
CREATE INDEX IF NOT EXISTS idx_pacl_playbook_name ON playbook_aggregation_change_logs(playbook_name);
|
|
1713
|
-
CREATE INDEX IF NOT EXISTS idx_pacl_agent_version ON playbook_aggregation_change_logs(agent_version);
|
|
2127
|
+
CREATE INDEX IF NOT EXISTS idx_eval_created_at_desc
|
|
2128
|
+
ON agent_success_evaluation_result(created_at DESC);
|
|
2129
|
+
CREATE INDEX IF NOT EXISTS idx_eval_agent_version_created_at_desc
|
|
2130
|
+
ON agent_success_evaluation_result(agent_version, created_at DESC);
|
|
2131
|
+
CREATE INDEX IF NOT EXISTS idx_eval_identity_created_at_desc
|
|
2132
|
+
ON agent_success_evaluation_result(user_id, session_id, evaluation_name, agent_version, created_at DESC);
|
|
1714
2133
|
|
|
1715
2134
|
CREATE TABLE IF NOT EXISTS agent_playbook_source_user_playbooks (
|
|
1716
2135
|
agent_playbook_id INTEGER NOT NULL,
|
|
@@ -1745,6 +2164,7 @@ CREATE TABLE IF NOT EXISTS playbook_optimization_candidates (
|
|
|
1745
2164
|
parent_candidate_ids TEXT NOT NULL DEFAULT '[]',
|
|
1746
2165
|
aggregate_score REAL,
|
|
1747
2166
|
is_winner INTEGER NOT NULL DEFAULT 0,
|
|
2167
|
+
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
1748
2168
|
created_at INTEGER NOT NULL
|
|
1749
2169
|
);
|
|
1750
2170
|
CREATE INDEX IF NOT EXISTS idx_poc_job ON playbook_optimization_candidates(job_id);
|
|
@@ -1941,5 +2361,30 @@ CREATE INDEX IF NOT EXISTS idx_shadow_verdicts_created_at
|
|
|
1941
2361
|
ON shadow_comparison_verdicts (created_at);
|
|
1942
2362
|
CREATE INDEX IF NOT EXISTS idx_shadow_verdicts_prompt_v
|
|
1943
2363
|
ON shadow_comparison_verdicts (judge_prompt_version);
|
|
2364
|
+
CREATE INDEX IF NOT EXISTS idx_shadow_verdicts_prompt_created_at_desc
|
|
2365
|
+
ON shadow_comparison_verdicts (judge_prompt_version, created_at DESC);
|
|
2366
|
+
|
|
2367
|
+
-- ============================================================================
|
|
2368
|
+
-- Append-only, content-free lineage event log
|
|
2369
|
+
-- ============================================================================
|
|
2370
|
+
|
|
2371
|
+
CREATE TABLE IF NOT EXISTS lineage_event (
|
|
2372
|
+
event_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2373
|
+
org_id TEXT NOT NULL,
|
|
2374
|
+
entity_type TEXT NOT NULL,
|
|
2375
|
+
entity_id TEXT NOT NULL,
|
|
2376
|
+
op TEXT NOT NULL,
|
|
2377
|
+
prov_relation TEXT NOT NULL DEFAULT '',
|
|
2378
|
+
source_ids TEXT NOT NULL DEFAULT '[]',
|
|
2379
|
+
actor TEXT NOT NULL DEFAULT '',
|
|
2380
|
+
request_id TEXT NOT NULL DEFAULT '',
|
|
2381
|
+
reason TEXT NOT NULL DEFAULT '',
|
|
2382
|
+
created_at INTEGER NOT NULL,
|
|
2383
|
+
from_status TEXT,
|
|
2384
|
+
to_status TEXT,
|
|
2385
|
+
status_namespace TEXT,
|
|
2386
|
+
UNIQUE (org_id, entity_type, entity_id, op, request_id)
|
|
2387
|
+
);
|
|
2388
|
+
CREATE INDEX IF NOT EXISTS idx_lineage_entity ON lineage_event (entity_type, entity_id);
|
|
1944
2389
|
|
|
1945
2390
|
"""
|