claude-smart 0.2.45 → 0.2.47
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 +47 -10
- package/bin/claude-smart.js +535 -23
- 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 +27 -6
- 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/layout.tsx +20 -0
- 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 +232 -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 +251 -0
- package/plugin/opencode/tsconfig.json +13 -0
- package/plugin/pyproject.toml +25 -4
- package/plugin/scripts/_lib.sh +5 -1
- package/plugin/scripts/backend-service.sh +22 -2
- package/plugin/scripts/ensure-plugin-root.sh +4 -4
- package/plugin/scripts/hook_entry.sh +1 -1
- 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 +17 -9
- package/plugin/src/README.md +1 -1
- package/plugin/src/claude_smart/cli.py +498 -12
- package/plugin/src/claude_smart/context_format.py +9 -9
- package/plugin/src/claude_smart/cs_cite.py +66 -19
- 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 +5 -5
- package/plugin/vendor/reflexio/.env.example +54 -30
- package/plugin/vendor/reflexio/README.md +17 -11
- package/plugin/vendor/reflexio/pyproject.toml +13 -1
- package/plugin/vendor/reflexio/reflexio/README.md +4 -1
- 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 +212 -52
- 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/cli/utils.py +44 -1
- package/plugin/vendor/reflexio/reflexio/client/client.py +154 -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 +246 -10
- package/plugin/vendor/reflexio/reflexio/lib/_base.py +15 -0
- package/plugin/vendor/reflexio/reflexio/lib/_config.py +11 -4
- package/plugin/vendor/reflexio/reflexio/lib/_generation.py +17 -5
- package/plugin/vendor/reflexio/reflexio/lib/_lineage_parity_readers.py +187 -0
- package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +224 -21
- package/plugin/vendor/reflexio/reflexio/lib/_reflection.py +1 -1
- package/plugin/vendor/reflexio/reflexio/lib/_search.py +52 -25
- package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +11 -1
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/__init__.py +1 -0
- 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/domain/governance.py +117 -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 +97 -3
- 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 +104 -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 +58 -33
- 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/api.py +899 -103
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/precondition_checks.py +7 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +4 -2
- package/plugin/vendor/reflexio/reflexio/server/auth.py +90 -0
- 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/extensions.py +213 -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/model_defaults.py +4 -4
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +1 -1
- 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/rerank/cross_encoder_reranker.py +12 -1
- 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 +278 -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 +28 -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 +47 -16
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +105 -23
- package/plugin/vendor/reflexio/reflexio/server/services/governance/config.py +52 -0
- package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +378 -0
- package/plugin/vendor/reflexio/reflexio/server/services/governance/subject_refs.py +34 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +219 -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 +18 -11
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_prompt_processing.py +100 -0
- 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} +334 -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_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} +31 -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/recency.py +211 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +99 -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 +31 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +681 -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 +16 -2
- 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 +644 -197
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +106 -119
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +1965 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +630 -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 +31 -1369
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +756 -210
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +148 -34
- 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/sqlite_storage/playbook/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +952 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +189 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_optimization.py +247 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_source_linkage.py +145 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +838 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +150 -23
- 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 +40 -57
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_governance.py +148 -0
- 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 +4 -677
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +134 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +62 -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/storage/storage_base/playbook/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +365 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +124 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_optimization.py +85 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_source_linkage.py +47 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +333 -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 +372 -34
- 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/_auth.py +0 -25
- 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
|
@@ -17,7 +17,8 @@ from collections import OrderedDict
|
|
|
17
17
|
from collections.abc import Callable
|
|
18
18
|
from concurrent.futures import Future, ThreadPoolExecutor
|
|
19
19
|
from concurrent.futures import TimeoutError as FuturesTimeoutError
|
|
20
|
-
from
|
|
20
|
+
from datetime import UTC, datetime
|
|
21
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
21
22
|
|
|
22
23
|
from reflexio.models.api_schema.retriever_schema import (
|
|
23
24
|
ConversationTurn,
|
|
@@ -41,7 +42,14 @@ from reflexio.models.config_schema import (
|
|
|
41
42
|
from reflexio.server.llm.litellm_client import LiteLLMClient
|
|
42
43
|
from reflexio.server.prompt.prompt_manager import PromptManager
|
|
43
44
|
from reflexio.server.services.pre_retrieval import QueryReformulator
|
|
44
|
-
from reflexio.server.services.retrieval.
|
|
45
|
+
from reflexio.server.services.retrieval.recency import (
|
|
46
|
+
RecencyConfig,
|
|
47
|
+
ScoredItem,
|
|
48
|
+
additive_penalty,
|
|
49
|
+
decay_for_item,
|
|
50
|
+
multiplicative_factor,
|
|
51
|
+
)
|
|
52
|
+
from reflexio.server.services.retrieval.relevance_floor import apply_relevance_floors
|
|
45
53
|
from reflexio.server.services.storage.storage_base import BaseStorage
|
|
46
54
|
from reflexio.server.tracing import profile_step, set_span_data
|
|
47
55
|
|
|
@@ -50,6 +58,7 @@ if TYPE_CHECKING:
|
|
|
50
58
|
|
|
51
59
|
logger = logging.getLogger(__name__)
|
|
52
60
|
_DEFAULT_ENTITY_TYPES = frozenset({"profiles", "agent_playbooks", "user_playbooks"})
|
|
61
|
+
_SOURCE_USER_PLAYBOOK_IDS_KEY = "_source_user_playbook_ids"
|
|
53
62
|
# Statuses returned for agent_playbooks when the caller does not pass an
|
|
54
63
|
# explicit ``agent_playbook_status_filter``. Excludes REJECTED so that a
|
|
55
64
|
# rejection in the dashboard immediately suppresses the playbook from search
|
|
@@ -66,6 +75,7 @@ _SEARCH_FANOUT_EXECUTOR = ThreadPoolExecutor(
|
|
|
66
75
|
max_workers=_SEARCH_FANOUT_MAX_WORKERS,
|
|
67
76
|
thread_name_prefix="reflexio-search",
|
|
68
77
|
)
|
|
78
|
+
_ENV_SINGLE_RPC = "REFLEXIO_UNIFIED_SEARCH_SINGLE_RPC"
|
|
69
79
|
_EMBEDDING_CACHE_TTL_SECONDS = max(
|
|
70
80
|
0, int(os.getenv("REFLEXIO_QUERY_EMBEDDING_CACHE_TTL_SECONDS", "300") or "300")
|
|
71
81
|
)
|
|
@@ -76,6 +86,20 @@ _embedding_cache_lock = threading.Lock()
|
|
|
76
86
|
_embedding_cache: OrderedDict[tuple[str, int, str, str], tuple[float, list[float]]] = (
|
|
77
87
|
OrderedDict()
|
|
78
88
|
)
|
|
89
|
+
RetrievalCaptureHook = Callable[
|
|
90
|
+
[UnifiedSearchRequest, UnifiedSearchResponse, BaseStorage, str], None
|
|
91
|
+
]
|
|
92
|
+
_retrieval_capture_hook: RetrievalCaptureHook | None = None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def configure_retrieval_capture_hook(hook: RetrievalCaptureHook | None) -> None:
|
|
96
|
+
"""Register an optional final-response retrieval capture hook.
|
|
97
|
+
|
|
98
|
+
Deployments that capture retrieval logs install the hook; OSS leaves it
|
|
99
|
+
unset so unified search behavior is unchanged by default.
|
|
100
|
+
"""
|
|
101
|
+
global _retrieval_capture_hook
|
|
102
|
+
_retrieval_capture_hook = hook
|
|
79
103
|
|
|
80
104
|
|
|
81
105
|
def run_unified_search(
|
|
@@ -86,6 +110,7 @@ def run_unified_search(
|
|
|
86
110
|
prompt_manager: PromptManager,
|
|
87
111
|
pre_retrieval_model_name: str | None = None,
|
|
88
112
|
retrieval_floor: RetrievalFloorConfig | None = None,
|
|
113
|
+
recency: RecencyConfig | None = None,
|
|
89
114
|
) -> UnifiedSearchResponse:
|
|
90
115
|
"""
|
|
91
116
|
Search across all entity types (profiles, agent playbooks, user playbooks) in parallel.
|
|
@@ -113,7 +138,12 @@ def run_unified_search(
|
|
|
113
138
|
|
|
114
139
|
floor_cfg = retrieval_floor or RetrievalFloorConfig()
|
|
115
140
|
floor_on = floor_cfg.enabled
|
|
116
|
-
|
|
141
|
+
recency_on = bool(recency and recency.enabled)
|
|
142
|
+
fetch_k = max(
|
|
143
|
+
top_k,
|
|
144
|
+
floor_cfg.pool_size if floor_on else 0,
|
|
145
|
+
recency.pool_size if recency_on and recency is not None else 0,
|
|
146
|
+
)
|
|
117
147
|
|
|
118
148
|
# --- Phase A: query reformulation + embedding generation ---
|
|
119
149
|
reformulated_query, embedding = _run_phase_a(
|
|
@@ -137,6 +167,7 @@ def run_unified_search(
|
|
|
137
167
|
query=reformulated_query,
|
|
138
168
|
top_k=fetch_k,
|
|
139
169
|
threshold=threshold,
|
|
170
|
+
recency_on=recency_on,
|
|
140
171
|
)
|
|
141
172
|
|
|
142
173
|
if profiles is None:
|
|
@@ -150,9 +181,36 @@ def run_unified_search(
|
|
|
150
181
|
user_playbooks=user_playbooks, # type: ignore[arg-type]
|
|
151
182
|
top_k=top_k,
|
|
152
183
|
cfg=floor_cfg,
|
|
184
|
+
recency=recency if recency_on else None,
|
|
185
|
+
)
|
|
186
|
+
elif recency_on and recency is not None:
|
|
187
|
+
profiles = _apply_combined_score_recency(
|
|
188
|
+
profiles or [], entity_type="profiles", top_k=top_k, cfg=recency
|
|
189
|
+
)
|
|
190
|
+
agent_playbooks = _apply_combined_score_recency(
|
|
191
|
+
agent_playbooks or [],
|
|
192
|
+
entity_type="agent_playbooks",
|
|
193
|
+
top_k=top_k,
|
|
194
|
+
cfg=recency,
|
|
195
|
+
)
|
|
196
|
+
user_playbooks = _apply_combined_score_recency(
|
|
197
|
+
user_playbooks or [],
|
|
198
|
+
entity_type="user_playbooks",
|
|
199
|
+
top_k=top_k,
|
|
200
|
+
cfg=recency,
|
|
153
201
|
)
|
|
202
|
+
else:
|
|
203
|
+
profiles = _unwrap_items(profiles or [])[:top_k]
|
|
204
|
+
agent_playbooks = _unwrap_items(agent_playbooks or [])[:top_k]
|
|
205
|
+
user_playbooks = _unwrap_items(user_playbooks or [])[:top_k]
|
|
154
206
|
|
|
155
|
-
|
|
207
|
+
user_playbooks = _suppress_source_user_playbooks(
|
|
208
|
+
storage=storage,
|
|
209
|
+
agent_playbooks=agent_playbooks or [],
|
|
210
|
+
user_playbooks=user_playbooks or [],
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
response = UnifiedSearchResponse(
|
|
156
214
|
success=True,
|
|
157
215
|
profiles=profiles,
|
|
158
216
|
agent_playbooks=agent_playbooks, # type: ignore[reportArgumentType]
|
|
@@ -161,6 +219,32 @@ def run_unified_search(
|
|
|
161
219
|
if reformulated_query != request.query
|
|
162
220
|
else None,
|
|
163
221
|
)
|
|
222
|
+
_maybe_capture_final_response(
|
|
223
|
+
request=request,
|
|
224
|
+
response=response,
|
|
225
|
+
storage=storage,
|
|
226
|
+
org_id=org_id,
|
|
227
|
+
)
|
|
228
|
+
return response
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _maybe_capture_final_response(
|
|
232
|
+
*,
|
|
233
|
+
request: UnifiedSearchRequest,
|
|
234
|
+
response: UnifiedSearchResponse,
|
|
235
|
+
storage: BaseStorage,
|
|
236
|
+
org_id: str,
|
|
237
|
+
) -> None:
|
|
238
|
+
hook = _retrieval_capture_hook
|
|
239
|
+
if hook is None:
|
|
240
|
+
return
|
|
241
|
+
try:
|
|
242
|
+
hook(request, response, storage, org_id)
|
|
243
|
+
except Exception:
|
|
244
|
+
logger.warning(
|
|
245
|
+
"Unified search retrieval capture hook failed",
|
|
246
|
+
exc_info=True,
|
|
247
|
+
)
|
|
164
248
|
|
|
165
249
|
|
|
166
250
|
def _run_phase_a(
|
|
@@ -236,10 +320,11 @@ def _run_phase_b(
|
|
|
236
320
|
query: str,
|
|
237
321
|
top_k: int,
|
|
238
322
|
threshold: float,
|
|
323
|
+
recency_on: bool = False,
|
|
239
324
|
) -> tuple[
|
|
240
|
-
list[
|
|
241
|
-
list[
|
|
242
|
-
list[
|
|
325
|
+
list[Any] | None,
|
|
326
|
+
list[Any] | None,
|
|
327
|
+
list[Any] | None,
|
|
243
328
|
]:
|
|
244
329
|
"""Run parallel searches across all entity types by delegating to storage methods.
|
|
245
330
|
|
|
@@ -266,6 +351,43 @@ def _run_phase_b(
|
|
|
266
351
|
entity_types=sorted(entity_types),
|
|
267
352
|
top_k=top_k,
|
|
268
353
|
) as span:
|
|
354
|
+
# Recency needs the per-row ``combined_score``, which only the scored
|
|
355
|
+
# single-RPC method threads back. Backends that don't advertise
|
|
356
|
+
# ``supports_unified_hybrid_search`` (e.g. native Postgres, which still
|
|
357
|
+
# inherits ``unified_hybrid_search_scored`` and runs it via the same
|
|
358
|
+
# ``_rpc`` it already uses for ``hybrid_match_*``) opt into the scored
|
|
359
|
+
# path only when recency is on, so non-recency routing is unchanged.
|
|
360
|
+
wants_scored_single_rpc = recency_on and callable(
|
|
361
|
+
getattr(storage, "unified_hybrid_search_scored", None)
|
|
362
|
+
)
|
|
363
|
+
if _unified_single_rpc_enabled() and (
|
|
364
|
+
getattr(storage, "supports_unified_hybrid_search", False)
|
|
365
|
+
or wants_scored_single_rpc
|
|
366
|
+
):
|
|
367
|
+
combined = _run_phase_b_single_rpc(
|
|
368
|
+
request=request,
|
|
369
|
+
storage=storage,
|
|
370
|
+
embedding=embedding,
|
|
371
|
+
query=query,
|
|
372
|
+
top_k=top_k,
|
|
373
|
+
threshold=threshold,
|
|
374
|
+
entity_types=entity_types,
|
|
375
|
+
allowed_agent_statuses=allowed_agent_statuses,
|
|
376
|
+
recency_on=recency_on,
|
|
377
|
+
)
|
|
378
|
+
if combined is not None:
|
|
379
|
+
profiles, agent_playbooks, user_playbooks = combined
|
|
380
|
+
set_span_data(
|
|
381
|
+
span,
|
|
382
|
+
{
|
|
383
|
+
"single_rpc": True,
|
|
384
|
+
"profiles_count": len(profiles),
|
|
385
|
+
"agent_playbooks_count": len(agent_playbooks),
|
|
386
|
+
"user_playbooks_count": len(user_playbooks),
|
|
387
|
+
},
|
|
388
|
+
)
|
|
389
|
+
return profiles, agent_playbooks, user_playbooks
|
|
390
|
+
span.set_data("single_rpc_fallback", True)
|
|
269
391
|
profiles_future = (
|
|
270
392
|
_submit_with_current_context(
|
|
271
393
|
_SEARCH_FANOUT_EXECUTOR,
|
|
@@ -347,6 +469,96 @@ def _run_phase_b(
|
|
|
347
469
|
return profiles, agent_playbooks, user_playbooks
|
|
348
470
|
|
|
349
471
|
|
|
472
|
+
def _unified_single_rpc_enabled() -> bool:
|
|
473
|
+
"""Kill switch for the combined Phase B RPC (default on)."""
|
|
474
|
+
return os.getenv(_ENV_SINGLE_RPC, "1").strip().lower() not in {"0", "false", "off"}
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def _run_phase_b_single_rpc(
|
|
478
|
+
*,
|
|
479
|
+
request: UnifiedSearchRequest,
|
|
480
|
+
storage: BaseStorage,
|
|
481
|
+
embedding: list[float] | None,
|
|
482
|
+
query: str,
|
|
483
|
+
top_k: int,
|
|
484
|
+
threshold: float,
|
|
485
|
+
entity_types: set[str],
|
|
486
|
+
allowed_agent_statuses: list[PlaybookStatus] | None,
|
|
487
|
+
recency_on: bool = False,
|
|
488
|
+
) -> tuple[list[Any], list[Any], list[Any]] | None:
|
|
489
|
+
"""Run all Phase B arms through one combined storage round trip.
|
|
490
|
+
|
|
491
|
+
Trades the per-arm round-trip overhead for serialized execution of the
|
|
492
|
+
three queries inside one database session — a win when round-trip
|
|
493
|
+
overhead dominates per-arm query time (toggle via
|
|
494
|
+
``REFLEXIO_UNIFIED_SEARCH_SINGLE_RPC`` to compare).
|
|
495
|
+
|
|
496
|
+
Returns:
|
|
497
|
+
The three result lists, or None when the combined call fails so the
|
|
498
|
+
caller can fall back to the per-arm fan-out (e.g. the SQL function
|
|
499
|
+
is not yet migrated on this deployment). Timeouts propagate like the
|
|
500
|
+
fan-out path so a hung database is not retried.
|
|
501
|
+
"""
|
|
502
|
+
statuses = (
|
|
503
|
+
list(allowed_agent_statuses)
|
|
504
|
+
if allowed_agent_statuses
|
|
505
|
+
else list(_DEFAULT_AGENT_PLAYBOOK_STATUSES)
|
|
506
|
+
)
|
|
507
|
+
# Resolve storage.unified_hybrid_search before submit so missing or stale
|
|
508
|
+
# capability flags can fall back to the fan-out path.
|
|
509
|
+
method_name = (
|
|
510
|
+
"unified_hybrid_search_scored" if recency_on else "unified_hybrid_search"
|
|
511
|
+
)
|
|
512
|
+
unified_hybrid_search = getattr(storage, method_name, None)
|
|
513
|
+
if not callable(unified_hybrid_search):
|
|
514
|
+
if recency_on:
|
|
515
|
+
logger.warning(
|
|
516
|
+
"event=search_recency_missing_scores source=single_rpc method=%s",
|
|
517
|
+
method_name,
|
|
518
|
+
)
|
|
519
|
+
return None
|
|
520
|
+
|
|
521
|
+
future = _submit_with_current_context(
|
|
522
|
+
_SEARCH_FANOUT_EXECUTOR,
|
|
523
|
+
unified_hybrid_search,
|
|
524
|
+
query=query,
|
|
525
|
+
query_embedding=embedding,
|
|
526
|
+
top_k=top_k,
|
|
527
|
+
threshold=threshold,
|
|
528
|
+
user_id=request.user_id,
|
|
529
|
+
agent_version=request.agent_version,
|
|
530
|
+
playbook_name=request.playbook_name,
|
|
531
|
+
agent_playbook_statuses=statuses,
|
|
532
|
+
search_mode=request.search_mode,
|
|
533
|
+
include_profiles="profiles" in entity_types and bool(request.user_id),
|
|
534
|
+
include_agent_playbooks="agent_playbooks" in entity_types,
|
|
535
|
+
include_user_playbooks="user_playbooks" in entity_types,
|
|
536
|
+
)
|
|
537
|
+
try:
|
|
538
|
+
profiles, agent_playbooks, user_playbooks = future.result(timeout=30)
|
|
539
|
+
except FuturesTimeoutError:
|
|
540
|
+
raise
|
|
541
|
+
except Exception:
|
|
542
|
+
logger.warning(
|
|
543
|
+
"Unified single-RPC search failed; falling back to per-arm fan-out",
|
|
544
|
+
exc_info=True,
|
|
545
|
+
)
|
|
546
|
+
return None
|
|
547
|
+
|
|
548
|
+
# Mirror _search_agent_playbooks_via_storage: dedupe by id, cap at top_k.
|
|
549
|
+
deduped: list[Any] = []
|
|
550
|
+
seen_ids: set[str] = set()
|
|
551
|
+
for candidate in agent_playbooks:
|
|
552
|
+
playbook = _unwrap_item(candidate)
|
|
553
|
+
playbook_id = str(getattr(playbook, "agent_playbook_id", ""))
|
|
554
|
+
if playbook_id and playbook_id not in seen_ids:
|
|
555
|
+
seen_ids.add(playbook_id)
|
|
556
|
+
deduped.append(candidate)
|
|
557
|
+
if len(deduped) >= top_k:
|
|
558
|
+
break
|
|
559
|
+
return profiles, deduped, user_playbooks
|
|
560
|
+
|
|
561
|
+
|
|
350
562
|
def _apply_floors(
|
|
351
563
|
query: str,
|
|
352
564
|
profiles: list[UserProfile],
|
|
@@ -354,37 +566,163 @@ def _apply_floors(
|
|
|
354
566
|
user_playbooks: list[UserPlaybook],
|
|
355
567
|
top_k: int,
|
|
356
568
|
cfg: RetrievalFloorConfig,
|
|
569
|
+
recency: RecencyConfig | None = None,
|
|
357
570
|
) -> tuple[list[UserProfile], list[AgentPlaybook], list[UserPlaybook]]:
|
|
358
|
-
"""Apply the per-arm relevance floor
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
571
|
+
"""Apply the per-arm relevance floor with one batched cross-encoder call."""
|
|
572
|
+
floored_profiles, floored_agent, floored_user = apply_relevance_floors(
|
|
573
|
+
query,
|
|
574
|
+
[
|
|
575
|
+
("profiles", profiles, cfg.profile_floor),
|
|
576
|
+
("agent_playbooks", agent_playbooks, cfg.agent_playbook_floor),
|
|
577
|
+
("user_playbooks", user_playbooks, cfg.user_playbook_floor),
|
|
578
|
+
],
|
|
579
|
+
top_k,
|
|
580
|
+
content_of=lambda item: _unwrap_item(item).content,
|
|
581
|
+
)
|
|
582
|
+
return (
|
|
583
|
+
_finalize_floor_arm(
|
|
584
|
+
floored_profiles, entity_type="profiles", top_k=top_k, recency=recency
|
|
585
|
+
),
|
|
586
|
+
_finalize_floor_arm(
|
|
587
|
+
floored_agent,
|
|
588
|
+
entity_type="agent_playbooks",
|
|
589
|
+
top_k=top_k,
|
|
590
|
+
recency=recency,
|
|
591
|
+
),
|
|
592
|
+
_finalize_floor_arm(
|
|
593
|
+
floored_user,
|
|
594
|
+
entity_type="user_playbooks",
|
|
595
|
+
top_k=top_k,
|
|
596
|
+
recency=recency,
|
|
597
|
+
),
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
def _finalize_floor_arm(
|
|
602
|
+
result: Any,
|
|
603
|
+
*,
|
|
604
|
+
entity_type: str,
|
|
605
|
+
top_k: int,
|
|
606
|
+
recency: RecencyConfig | None,
|
|
607
|
+
) -> list[Any]:
|
|
608
|
+
if not recency or not recency.enabled:
|
|
609
|
+
return _unwrap_items(result.items)[:top_k]
|
|
610
|
+
if result.scores is None:
|
|
611
|
+
return _apply_combined_score_recency(
|
|
612
|
+
result.items, entity_type=entity_type, top_k=top_k, cfg=recency
|
|
613
|
+
)
|
|
614
|
+
now = int(datetime.now(UTC).timestamp())
|
|
615
|
+
rescored = []
|
|
616
|
+
for item, score in zip(result.items, result.scores, strict=True):
|
|
617
|
+
unwrapped = _unwrap_item(item)
|
|
618
|
+
freshness = decay_for_item(unwrapped, entity_type=entity_type, now=now)
|
|
619
|
+
rescored.append(
|
|
620
|
+
(unwrapped, score - additive_penalty(freshness, recency.max_penalty_logit))
|
|
368
621
|
)
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
622
|
+
rescored.sort(key=lambda pair: pair[1], reverse=True)
|
|
623
|
+
return [item for item, _score in rescored[:top_k]]
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def _apply_combined_score_recency(
|
|
627
|
+
items: list[Any],
|
|
628
|
+
*,
|
|
629
|
+
entity_type: str,
|
|
630
|
+
top_k: int,
|
|
631
|
+
cfg: RecencyConfig,
|
|
632
|
+
) -> list[Any]:
|
|
633
|
+
if not items:
|
|
634
|
+
return []
|
|
635
|
+
scored_items: list[tuple[Any, float]] = []
|
|
636
|
+
for item in items:
|
|
637
|
+
if not isinstance(item, ScoredItem) or item.score is None:
|
|
638
|
+
logger.warning(
|
|
639
|
+
"event=search_recency_missing_scores entity_type=%s items=%d",
|
|
640
|
+
entity_type,
|
|
641
|
+
len(items),
|
|
642
|
+
)
|
|
643
|
+
return _unwrap_items(items)[:top_k]
|
|
644
|
+
scored_items.append((item.item, item.score))
|
|
645
|
+
now = int(datetime.now(UTC).timestamp())
|
|
646
|
+
rescored = []
|
|
647
|
+
for item, score in scored_items:
|
|
648
|
+
freshness = decay_for_item(item, entity_type=entity_type, now=now)
|
|
649
|
+
rescored.append(
|
|
650
|
+
(
|
|
651
|
+
item,
|
|
652
|
+
score * multiplicative_factor(freshness, cfg.max_penalty_frac),
|
|
653
|
+
)
|
|
377
654
|
)
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
655
|
+
rescored.sort(key=lambda pair: pair[1], reverse=True)
|
|
656
|
+
return [item for item, _score in rescored[:top_k]]
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
def _unwrap_item(item: Any) -> Any:
|
|
660
|
+
return item.item if isinstance(item, ScoredItem) else item
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
def _unwrap_items(items: list[Any]) -> list[Any]:
|
|
664
|
+
return [_unwrap_item(item) for item in items]
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
def _suppress_source_user_playbooks(
|
|
668
|
+
*,
|
|
669
|
+
storage: BaseStorage,
|
|
670
|
+
agent_playbooks: list[AgentPlaybook],
|
|
671
|
+
user_playbooks: list[UserPlaybook],
|
|
672
|
+
) -> list[UserPlaybook]:
|
|
673
|
+
"""Drop user playbooks already represented by returned agent playbooks."""
|
|
674
|
+
if not agent_playbooks or not user_playbooks:
|
|
675
|
+
return user_playbooks
|
|
676
|
+
|
|
677
|
+
source_user_playbook_ids: set[int] = set()
|
|
678
|
+
agent_ids_needing_lookup: list[int] = []
|
|
679
|
+
for playbook in agent_playbooks:
|
|
680
|
+
source_ids = getattr(playbook, _SOURCE_USER_PLAYBOOK_IDS_KEY, None)
|
|
681
|
+
if source_ids is None:
|
|
682
|
+
agent_playbook_id = int(getattr(playbook, "agent_playbook_id", 0) or 0)
|
|
683
|
+
if agent_playbook_id:
|
|
684
|
+
agent_ids_needing_lookup.append(agent_playbook_id)
|
|
685
|
+
continue
|
|
686
|
+
source_user_playbook_ids.update(int(source_id) for source_id in source_ids)
|
|
687
|
+
|
|
688
|
+
if agent_ids_needing_lookup:
|
|
689
|
+
lookup = getattr(
|
|
690
|
+
storage, "get_source_user_playbook_ids_for_agent_playbooks", None
|
|
386
691
|
)
|
|
387
|
-
|
|
692
|
+
if callable(lookup):
|
|
693
|
+
try:
|
|
694
|
+
source_ids_by_agent = cast(
|
|
695
|
+
dict[int, list[int]], lookup(agent_ids_needing_lookup)
|
|
696
|
+
)
|
|
697
|
+
except Exception:
|
|
698
|
+
logger.warning(
|
|
699
|
+
"Failed to resolve source user playbooks for unified search suppression",
|
|
700
|
+
exc_info=True,
|
|
701
|
+
)
|
|
702
|
+
else:
|
|
703
|
+
for source_ids in source_ids_by_agent.values():
|
|
704
|
+
source_user_playbook_ids.update(
|
|
705
|
+
int(source_id) for source_id in source_ids
|
|
706
|
+
)
|
|
707
|
+
|
|
708
|
+
if not source_user_playbook_ids:
|
|
709
|
+
return user_playbooks
|
|
710
|
+
|
|
711
|
+
filtered = [
|
|
712
|
+
playbook
|
|
713
|
+
for playbook in user_playbooks
|
|
714
|
+
if int(getattr(playbook, "user_playbook_id", 0) or 0)
|
|
715
|
+
not in source_user_playbook_ids
|
|
716
|
+
]
|
|
717
|
+
suppressed_count = len(user_playbooks) - len(filtered)
|
|
718
|
+
if suppressed_count:
|
|
719
|
+
with profile_step(
|
|
720
|
+
"search.suppress_source_user_playbooks",
|
|
721
|
+
suppressed_count=suppressed_count,
|
|
722
|
+
source_user_playbook_count=len(source_user_playbook_ids),
|
|
723
|
+
):
|
|
724
|
+
pass
|
|
725
|
+
return filtered
|
|
388
726
|
|
|
389
727
|
|
|
390
728
|
def _get_cached_query_embedding(
|
|
@@ -11,7 +11,7 @@ Description: Global configuration manager for site-wide variables and per-org fe
|
|
|
11
11
|
|
|
12
12
|
## Purpose
|
|
13
13
|
|
|
14
|
-
1. **Global settings** - Model names, embedding models
|
|
14
|
+
1. **Global settings** - Model names, embedding models, and retrieval defaults
|
|
15
15
|
2. **Feature flags** - Per-org feature gating (global enable or per-org allowlist)
|
|
16
16
|
3. **Dual storage** - File-based with optional Redis caching
|
|
17
17
|
4. **Auto-fallback** - Redis → File system graceful degradation
|
|
@@ -62,6 +62,7 @@ site_var/
|
|
|
62
62
|
└── site_var_sources/
|
|
63
63
|
├── app_config.json # JSON → parsed dict
|
|
64
64
|
├── model_config.json
|
|
65
|
+
├── search_settings.json # Default retrieval mode and hybrid search weights
|
|
65
66
|
└── feature_flags.json # Feature flag config (per-flag enable + org allowlist)
|
|
66
67
|
```
|
|
67
68
|
|
|
@@ -55,7 +55,7 @@ def is_feature_enabled(org_id: str, feature_name: str) -> bool:
|
|
|
55
55
|
if feature_config.get("enabled", False):
|
|
56
56
|
return True
|
|
57
57
|
|
|
58
|
-
enabled_org_ids = feature_config.get("enabled_org_ids", [])
|
|
58
|
+
enabled_org_ids = feature_config.get("enabled_org_ids", []) or []
|
|
59
59
|
return org_id in enabled_org_ids
|
|
60
60
|
|
|
61
61
|
|
|
@@ -103,6 +103,103 @@ def is_deduplicator_enabled(org_id: str) -> bool:
|
|
|
103
103
|
return is_feature_enabled(org_id, "deduplicator")
|
|
104
104
|
|
|
105
105
|
|
|
106
|
+
def _is_fail_closed_flag_enabled(org_id: str, feature_key: str) -> bool:
|
|
107
|
+
"""
|
|
108
|
+
Shared fail-closed helper: returns False if key absent or config malformed.
|
|
109
|
+
|
|
110
|
+
Unlike is_feature_enabled (fail-open), this function returns False when the
|
|
111
|
+
feature key is absent or when its value is not a dict. This is the safety
|
|
112
|
+
invariant for soft-delete flags — unconfigured or malformed entries must
|
|
113
|
+
never accidentally activate tombstone-producing paths.
|
|
114
|
+
|
|
115
|
+
A feature is enabled if:
|
|
116
|
+
- The feature's "enabled" field is True (globally enabled), OR
|
|
117
|
+
- The org_id is in the feature's "enabled_org_ids" list.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
org_id (str): The organization ID to check
|
|
121
|
+
feature_key (str): The feature flag key in the config dict
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
bool: True only if the feature is explicitly enabled for this org
|
|
125
|
+
"""
|
|
126
|
+
config = _get_feature_flags_config()
|
|
127
|
+
feature_config = config.get(feature_key)
|
|
128
|
+
|
|
129
|
+
if feature_config is None:
|
|
130
|
+
# Key absent — fail-CLOSED
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
if not isinstance(feature_config, dict):
|
|
134
|
+
logger.warning(
|
|
135
|
+
"feature_flags[%s] is not a dict (got %s), defaulting to OFF",
|
|
136
|
+
feature_key,
|
|
137
|
+
type(feature_config).__name__,
|
|
138
|
+
)
|
|
139
|
+
return False
|
|
140
|
+
|
|
141
|
+
# Strict bool identity — truthy strings like "false" must not enable (#195).
|
|
142
|
+
enabled = feature_config.get("enabled", False)
|
|
143
|
+
if enabled is True:
|
|
144
|
+
return True
|
|
145
|
+
|
|
146
|
+
# Reject non-list values — a string does substring `in` match, not membership (#195).
|
|
147
|
+
org_ids = feature_config.get("enabled_org_ids", [])
|
|
148
|
+
if not isinstance(org_ids, list):
|
|
149
|
+
return False
|
|
150
|
+
return org_id in org_ids
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _is_default_open_flag_enabled(org_id: str, feature_key: str) -> bool:
|
|
154
|
+
"""
|
|
155
|
+
Shared default-open helper for soft-delete flags.
|
|
156
|
+
|
|
157
|
+
Returns True when the feature key is absent from config (default ON), but
|
|
158
|
+
preserves all explicit-disable and per-org override semantics:
|
|
159
|
+
- Key absent or None → True (default ON — GC is also ON by default).
|
|
160
|
+
- Key present but malformed (not a dict) → False with a warning (safe fallback).
|
|
161
|
+
- Key present, enabled=True → True for all orgs.
|
|
162
|
+
- Key present, enabled=False, org in enabled_org_ids → True.
|
|
163
|
+
- Key present, enabled=False, org NOT in enabled_org_ids → False (explicit disable).
|
|
164
|
+
|
|
165
|
+
Strict-bool and strict-list guards from _is_fail_closed_flag_enabled are
|
|
166
|
+
preserved: truthy strings and non-bool ints do NOT enable, and a string
|
|
167
|
+
enabled_org_ids does NOT match via substring (anti-#195).
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
org_id (str): The organization ID to check
|
|
171
|
+
feature_key (str): The feature flag key in the config dict
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
bool: True when the flag is on (including when absent/unconfigured)
|
|
175
|
+
"""
|
|
176
|
+
config = _get_feature_flags_config()
|
|
177
|
+
feature_config = config.get(feature_key)
|
|
178
|
+
|
|
179
|
+
if feature_config is None:
|
|
180
|
+
# Key absent — default OPEN (soft-delete is on by default; GC runs too).
|
|
181
|
+
return True
|
|
182
|
+
|
|
183
|
+
if not isinstance(feature_config, dict):
|
|
184
|
+
logger.warning(
|
|
185
|
+
"feature_flags[%s] is not a dict (got %s), defaulting to OFF",
|
|
186
|
+
feature_key,
|
|
187
|
+
type(feature_config).__name__,
|
|
188
|
+
)
|
|
189
|
+
return False
|
|
190
|
+
|
|
191
|
+
# Strict bool identity — truthy strings like "false" must not enable (#195).
|
|
192
|
+
enabled = feature_config.get("enabled", False)
|
|
193
|
+
if enabled is True:
|
|
194
|
+
return True
|
|
195
|
+
|
|
196
|
+
# Reject non-list values — a string does substring `in` match, not membership (#195).
|
|
197
|
+
org_ids = feature_config.get("enabled_org_ids", [])
|
|
198
|
+
if not isinstance(org_ids, list):
|
|
199
|
+
return False
|
|
200
|
+
return org_id in org_ids
|
|
201
|
+
|
|
202
|
+
|
|
106
203
|
def is_resumable_extraction_agent_enabled(org_id: str) -> bool:
|
|
107
204
|
"""
|
|
108
205
|
Convenience check for whether classic extraction should use the resumable agent.
|
|
@@ -114,3 +211,25 @@ def is_resumable_extraction_agent_enabled(org_id: str) -> bool:
|
|
|
114
211
|
bool: True if the resumable extraction agent is enabled
|
|
115
212
|
"""
|
|
116
213
|
return is_feature_enabled(org_id, "resumable_extraction_agent")
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def is_lineage_dual_read_diff_enabled(org_id: str) -> bool:
|
|
217
|
+
"""
|
|
218
|
+
Check if the lineage dual-read diff path is enabled for a given organization.
|
|
219
|
+
|
|
220
|
+
Defaults to DISABLED when the key is absent from config (default-CLOSED). This
|
|
221
|
+
flag gates an experimental read path that compares new and old lineage query
|
|
222
|
+
results side-by-side; it must never activate accidentally on unconfigured
|
|
223
|
+
deployments.
|
|
224
|
+
|
|
225
|
+
A feature is enabled only if:
|
|
226
|
+
- The feature's "enabled" field is True (globally enabled), OR
|
|
227
|
+
- The org_id is in the feature's "enabled_org_ids" list.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
org_id (str): The organization ID to check
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
bool: True only if the feature is explicitly enabled for this org
|
|
234
|
+
"""
|
|
235
|
+
return _is_fail_closed_flag_enabled(org_id, "lineage_dual_read_diff")
|